Commit Graph
5 Commits
Author SHA1 Message Date
timothyandClaude Opus 4.8 a372742eb0 harden(421,559): percent-encode access_token in IPTV URLs, redact from logs, no-store on tokened manifests
PR Gates / decisions lifecycle (pull_request) Failing after 12s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 21s
PR Gates / Docs update reminder (pull_request) Successful in 21s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m24s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 22s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 19s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 5m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 15m51s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
#421 — a token containing a double-quote/ampersand could break out of the quoted M3U
`url-tvg="..."`/`tvg-logo="..."` attributes or split the query. Percent-encode
(`Uri.EscapeDataString`) at every site the token is interpolated into an M3U/HLS/XMLTV
URL: `ChannelPlaylist.ToM3U`, `IptvController.AccessTokenQuery`,
`FFmpegLibraryProcessService.WrapSegmenter`, and the XMLTV twin
`GetChannelGuideHandler` (percent-encode then XML-escape). No-op for a normal base64url
JWT, so the M3U/XMLTV goldens are byte-identical.

#559 — (1) Serilog logged the full `?access_token=<jwt>` query on any 5xx/Error `/iptv`
request. `IncludeQueryInRequestPath = false` + a new `RequestLogScrubber` enriching
`RequestPathScrubbed` (masks the token to `***`, keeps other params) that the message
template now references. (2) Token-bearing dynamic manifests (`channels.m3u`,
`xmltv.xml`, the `.m3u8` HLS manifests, and their token-bearing 302 Locations) now return
`Cache-Control: private, no-store`.

Independent cold review (2 rounds) clean. Live-E2E verified: no-store header on
m3u/xmltv/m3u8, token percent-encoded in url-tvg, and the request log rendering
`access_token=***` with the raw token absent. Decision record
`security.iptv-access-token-transport` added; resolves the deferred-hardening note under
`security.iptv-browser-token`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 08:30:18 +02:00
timothyandClaude Opus 4.8 60a0c50578 fix(552): fold #552 security-review findings
Cold review (no Critical/High). Folded:
- Low: clamp JWT:BrowserTokenLifetimeMinutes to a 24h max so a seconds-vs-minutes
  typo can't mint a multi-year bearer token (non-positive/unparseable still falls
  back to 60 min).
- Low: reset the SPA iptv-token cache on the preview panel's Retry and on each
  troubleshooting Play, so a stale token (key rotated) or a stale "JWT disabled"
  latch (backend reconfigured since page load) can't wedge a user-initiated retry.

Deferred to #559 (tracked): redact access_token from Serilog request logs and set
no-store on token-bearing /iptv manifests — pre-existing properties of the shared
?access_token= transport (Jellyfin/M3U already use it), now bounded by the 60-min
lifetime; cross-cutting fixes beyond this feature's scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 17:34:01 +02:00
timothyandClaude Opus 4.8 f8ae4d62ab fix(552): mint a short-lived JWT so the SPA reaches /iptv/* under JWT auth
Under a JWT-enabled deployment (JWT:IssuerSigningKey set), /iptv/* is gated by
ConditionalIptvAuthorizeFilter and the "jwt" scheme does not accept the SPA's
ctv-session cookie, and nothing minted a JWT for the browser. So the #60 channel
preview was declared Unavailable and could not run at all.

Add GET /api/v1/auth/iptv-token (session-gated, on the [IgnoreApi] AuthController):
mints a short-lived global token via JwtHelper.GenerateBrowserToken (60 min default,
JWT:BrowserTokenLifetimeMinutes override), 204 when JWT is disabled. The SPA's new
withIptvToken(url) helper appends it as ?access_token= to the manifest URL (a no-op
when JWT is off), used by the channel-preview panel and the troubleshooting screen.
Mapper.GetPreview drops its iptvJwtEnabled -> Unavailable guard; preview is now
JWT-agnostic.

Live-E2E under JWT: /iptv manifest 401s without a token and passes with a valid one
(garbage token -> 401); token endpoint 401s anonymous, mints with a session.

Honest finding: the issue's point 2 (troubleshooting screen broken under JWT) does
not reproduce -- its live.m3u8 is static-served (UseStaticFiles at /iptv/session),
outside the JWT filter, so it was never gated. The withIptvToken call there is a
harmless defensive no-op.

Docs: security.iptv-browser-token (api-auth-security.md), amended
api.channel-preview-capability, spa-conventions §5b. No OpenAPI change (IgnoreApi +
unchanged Preview schema).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 17:21:05 +02:00
timothyandClaude Opus 4.8 63a6fe80fb feat(521): migrate topic-file decision records to lifecycle schema [decisions-edit]
Adds metadata blocks (key/status/since/supersedes/superseded-by + Rule/Signals/Mechanics)
to all 26 legacy-unmigrated ## records across optimistic-concurrency.md (7, concurrency.*),
api-auth-security.md (9, security.*), release-ci-governance.md (7 remaining legacy records,
release.*), and spa-modularization.md (3, spa.*). Heading text and rationale prose are
byte-identical; only metadata was inserted, per the key-table assignments.

security.session-auth-dual-credential (#295 PR1) gets a one-line cross-reference noting it
narrows the OIDC-inert sub-claim of security.blazor-removal-auth-posture (#206), per
orchestrator adjudication — both stay status: active with supersedes/superseded-by: none.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 11:26:25 +02:00
timothyandClaude Opus 4.8 a2c056dd7a docs(release): prepare v26.9.0 promotion [decisions-edit]
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / Build & test (.NET) (push) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Has been cancelled
Consolidate docs/decisions.md (1923 -> 1028) by extracting four cohesive
topic clusters into docs/decisions/ (optimistic-concurrency,
api-auth-security, release-ci-governance, spa-modularization) — content
relocated verbatim (lossless; all rationale + reversals preserved), main
Index rebuilt to reference the topic files plus the remaining in-file
entries, docs/README.md points back at the decisions Index. Add the
v26.9.0 row to the ci-cd.md version table.

refs #340

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:17:23 +02:00