Skip to content

Harden ad-spoof pod accounting (cap + unknown-length gating) + GQL body-read timeout - #240

Open
ryanbr wants to merge 1 commit into
masterfrom
fix/spoof-pod-cap
Open

Harden ad-spoof pod accounting (cap + unknown-length gating) + GQL body-read timeout#240
ryanbr wants to merge 1 commit into
masterfrom
fix/spoof-pod-cap

Conversation

@ryanbr

@ryanbr ryanbr commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Spoofing-code hardening mirroring upstream GosuDRM/TTV-AB — all in the shared notifyAdComplete lineage, where TTV-AB hit these in the field.

1. Pod accounting (TTV-AB v9.4.1 + v9.6.4 + v9.7.3)

Two related edge cases around X-TV-TWITCH-AD-POD-LENGTH, both producing internally-inconsistent ad-completion beacons (the cross-validation flag the payload is designed to avoid):

(a) Over-surface — cap. Twitch occasionally exposes more unique stitched-ad DATERANGEs in one poll than the pod declares. The pre-loop early-out only caught that across polls, so within one poll vaft kept spoofing past the pod (5/2 pod logs, beacons for more ads than the pod claims). A per-iteration break now stops at the declared length. (v9.4.1)

(b) Unknown length — gate. When Twitch omits the pod-length attribute, podLength falls back to the current poll's match count, making every size-vs-podLength comparison meaningless:

  • the early-out bailed after the first ad → later ads in the pod left unspoofed;
  • pod_complete could fire on multiple polls.

All three checks (pre-loop early-out, in-loop cap, pod_complete) are now gated on hasExplicitPodLength = !!podLenMatch. Unknown length → never early-out (spoof every surfaced ad), never fabricate pod_complete. (v9.6.4 + v9.7.3)

2. GQL relay abort timeout through body read (TTV-AB v9.6.1)

The main-thread fetch relay (worker's access tokens + spoof beacons) cleared its 5s AbortController timer at headers, leaving await response.text() unbounded on a mid-body hang. Worker-side relay timeout already bounded the impact (no stall — leaked promise), but the timer now clears after the body read so a mid-body abort rejects through the existing catch.

Scope / validation

  • Pod-accounting changes are opt-in spoof-on territory (spoofing default-off since v68.3.0). The relay-timeout is always-on but bounded.
  • vaft release pair here; mirrored to the testing pair (direct to master, v656). Spoofing is vaft-only → no video-swap-new change.
  • Worker-blob safe (pod logic uses loop locals + the new hasExplicitPodLength); relay change is main-thread.
  • npx acorn --ecma2022 clean on both; added lines byte-identical across the pair.
  • No version bump (accumulates under ## Unreleased).

🤖 Generated with Claude Code

ryanbr added a commit that referenced this pull request Jun 10, 2026
Mirror of PR #240 (release pair) into the testing pair:
- Per-iteration break caps spoofing at the declared pod length (mirrors
  TTV-AB v9.4.1) — prevents beacons for more ads than the pod claims and
  impossible "5/2 pod" logs when one poll surfaces more DATERANGEs than
  X-TV-TWITCH-AD-POD-LENGTH declares.
- GQL relay abort timer now clears after the response body read (mirrors
  TTV-AB v9.6.1) so a mid-body hang rejects instead of leaking.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spoofing-code hardening mirroring upstream TTV-AB (shared-lineage code):

1. Pod accounting (TTV-AB v9.4.1 + v9.6.4 + v9.7.3):
   - Cap: a per-iteration break stops spoofing at the declared pod length
     when Twitch over-surfaces DATERANGEs in one poll (no beacons past the
     pod, no "5/2 pod" logs).
   - hasExplicitPodLength gate: when Twitch OMITS X-TV-TWITCH-AD-POD-LENGTH,
     podLength is just the current poll's match count, so every
     size-vs-podLength check was meaningless — the early-out bailed after
     the first ad (later ads unspoofed) and pod_complete could fire on
     multiple polls. All three checks (pre-loop early-out, in-loop cap,
     pod_complete) now gate on hasExplicitPodLength: unknown length → never
     early-out, never fabricate pod_complete.

2. GQL relay timeout through body read (TTV-AB v9.6.1): the main-thread
   fetch relay cleared its 5s abort timer at headers, leaving
   response.text() unbounded on a mid-body hang. Timer now clears after
   the body read; abort mid-body rejects through the existing catch.

All in opt-in spoof-on territory (spoofing default-off) except the relay
timeout. Worker-blob safe; main-thread for the relay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ryanbr
ryanbr force-pushed the fix/spoof-pod-cap branch from 665369e to af3cd80 Compare June 11, 2026 10:31
ryanbr added a commit that referenced this pull request Jun 11, 2026
Mirror of PR #240 (release pair) into the testing pair. Adds
hasExplicitPodLength gating on top of v655's pod cap: when Twitch omits
X-TV-TWITCH-AD-POD-LENGTH, podLength is just the current poll's match
count, so the size-vs-podLength checks were meaningless — the early-out
bailed after the first ad (later ads unspoofed) and pod_complete could
fire on multiple polls. All three checks (pre-loop early-out, in-loop
cap, pod_complete) now gate on hasExplicitPodLength. Mirrors TTV-AB
v9.6.4 + v9.7.3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ryanbr ryanbr changed the title Cap ad-spoof pod accounting + cover GQL body read with abort timeout Harden ad-spoof pod accounting (cap + unknown-length gating) + GQL body-read timeout Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant