harden: gate host-affecting bridge messages + pin CSP isolation in tests - #75
Merged
Merged
Conversation
…on in tests The postMessage bridge honored switch-session and open-link from any frame, while resize/send-prompt were already gated to a recognized surface frame. Gate the former two on isOwnFrame(ev.source) so a stray or nested frame can't drive session navigation or pop an open-link dialog. switch-session is sent only by html frames, but open-link is also sent by rich-part frames (which are not in the html-only frameForSource registry), so isOwnFrame recognizes any iframe the viewer embedded. Also pin the load-bearing isolation guarantee directly, where it was only covered by the sandbox attribute as a proxy: - unit: the board origin is never a connect-src/script-src source (img/media only) — the exact exfil hole that 'self'/wildcard/`https:` checks miss. - e2e: script inside an html part is CSP-blocked from fetching the board API, asserted on real Chromium and WebKit via a self-reporting probe. 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.
Why
While stress-testing fresh
main(every surface kind, all three integration tiers, and a battery of iframe-escape attempts on real Chromium + WebKit), the isolation held up — but two gaps stood out:The core invariant was only tested by proxy. Every isolation e2e asserts the sandbox attribute (
sandbox="allow-scripts", noallow-same-origin). Nothing asserted the actual behavior — that a contained script can't reach the board API. A regression that put the board origin back intoconnect-src(or dropped the CSP meta tag) would keep the attribute intact and pass every test while silently opening exfil.Two bridge handlers were ungated.
resize/send-promptalready required the message to come from a recognized surface frame, butswitch-sessionandopen-linkhonored a message from any frame — so a stray or nested frame could drive session navigation or pop an "Open external link?" dialog.What changed
Bridge gating (
viewer/src/App.tsx):switch-sessionandopen-linknow requireisOwnFrame(ev.source)— a frame the viewer actually embedded.frameForSourceonly tracks html-part frames, butopen-linkis also sent by rich-part frames (markdown links route throughopenLinkbecausetarget=_blankcan't open underallow-scriptsalone), soisOwnFramerecognizes any embedded iframe rather than just the html-only registry.Tests pinning the guarantee directly:
test/surfacePage.test.ts): the board origin is never aconnect-src/script-srcsource — onlyimg-src/media-src, for asset embedding. This is the exact exfil hole the existing'self'/wildcard/https:checks miss (the origin is none of those).e2e/isolation.spec.ts): script inside an html part is CSP-blocked fromfetch('/api/surfaces'), asserted on real Chromium and WebKit via a self-reporting probe.No CSP change —
script-src(inline + CDN allowlist) is untouched; scripts in html parts run exactly as before. This only tightens the host-side bridge and adds coverage.Validation
npm test— 158 pass ·npm run typecheck·npm run lint·npm run format:checkall cleannpm run test:e2eisolation (2), viewer incl. send-prompt/comment (34), markdown + diff (4) — pass on both browsers🤖 Generated with Claude Code