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(