From dd946d17fc520a7d355c7f58bc2e4b43796316c8 Mon Sep 17 00:00:00 2001 From: Mason Hall Date: Fri, 2 Oct 2026 12:27:37 -0400 Subject: [PATCH] Preserve Origin on Link connection form posts --- app/(authenticated)/link/page.tsx | 4 +++- tests/integration/link.test.ts | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/(authenticated)/link/page.tsx b/app/(authenticated)/link/page.tsx index 5f334c7f..fc877767 100644 --- a/app/(authenticated)/link/page.tsx +++ b/app/(authenticated)/link/page.tsx @@ -6,7 +6,9 @@ import { applicationOrigin } from "@shared/environment/origin"; export const metadata = { title: "Link wallet", - referrer: "no-referrer" as const, + // Native form POSTs need their Origin header for the route's CSRF check. + // Cross-origin navigations still receive no referrer. + referrer: "same-origin" as const, }; export default async function Page({ searchParams }: PageProps<"/link">) { diff --git a/tests/integration/link.test.ts b/tests/integration/link.test.ts index 0f9d80f1..06be54cc 100644 --- a/tests/integration/link.test.ts +++ b/tests/integration/link.test.ts @@ -121,9 +121,16 @@ describe("Link wallet integration", () => { const denied = request(); denied.headers.set("origin", "https://attacker.example"); expect((await POST(denied)).status).toBe(403); + const opaqueOrigin = request(); + opaqueOrigin.headers.set("origin", "null"); + expect((await POST(opaqueOrigin)).status).toBe(403); + const missingOrigin = request(); + missingOrigin.headers.delete("origin"); + expect((await POST(missingOrigin)).status).toBe(403); const started = await POST(request()); expect(started.status).toBe(303); + expect(started.headers.get("referrer-policy")).toBe("no-referrer"); const destination = new URL(started.headers.get("location") ?? ""); expect(destination.origin).toBe("https://login.link.com"); expect(destination.searchParams.get("redirect_uri")).toBe(