From f720da49d6eda2ddeaaad9ccd98e9db6c7ee0c19 Mon Sep 17 00:00:00 2001 From: saopig1 <4x7sw862st@gmail.com> Date: Fri, 29 May 2026 21:25:20 +0800 Subject: [PATCH] fix(web): referrer-policy same-origin so same-origin POSTs keep a real Origin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit no-referrer downgraded the Origin header to the literal "null" on same-origin non-GET requests (per the WHATWG Fetch "Append a request Origin header" algorithm), which the /api/* csrfOriginCheck then rejected with 403 "bad origin" — silently breaking QR login, cookie save, and every other WebUI POST/PUT/DELETE/PATCH (playback, bot management, user admin). /api/session/* was unaffected because it mounts before the CSRF gate, which is why WebUI login still worked. same-origin keeps the real Origin on same-origin requests (CSRF passes) while still sending no Referer cross-origin, so Bē«™/NetEase/QQ CDN cover thumbnails keep loading. Adds referrer-policy.test.ts pinning the policy and a csrf.test.ts case for the Origin: "null" rejection. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/web/middleware/csrf.test.ts | 15 +++++++++++ src/web/referrer-policy.test.ts | 46 +++++++++++++++++++++++++++++++++ web/index.html | 16 +++++++++--- 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 src/web/referrer-policy.test.ts diff --git a/src/web/middleware/csrf.test.ts b/src/web/middleware/csrf.test.ts index d056ebd..24da36f 100644 --- a/src/web/middleware/csrf.test.ts +++ b/src/web/middleware/csrf.test.ts @@ -55,4 +55,19 @@ describe("csrfOriginCheck middleware", () => { .set("Referer", "https://evil.com/some/path"); expect(res.status).toBe(403); }); + + // Documents the server side of the QR-login outage: a `no-referrer` document + // policy makes the browser send the literal `Origin: null` on same-origin + // POSTs, which this guard cannot parse a host from and therefore rejects. + // The fix lives in the frontend (referrer policy -> same-origin); this test + // pins the gate behavior so the interaction stays understood. See + // src/web/referrer-policy.test.ts. + it('rejects POST with the literal Origin: "null" (no-referrer downgrade)', async () => { + const res = await request(app) + .post("/") + .set("Host", "example.com") + .set("Origin", "null"); + expect(res.status).toBe(403); + expect(res.body).toEqual({ error: "bad origin" }); + }); }); diff --git a/src/web/referrer-policy.test.ts b/src/web/referrer-policy.test.ts new file mode 100644 index 0000000..02ca30f --- /dev/null +++ b/src/web/referrer-policy.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +/** + * Regression guard for the QR-login / cookie-save outage (and in fact every + * mutating WebUI action). On 2026-05-27 the WebUI-auth feature added the + * same-origin CSRF gate `app.use("/api", csrfOriginCheck)` in server.ts, and + * the same day a `` was added to + * web/index.html so cross-origin CDN cover thumbnails would load. + * + * Those two changes conflict: per the WHATWG Fetch "Append a request Origin + * header" algorithm, the `no-referrer` policy sets the Origin header to the + * literal string "null" on same-origin non-GET requests. csrfOriginCheck then + * fails to parse a host (`new URL("null")` throws) and returns 403 "bad + * origin", so POST /api/auth/qrcode (and every other POST/PUT/DELETE under + * /api/* except /api/session/*) never reaches its handler. + * + * `same-origin` is the correct policy: it keeps the real Origin on same-origin + * requests (CSRF passes) while still sending no Referer cross-origin (CDN + * thumbnails keep loading). Never switch this back to `no-referrer`. + */ +describe("frontend referrer policy (CSRF / Origin-header regression)", () => { + const indexHtmlPath = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "../../web/index.html" + ); + const html = fs.readFileSync(indexHtmlPath, "utf-8"); + + const referrerMeta = html.match( + //i + ); + + it("declares a referrer policy meta tag", () => { + expect(referrerMeta).not.toBeNull(); + }); + + it("uses same-origin (NOT no-referrer, which sends Origin: null and 403s every POST)", () => { + expect(referrerMeta?.[1]).toBe("same-origin"); + }); + + it("does not contain no-referrer anywhere in the document head", () => { + expect(html).not.toMatch(/content=["']no-referrer["']/i); + }); +}); diff --git a/web/index.html b/web/index.html index bf7535c..5c0042c 100644 --- a/web/index.html +++ b/web/index.html @@ -3,10 +3,18 @@ - - + + TSMusicBot