Skip to content

test: automated claim-link fragment-leak check (M3-14) - #34

Merged
YazarAyobami merged 1 commit into
mainfrom
feat/m3-14-fragment-leak-test
Oct 8, 2026
Merged

YazarAyobami merged 1 commit into
mainfrom
feat/m3-14-fragment-leak-test

Conversation

@YazarAyobami

Copy link
Copy Markdown
Contributor

What and why

M3-14's security headers/CSP implementation (proxy.ts, lib/security/csp.ts) already existed and was solid. The row's actual "Done when" criterion was an automated test confirming the claim-link fragment never appears in any network request, and that didn't exist: lib/security/csp.test.ts only unit-tests the CSP string-builder function, never real runtime browser behavior.

Changes

  • e2e/claim-fragment-privacy.spec.ts: loads /claim/[id] with a real-shaped claim-link fragment (reference + a generateSalt()-format salt), captures every outbound network request for the page's whole lifetime (URL, POST body, and headers), and fails if the reference or salt turns up anywhere.
  • playwright.config.ts: added a webServer (pnpm build && pnpm start, not dev, since proxy.ts's CSP differs by NODE_ENV) and a baseURL. Neither existed before; the config was a one-liner.

Hard rules / invariants touched

Rule 5 (claim-link fragment never reaches a server). This is the automated enforcement of it, not a change to it.

Tests

pnpm lint, pnpm typecheck, pnpm test (108/108), pnpm build all green.

Didn't just write the E2E test and assume it works. Verified it can actually fail, not just trivially pass:

  1. Temporarily injected a real leak into useClaim.ts (a stray fetch echoing the reference and salt to a throwaway path).
  2. Ran the test: it caught it immediately, with a clear diagnostic naming the exact leaked URL.
  3. Reverted the injection (git diff on that file is empty, confirming byte-identical to before).
  4. Ran the test again: clean pass.

Also hit and fixed two real test-design bugs along the way, not app bugs: an invalid (not generateSalt()-shaped) test salt silently made parseClaimLink throw before any of the code under test ever ran (so my first "passing" result was a false negative), and waitForLoadState("networkidle") never resolves on this page since it polls, so switched to a fixed wait window.

Docs / ADR

None needed.

Roadmap

M3-14 → DONE. Phase 4 App: 13→14 DONE, 6→5 IN PROGRESS (50%→54%). All rows: 43→44 DONE, 15→14 IN PROGRESS (24%→25%). Changelog entry added with the full verification account.

Assumptions and open questions

Not done here, by design: wiring this into CI. .github/workflows/e2e.yml is marked DRAFT (ask-first: CI) and currently just echoes a placeholder (E2E not configured yet) rather than running anything — that's why e2e has been passing trivially on every PR this whole session. Actually running pnpm e2e there is a CI/CD change, which AGENTS.md requires a human to approve first. No new secrets would be needed either way: every env var the test needs is already-public NEXT_PUBLIC_* testnet config.

If you want it enabled, the change is small:

-      - run: echo 'E2E not configured yet (M3-19)'
+      - run: pnpm exec playwright install --with-deps chromium
+      - run: pnpm e2e
+        env:
+          NEXT_PUBLIC_STELLAR_NETWORK: testnet
+          NEXT_PUBLIC_RPC_URLS: https://soroban-testnet.stellar.org
+          NEXT_PUBLIC_CONTRACT_ID: CCSHDQFRYFC3AHV5NE6ULQW6X2CMG5RPANBORDXJGSUD6UKECASJQBRI
+          NEXT_PUBLIC_USDC_CONTRACT_ID: CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA
+          NEXT_PUBLIC_USDC_ISSUER: GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5

The security headers/CSP work already existed; the row's actual
Done-when (an automated test confirming the claim-link fragment never
appears in any network request) didn't. csp.test.ts only unit-tests
the CSP string builder, not real runtime behavior.

Added e2e/claim-fragment-privacy.spec.ts: loads /claim/[id] with a
real-shaped fragment, captures every outbound request for the page's
lifetime, fails if the reference or salt appears anywhere. Configured
playwright.config.ts with a webServer and baseURL (neither existed).

Verified the test can actually fail: temporarily injected a real leak
into useClaim.ts, confirmed the test caught it, reverted (byte-
identical diff), confirmed a clean pass on the real code.

Not done here: wiring into CI. .github/workflows/e2e.yml is marked
DRAFT (ask-first: CI); editing it needs explicit approval.
@YazarAyobami
YazarAyobami requested a review from a team as a code owner October 8, 2026 15:01
@YazarAyobami
YazarAyobami merged commit 156db2e into main Oct 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants