fix(376): XML-escape access_token value in XMLTV guide output #419

Merged
timothy merged 1 commits from fix/376-xmltv-token-escape into main 2026-07-17 19:34:25 +02:00
Owner

Closes #376.

Problem

GetChannelGuideHandler interpolated request.AccessToken (HTTP-request-derived, from ?access_token=) raw into the pre-built XMLTV cache fragments. A token containing &, <, >, or " would emit invalid XML and malform the whole guide. Pre-existing; surfaced by the #340 (PR #374) independent review as out-of-scope.

Fix

Escape the token with SecurityElement.Escape where it's appended to {AccessTokenUri}, consistent with how #340 escaped {RequestBase}. Opaque tokens are a no-op.

The M3U path (ChannelPlaylist.ToM3U) also interpolates the token, but M3U is not XML — escaping there is neither needed nor correct — left unchanged (confirmed per the issue).

Test

Guide_xml_escapes_access_token drives the real handler with a token containing all four XML-special chars and asserts the output is escaped (sibling to the #340 Guide_xml_escapes_advertised_base_url test). Verified non-vacuous: it fails with the escape reverted, passes with it. Full ErsatzTV.Core.Tests green (549).

Scope / review

Read-path only (XMLTV guide output); no write-path/lock/auth/migration, no API-endpoint or SPA change (no OpenAPI/docs regen needed), ~1 line + 1 test. A cold-context adversarial review pass follows.

Done-when

  • Access token value is XML-safe in the XMLTV guide output (escaped)
  • Regression test with an XML-special-char token
  • adversarial review passed

🤖 Generated with Claude Code

Closes #376. ## Problem `GetChannelGuideHandler` interpolated `request.AccessToken` (HTTP-request-derived, from `?access_token=`) **raw** into the pre-built XMLTV cache fragments. A token containing `&`, `<`, `>`, or `"` would emit invalid XML and malform the whole guide. Pre-existing; surfaced by the #340 (PR #374) independent review as out-of-scope. ## Fix Escape the token with `SecurityElement.Escape` where it's appended to `{AccessTokenUri}`, consistent with how #340 escaped `{RequestBase}`. Opaque tokens are a no-op. The M3U path (`ChannelPlaylist.ToM3U`) also interpolates the token, but M3U is not XML — escaping there is neither needed nor correct — **left unchanged** (confirmed per the issue). ## Test `Guide_xml_escapes_access_token` drives the real handler with a token containing all four XML-special chars and asserts the output is escaped (sibling to the #340 `Guide_xml_escapes_advertised_base_url` test). Verified **non-vacuous**: it fails with the escape reverted, passes with it. Full `ErsatzTV.Core.Tests` green (549). ## Scope / review Read-path only (XMLTV guide output); no write-path/lock/auth/migration, no API-endpoint or SPA change (no OpenAPI/docs regen needed), ~1 line + 1 test. A cold-context adversarial review pass follows. ## Done-when - [x] Access token value is XML-safe in the XMLTV guide output (escaped) - [x] Regression test with an XML-special-char token - [ ] adversarial review passed 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 1 commit 2026-07-17 19:05:05 +02:00
fix(376): XML-escape access_token value in XMLTV guide output
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 5s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 5s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 5s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m2s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 47s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 14m14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 18m31s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
bf15677dc6
`GetChannelGuideHandler` interpolated `request.AccessToken` (HTTP-request-
derived, from `?access_token=`) raw into the pre-built XMLTV cache fragments.
A token containing `&`, `<`, `>`, or `"` would emit invalid XML and malform
the entire guide. Escape it with `SecurityElement.Escape`, consistent with
how #340 escaped `{RequestBase}`.

The M3U path (`ChannelPlaylist.ToM3U`) also interpolates the token but M3U is
not XML, so escaping there is neither needed nor correct — left unchanged.

Regression test `Guide_xml_escapes_access_token` drives the real handler with
a token containing all four XML-special chars and asserts the output is
escaped (sibling to the #340 `Guide_xml_escapes_advertised_base_url` test).
Verified non-vacuous: it fails with the escape reverted.

fixes #376

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Cold-context adversarial review (independent, review-only) of the head commit:

  • Escaping correctness: SecurityElement.Escape escapes a superset of what a double-quoted XML attribute requires (&,<," mandatory; >,' harmless), applied exactly once — no missed char, no double-escape. Mirrors the pre-existing {RequestBase} escaping one line above.
  • Coverage: the single escaped accessTokenUri feeds both channelsFragment and every channelDataFragment via the shared {AccessTokenUri} placeholder, so all XMLTV cache-fragment consumers are covered. ChannelPlaylist.ToM3U is M3U (not XML) — no-change claim holds.
  • Test: verified non-vacuous (fails with the escape reverted). Positive assertion matches SecurityElement.Escape output char-for-char; negative assertion doesn't collide with the unrelated bare &text=Movies golden quirk.
  • Edge cases: empty/whitespace short-circuited; '→&apos; valid in XML; no URL/XML-encoding confusion introduced.

One minor, non-blocking, out-of-scope observation: a token containing a literal " could break the M3U url-tvg="..." quoted attribute for strict parsers — distinct M3U-quoting concern, not the XML malformation #376 fixes. Filed as a follow-up.

Review-verdict: MERGEABLE @ bf15677dc6

Cold-context adversarial review (independent, review-only) of the head commit: - Escaping correctness: `SecurityElement.Escape` escapes a superset of what a double-quoted XML attribute requires (`&`,`<`,`"` mandatory; `>`,`'` harmless), applied exactly once — no missed char, no double-escape. Mirrors the pre-existing `{RequestBase}` escaping one line above. - Coverage: the single escaped `accessTokenUri` feeds both `channelsFragment` and every `channelDataFragment` via the shared `{AccessTokenUri}` placeholder, so all XMLTV cache-fragment consumers are covered. `ChannelPlaylist.ToM3U` is M3U (not XML) — no-change claim holds. - Test: verified non-vacuous (fails with the escape reverted). Positive assertion matches `SecurityElement.Escape` output char-for-char; negative assertion doesn't collide with the unrelated bare `&text=Movies` golden quirk. - Edge cases: empty/whitespace short-circuited; `'`→`&apos;` valid in XML; no URL/XML-encoding confusion introduced. One minor, non-blocking, out-of-scope observation: a token containing a literal `"` could break the M3U `url-tvg="..."` quoted attribute for strict parsers — distinct M3U-quoting concern, not the XML malformation #376 fixes. Filed as a follow-up. Review-verdict: MERGEABLE @ bf15677dc638a79be3250472d0c2c738c09462b3
timothy merged commit ffa8e5a000 into main 2026-07-17 19:34:25 +02:00
timothy deleted branch fix/376-xmltv-token-escape 2026-07-17 19:34:25 +02:00
Sign in to join this conversation.