From f7c9a28c33f1a1e55ef97a2366180267de0aebea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 5 Sep 2026 21:23:48 +0100 Subject: [PATCH] a login through the proxy lost its csrf cookie, and looped `stripHopByHop` copies headers with `out.set(key, value)`, which overwrites. Correct for every header except the one that legitimately repeats: a NextAuth sign-in sends several Set-Cookie - csrf-token, callback-url, session-token - and collapsing them to one drops the rest. Measured: two Set-Cookie headers direct from Linkwarden, one through the door, and the survivor was callback-url. So the csrf cookie never reached the browser, the sign-in posted, failed its CSRF check and bounced back to the form. A redirect loop with nothing in any log saying why - and the owner predicted this exact symptom from the other side before it was found. `getSetCookie()` returns them unjoined, which is the reason to use it rather than splitting a combined value: `Expires` contains a comma, so naive splitting corrupts the date and the cookie is discarded. Both hops use this one function, so both are fixed by it. --- manifest.ts | 2 +- sidecar/upstream.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/manifest.ts b/manifest.ts index 2671082..6c926fc 100644 --- a/manifest.ts +++ b/manifest.ts @@ -32,7 +32,7 @@ import type { PluginManifest } from '@@/plugins/manifest'; */ export const manifest: PluginManifest = { publisher: 'officerdev', - version: '2026.09.05.04', + version: '2026.09.05.05', platform: '>=1.0.0', label: 'Linkwarden', diff --git a/sidecar/upstream.ts b/sidecar/upstream.ts index e26d597..98e9e86 100644 --- a/sidecar/upstream.ts +++ b/sidecar/upstream.ts @@ -99,6 +99,25 @@ export function stripResponseHeaders(src: Headers): Headers { const out = stripHopByHop(src); out.delete('content-encoding'); out.delete('content-length'); + + /* + ── `Set-Cookie` is the one header that legitimately repeats, and `.set()` keeps only the last ── + + `stripHopByHop` copies with `out.set(key, value)`, which OVERWRITES. Everything else is fine that + way; Set-Cookie is not. A NextAuth sign-in sends several — csrf-token, callback-url, session-token — + and collapsing them to one silently drops the rest. + + Measured on this install: two headers direct from Linkwarden, one through the proxy, and the one + that survived was `callback-url`. The csrf cookie never reached the browser, which is a sign-in that + posts, fails its CSRF check, and bounces back to the form — the redirect loop, with nothing in any + log to say why. + + `getSetCookie()` is the standard accessor for exactly this case: it returns the values unjoined, + which matters because a cookie's `Expires` contains a comma and naive comma-splitting corrupts it. + */ + out.delete('set-cookie'); + for (const cookie of src.getSetCookie()) out.append('set-cookie', cookie); + return out; }