From 03bde8d40cc8dd1a3c5e3d9d034e85d96cf12869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 14 Jun 2026 19:34:14 +0200 Subject: [PATCH] Harden invite token-in-path against cache and index leaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the /c/ edge defence (no-store, noindex, no-referrer) to the /invite/ surface — the invite secret rides in the path, so no CDN may cache it and no crawler may index the URL. --- src/__tests__/proxy-invite-public.test.ts | 11 +++++++++++ src/proxy.ts | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/__tests__/proxy-invite-public.test.ts b/src/__tests__/proxy-invite-public.test.ts index 32ef0ed7b..feb7aee54 100644 --- a/src/__tests__/proxy-invite-public.test.ts +++ b/src/__tests__/proxy-invite-public.test.ts @@ -48,6 +48,17 @@ describe("proxy.ts /invite public-allowlist (v1.17.0)", () => { expect(res.headers.get("location")).toBeNull(); }); + it("hardens the token-in-path surface against cache / index / referrer leaks", () => { + // Mirror the `/c/` edge defence — the secret rides in the path, + // so no CDN may cache it and no crawler may index the URL. + const res = proxy(makeRequest(`/invite/${VALID_TOKEN}`)); + expect(res.headers.get("Cache-Control")).toBe( + "no-store, no-cache, must-revalidate", + ); + expect(res.headers.get("X-Robots-Tag")).toBe("noindex, nofollow"); + expect(res.headers.get("Referrer-Policy")).toBe("no-referrer"); + }); + it("still redirects unauthenticated requests for protected pages", () => { // Negative check — confirms the fixture would catch a real // regression. `/insights` is not public. diff --git a/src/proxy.ts b/src/proxy.ts index 754892f5b..c93c7a666 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -279,7 +279,12 @@ export function proxy(request: NextRequest) { // the header, and the token must never reach a search index. // - `Referrer-Policy: no-referrer` so the token-bearing URL is not // leaked in the `Referer` of any outbound navigation from the page. - if (pathname.startsWith("/c/")) { + // + // v1.17.0 — `/invite/` (iOS #16) carries an equally sensitive + // secret in the path, so it earns the same edge defence even though it is + // a pure server redirect: no shared proxy / CDN may cache it and no + // crawler may index the token-bearing URL. + if (pathname.startsWith("/c/") || pathname.startsWith("/invite/")) { response.headers.set( "Cache-Control", "no-store, no-cache, must-revalidate",