fix(renderer): close 3 Codex security findings from PR #4 (media gate)#5
Merged
Merged
Conversation
Codex flagged on PR #4 that the pre-srcdoc string-layer gate (gateOneMediaTag) reads only a `src` attribute, so a responsive <picture><source srcset="https://169.254.169.254/x.png 2x"> re-emits the blocked candidate unchanged. A real browser prefetches srcset candidates during srcdoc parse, before the post-parse DOM pass can sanitize, so this was a live SSRF / fetch-policy bypass. Gate <source srcset> with the same per-candidate vocabulary the <img> path already uses (srcsetHasBlockedCandidate): if any candidate is blocked and no allowed src remains, fall back to the dimensioned placeholder; otherwise strip only the offending srcset so the allowed src can still fetch. <video>/<audio> carry no srcset, so this is scoped to <source>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex flagged on PR #4 that the string-layer opener scanner enumerated only <img>/<video>/<source>, so an <audio src="..."> was never gated before srcdoc parse. A real browser starts the media GET during parse (a private-origin SSRF, or full media bytes in off/poster mode) before the post-parse gateFragmentMedia pass can strip it. Add <audio> to the opener scan; gateOneMediaTag already handles any tag name uniformly (audio src gates as 'media'; the video-only poster branch is naturally skipped). Generalize the container-orphan guard, which was hardcoded to <video>, so a neutralized <audio> also consumes its matching </audio> close tag and swallowed children rather than leaving an orphan in the stream. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex flagged on PR #4 that the diff ATTR gate always gated a `src` mutation as kind 'image'. The per-viewer gate closure binds mediaMode, so a poster-mode `video.src = <public-https>` (or `source.src`) mutation passed as 'image' never tripped the poster-mode-media deny and was written to the live mirror, fetching playable media bytes that poster mode exists to withhold. Pick the gate kind from the live element's tag (reusing the existing lowercased targetTag): <video>/<audio> src -> 'media', <source> src -> 'source', any other src -> 'image'; a poster attr stays 'poster'. This mirrors gateOneMediaTag and gateFragmentMedia, so a poster-mode media src mutation is now denied and dropped, while the <img> path is intact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
LakshmanTurlapati
added a commit
that referenced
this pull request
Jun 22, 2026
Patch release over v0.2.0 carrying the three renderer media-gate fixes from PR #5 (Codex P1/P2 findings, all already on main): - gate <source srcset> at the string layer (c8470a2) - scan <audio> in the pre-parse media gate (54a7a95) - poster-mode-aware src mutation gate (3789dd3) No API changes, backward compatible. Bumps package.json and package-lock.json to 0.2.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This follows up PR #4 (v0.2.0) by closing the three security findings Codex raised on that release. All three live in the renderer media gate and are real SSRF or fetch policy bypasses. Each fix reuses the gate's existing vocabulary, so the change stays surgical.
The viewer renders attacker influenced HTML into a sandboxed iframe via
iframe.srcdoc. A string layer gate runs on the raw HTML beforesrcdocis assigned, because a real browser prefetches media URLs during parse, before the post parse DOM sanitization can run. Each finding was a hole in that gate or its diff twin.Findings closed
P1, gate
<source srcset>at the string layer.gateOneMediaTagread only asrcattribute, so a responsive<picture><source srcset="https://169.254.169.254/x.png 2x">re-emitted the blocked candidate unchanged and the parser could prefetch it. The fix gatessrcsetper candidate with the same helper the<img>path already uses, then strips the offending srcset or falls back to the placeholder when no allowed src remains.P1, scan
<audio>in the pre parse media gate. The opener scanner enumerated only<img>,<video>, and<source>, so an<audio src>was never string gated and the browser could start the media GET during parse. The fix adds<audio>to the scan and generalizes the container orphan guard, which was hardcoded to<video>, so a neutralized<audio>also consumes its matching close tag and swallowed children.P2, preserve poster mode for media
srcmutations. The diff ATTR gate always gated asrcmutation as an image, so a poster modevideo.srcmutation slipped through and fetched playable bytes that poster mode exists to withhold. The fix picks the gate kind from the live element tag, mirroring the snapshot and fragment paths, so poster mode now withholds the playable source on a mutation too.Tests
Eight new tests across
tests/renderer-media.test.js(string layer) andtests/renderer-asset-gate.test.js(diff seam), covering each finding plus a reference mode counterpoint and an image path regression guard. The full suite is green at 712 of 712 (was 704 of 704), the differential oracle stays at 48 of 48, andnpm run typesis clean.Out of scope
The six older Codex findings from PRs #1 and #2 are already fixed in current code, verified during this pass. The npm publish trusted publisher configuration is owner side and unchanged; these fixes can ride a future 0.2.1 patch once publishing is unblocked.
Closes the three Codex review threads on PR #4.
Co-Authored-By: Claude Opus 4.8 (1M context)