fix(security): anti-replay, 0600 client key, open redirect, supply-chain

Address findings from a repo security review:

- core: add a sliding-window anti-replay filter over the AEAD-authenticated
  sequence in Session (poll_input/poll_frame), closing the input-replay gap the
  data plane previously left to the LAN/VPN trust assumption. 4096-deep window,
  unit-tested; the encrypted loopback suite confirms no false drops.
- clients: write the mTLS client private key 0600 and lock the config dir 0700
  on Unix (it was world-readable at the umask default), re-locking existing
  stores on load. pf-client-core::trust plus the probe's own identity writer.
  Windows keeps the %APPDATA% ACL; Android/Apple already wrap the key.
- web: fix a post-login open redirect — resolve `next` via URL and require it to
  stay same-origin, rejecting `/\evil.com` and tab/encoding variants the old
  `!startsWith("//")` guard missed. Also fixes the dead safeNextPath helper.
- ci: SHA-256-pin the BtbN FFmpeg DLLs bundled into the signed Windows installer
  (were fetched from the rolling `latest` tag unverified); fails closed on a
  re-roll, matching the VB-CABLE gate.
- ci: fail-open fork-guard on the Windows/Apple host-mode PR build jobs that
  share runner labels with the signing jobs. Definitive fix stays server-side
  (Gitea outside-collaborator approval / isolated PR runners) — see the notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 10:04:23 +02:00
parent ef39050dbc
commit e707a962b6
9 changed files with 338 additions and 18 deletions
+14 -3
View File
@@ -21,9 +21,20 @@ export const SectionLogin: FC<{ next?: string }> = ({ next }) => {
setBusy(false);
return;
}
// Full reload to the target so SSR re-runs WITH the new session cookie. Only a
// same-origin path — reject protocol-relative/absolute URLs (open-redirect guard).
const safe = next?.startsWith("/") && !next.startsWith("//") ? next : "/";
// Full reload to the target so SSR re-runs WITH the new session cookie. Resolve `next`
// against our own origin and accept it ONLY if it stays same-origin, rejecting absolute
// and protocol-relative targets. A naive `!startsWith("//")` check misses `/\evil.com`
// (browsers fold `\`→`/` under http(s), so it resolves to //evil.com) plus tab/newline
// tricks; parsing closes them because it's the same parser this redirect will use.
let safe = "/";
try {
const u = new URL(next ?? "/", window.location.origin);
if (u.origin === window.location.origin) {
safe = u.pathname + u.search + u.hash;
}
} catch {
safe = "/";
}
window.location.href = safe;
} catch {
setError(true);