From 5d6b1db997208d0d60802d057a15d8bac7deac59 Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Sun, 16 Aug 2026 08:21:39 -0400 Subject: [PATCH] fix(security): set referrer policy --- .changeset/whole-needles-remain.md | 2 ++ server/app.ts | 7 +++++++ test/api.test.ts | 14 ++++++++++++++ test/workerIntegration.integration.ts | 1 + 4 files changed, 24 insertions(+) create mode 100644 .changeset/whole-needles-remain.md diff --git a/.changeset/whole-needles-remain.md b/.changeset/whole-needles-remain.md new file mode 100644 index 00000000..a845151c --- /dev/null +++ b/.changeset/whole-needles-remain.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/server/app.ts b/server/app.ts index 66c89b10..71e9415f 100644 --- a/server/app.ts +++ b/server/app.ts @@ -287,6 +287,13 @@ export function createApp({ maxHoldConnections = DEFAULT_MAX_HOLD_CONNECTIONS, }: AppOptions) { const app = new Hono(); + // `?key=` bootstraps cookie auth, so never let a board URL disclose that + // credential to another origin through an outbound Referer header. Set this + // before auth so denied and public routes carry the same policy. + app.use("*", (c, next) => { + c.header("Referrer-Policy", "no-referrer"); + return next(); + }); const bus = new EventBus(); if (onEvent) { bus.subscribe((event) => { diff --git a/test/api.test.ts b/test/api.test.ts index a843aaaf..db1ac206 100644 --- a/test/api.test.ts +++ b/test/api.test.ts @@ -1171,6 +1171,20 @@ test("auth token guards mutating routes when configured", async () => { assert.equal(viaCookie.status, 200); }); +test("Referrer-Policy protects public, denied, and authenticated board responses", async () => { + const app = makeApp("secret"); + const responses = await Promise.all([ + app.request("/guide"), + app.request("/"), + app.request("/?key=secret"), + app.request("/api/sessions"), + ]); + + for (const response of responses) { + assert.equal(response.headers.get("referrer-policy"), "no-referrer"); + } +}); + async function readSseUntil(res: Response, needle: string, abort?: () => void): Promise { assert.ok(res.body); const reader = res.body.getReader(); diff --git a/test/workerIntegration.integration.ts b/test/workerIntegration.integration.ts index 3b16e6a9..43d7c89d 100644 --- a/test/workerIntegration.integration.ts +++ b/test/workerIntegration.integration.ts @@ -169,6 +169,7 @@ test( assert.equal(rendered.status, 200); assert.match(await rendered.text(), /worker-marker/); assert.equal(rendered.headers.get("content-security-policy"), "sandbox allow-scripts"); + assert.equal(rendered.headers.get("referrer-policy"), "no-referrer"); assert.equal(rendered.headers.get("x-content-type-options"), "nosniff"); assert.match(rendered.headers.get("cache-control") ?? "", /immutable/);