From e79d05f8ef9eab0f0a276f79e08ae8419561ddf4 Mon Sep 17 00:00:00 2001 From: springswell <315791293+springswell@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:57:52 +0100 Subject: [PATCH 1/2] fix(api): stop sending wallet address as a /policies query param fetchUserPolicies sent the wallet as ?wallet=..., leaking the address into server/proxy access logs and browser history. The backend already resolves the wallet from the JWT and ignores this param (backend #345), so drop it and the now-unused argument; usePolicies still gates the fetch on a connected wallet. Closes #525 --- src/hooks/usePolicies.ts | 2 +- src/lib/api.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/hooks/usePolicies.ts b/src/hooks/usePolicies.ts index beccb22..cba8356 100644 --- a/src/hooks/usePolicies.ts +++ b/src/hooks/usePolicies.ts @@ -23,7 +23,7 @@ export function usePolicies(walletAddress: string | null) { } setError(null); try { - const data = await fetchUserPolicies(walletAddress); + const data = await fetchUserPolicies(); if (signal.aborted) return; setPolicies(data); } catch (err) { diff --git a/src/lib/api.ts b/src/lib/api.ts index d2be109..e15429e 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -147,8 +147,14 @@ export function fetchProduct(id: string): Promise { // ── Policies ────────────────────────────────────────────────────────────────── -export function fetchUserPolicies(wallet: string): Promise { - return get('/policies', { params: { wallet } }); +/** + * Policies for the authenticated wallet. The backend resolves the wallet from + * the JWT (backend #345), so it is deliberately NOT sent as a `?wallet=` query + * param -- query strings end up in server/proxy access logs and browser + * history, leaking the user's address in plaintext (#525). + */ +export function fetchUserPolicies(): Promise { + return get('/policies'); } export function fetchPolicy(id: string): Promise { From bb9ad91fe7dab2ac7a767df2ed2f06ed79fd2aac Mon Sep 17 00:00:00 2001 From: springswell <315791293+springswell@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:57:53 +0100 Subject: [PATCH 2/2] test(policies): assert wallet is not passed to fetchUserPolicies Add a regression test that fetchUserPolicies is called with no wallet argument, and key the stale-wallet test's pending requests by the wallet active at call time instead of the removed argument. Refs #525 --- src/__tests__/usePolicies.test.tsx | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/__tests__/usePolicies.test.tsx b/src/__tests__/usePolicies.test.tsx index 52404db..c29d163 100644 --- a/src/__tests__/usePolicies.test.tsx +++ b/src/__tests__/usePolicies.test.tsx @@ -58,6 +58,18 @@ describe('usePolicies', () => { expect(hook.current.error).toBeNull(); }); + // #525: the wallet must not leak into the /policies query string -- the + // backend resolves it from the JWT, so the API call takes no wallet arg. + it('does not pass the wallet address to fetchUserPolicies', async () => { + fetchUserPolicies.mockResolvedValue([]); + + renderHook(() => usePolicies('GWALLET')); + await flushMicrotasks(); + + expect(fetchUserPolicies).toHaveBeenCalledTimes(1); + expect(fetchUserPolicies).toHaveBeenCalledWith(); + }); + it('surfaces an error message when the fetch fails', async () => { fetchUserPolicies.mockRejectedValue(new Error('failed to reach api')); @@ -88,13 +100,17 @@ describe('usePolicies', () => { }); it('discards a stale response from a previous wallet after the wallet address changes', async () => { + // fetchUserPolicies takes no wallet arg (#525 -- the backend reads it from + // the JWT), so key pending requests by the wallet active at call time. const pending: Record void> = {}; + let wallet = 'GWALLET_A'; fetchUserPolicies.mockImplementation( - (wallet: string) => - new Promise((resolve) => { pending[wallet] = resolve; }), + () => { + const forWallet = wallet; + return new Promise((resolve) => { pending[forWallet] = resolve; }); + }, ); - let wallet = 'GWALLET_A'; const hook = renderHook(() => usePolicies(wallet)); await flushMicrotasks(); expect(pending['GWALLET_A']).toBeDefined();