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 #340Guide_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)
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)
`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>
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; '→' 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.
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; `'`→`'` 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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #376.
Problem
GetChannelGuideHandlerinterpolatedrequest.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.Escapewhere 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_tokendrives the real handler with a token containing all four XML-special chars and asserts the output is escaped (sibling to the #340Guide_xml_escapes_advertised_base_urltest). Verified non-vacuous: it fails with the escape reverted, passes with it. FullErsatzTV.Core.Testsgreen (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
🤖 Generated with Claude Code
Cold-context adversarial review (independent, review-only) of the head commit:
SecurityElement.Escapeescapes 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.accessTokenUrifeeds bothchannelsFragmentand everychannelDataFragmentvia the shared{AccessTokenUri}placeholder, so all XMLTV cache-fragment consumers are covered.ChannelPlaylist.ToM3Uis M3U (not XML) — no-change claim holds.SecurityElement.Escapeoutput char-for-char; negative assertion doesn't collide with the unrelated bare&text=Moviesgolden quirk.'→'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 M3Uurl-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