# API auth & security posture (#197, #206, #279, #283, #292, #295, #301, #319, #330) Why ErsatzTV's REST/SPA surface is gated the way it is: the #197 cold-review remediation and its bundles, the Blazor-removal auth sign-off, the artwork stored-XSS fix, fail-closed API auth, browser-session auth + CSRF, the side-effecting-GET POST-ification, and the response security headers (CSP/Permissions-Policy/CORP). Rationale relocated from the append-only `docs/decisions.md` at the v26.9.0 consolidation; enforcement/mechanics cross-link to `docs/api-conventions.md` §5/§9 and `docs/spa-conventions.md` §5e. Issue trail: #197 (Phase-0 headers/PR #279; Bundle A fail-closed/PR #292; Bundle C contract-freeze), #206 (Blazor-removal auth posture), #283 (artwork content-type), #295 (PR1 server session auth + PR2 SPA cutover), #301 (side-effecting GETs), #319 (CSP), #330 (CORP). ## Contents --- ## Records formerly in this file Each record below moved to its own file under `records/` (ersatztv#610); the rationale is unchanged. Resolve by **key** — that is the stable identity. A date-based pointer from another doc or an old issue comment should land here and then follow the link. - 2026-07-11 — Baseline security response headers + Phase-0 API hardening (#197, PR #279) — [`security.baseline-response-headers`](records/security/baseline-response-headers.md) - 2026-07-11 — Blazor removal auth posture: no new exposure beyond phase (a); real auth deferred to #197 (#206) — [`security.blazor-removal-auth-posture`](records/security/blazor-removal-auth-posture.md) - 2026-07-12 (#197 Bundle C — contract-freeze honesty) — [`security.contract-freeze-honesty`](records/security/contract-freeze-honesty.md) - 2026-07-12 — #295 PR2: SPA session cutover + #301 side-effecting-GET POST-ification — [`security.session-cutover-postify`](records/security/session-cutover-postify.md) - 2026-07-12 — Artwork content-type is sniffed, never reflected (#283, S4/S9 stored XSS) — [`security.artwork-content-type-sniff`](records/security/artwork-content-type-sniff.md) - 2026-07-12 — Browser SPA session auth: `/api` accepts session OR machine key (#295 PR1, server-only) — [`security.session-auth-dual-credential`](records/security/session-auth-dual-credential.md) - 2026-07-12 — Enforcing CSP + Permissions-Policy on the host (#319, ZAP baseline) — [`security.csp-permissions-policy`](records/security/csp-permissions-policy.md) - 2026-07-12 — Fail-closed API auth + sensitive-read tier + CORS/ForwardedHeaders lockdown (#197 Bundle A, PR #292) — [`security.fail-closed-api-auth`](records/security/fail-closed-api-auth.md) - 2026-07-13 — Cross-origin resource policy: `same-origin` on every response (#330) — [`security.corp-same-origin`](records/security/corp-same-origin.md) - 2026-07-22 — Short-lived browser IPTV token so the SPA reaches `/iptv/*` under JWT auth (#552) — [`security.iptv-browser-token`](records/security/iptv-browser-token.md) - 2026-07-23 — access_token transport hardening: percent-encode in M3U/HLS, redact from logs, no-store on tokened manifests (#421, #559) — [`security.iptv-access-token-transport`](records/security/iptv-access-token-transport.md)