Skip to content

test(e2e): fix no-op security injection test via frames[] traversal#540

Open
ochafik wants to merge 1 commit intomainfrom
worktree-agent-a087874c
Open

test(e2e): fix no-op security injection test via frames[] traversal#540
ochafik wants to merge 1 commit intomainfrom
worktree-agent-a087874c

Conversation

@ochafik
Copy link
Contributor

@ochafik ochafik commented Mar 6, 2026

Summary

The cross-app message injection test at security.spec.ts:313 has been a no-op since it was added in 2411c71. This PR makes it actually exercise the event.source check in PostMessageTransport.

The bug

The old test tried to reach the inner app iframe via:

outerIframe.contentDocument?.querySelector('iframe')

But host runs on :8080 and the sandbox iframe on :8081, so contentDocument is null cross-origin. The ?. short-circuited to undefined, the early return at :341 fired, and postMessage never executed. The test passed vacuously.

Ironically, the test ~60 lines above it (added in the same commit) explicitly asserts contentDocument === null:
https://github.com/modelcontextprotocol/ext-apps/blob/2411c71a/tests/e2e/security.spec.ts#L247

Why frames[] works

Per HTML spec, window.frames[] is one of the few properties that remains accessible across origins — it's the mechanism by which cross-origin windows can postMessage each other at all. This is also the actual attack path from the threat model comment at :309: a malicious sibling app reaches window.parent.parent.frames[i].frames[0].postMessage(...). The message does get delivered. PostMessageTransport's event.source !== window.parent check (message-transport.ts:77) is the only defense, and this test now actually verifies it.

Sentinel against recurrence

page.evaluate() now returns "POSTED" / "UNREACHABLE" and we assert on it. If frame traversal ever breaks again (hierarchy change, etc.), the test fails at the sentinel instead of silently passing.

Other changes

  • Actually assert rejectionLogs.length > 0 (was captured but never checked — see old :359 comment "ideally we see rejection")
  • expect.poll() instead of waitForTimeout(500)
  • Reuse existing captureConsoleLogs helper
  • Drive-by: fixed misleading comment at :293 — it claimed "Outer frame should NOT have allow-same-origin" but the assertion checks allow-top-navigation. The outer frame does have allow-same-origin (implementation.ts:167); isolation comes from the separate port.

Verification

npx tsc --noEmit clean. Not run locally — Playwright can't run headless outside docker on my machine. CI green (19/19 checks).

The cross-app message injection test at security.spec.ts:313 has been a
no-op since it was added in 2411c71. It attempted to reach the inner app
iframe via:

  outerIframe.contentDocument?.querySelector('iframe')

but contentDocument is null across the 8080→8081 origin boundary (the test
at :247 in the same file explicitly asserts this). The ?. short-circuited,
postMessage never fired, and the test passed without exercising the
defense in message-transport.ts.

Fix:
- Use window.frames[0].frames[0] — cross-origin accessible per HTML spec,
  and the actual attack path a malicious sibling app would use
- Return a sentinel from page.evaluate() and assert it, so the test fails
  loudly if frame traversal ever breaks again
- Actually assert the rejection log (was previously captured but never
  checked)
- Use expect.poll() instead of waitForTimeout(500)
- Reuse captureConsoleLogs helper

Also fixed: comment at :293 claimed 'Outer frame should NOT have
allow-same-origin' but the assertion checks allow-top-navigation. The
outer frame DOES have allow-same-origin (implementation.ts:167);
isolation comes from the separate port, not the sandbox attribute.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 6, 2026

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/@modelcontextprotocol/ext-apps@540

@modelcontextprotocol/server-basic-preact

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-preact@540

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-react@540

@modelcontextprotocol/server-basic-solid

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-solid@540

@modelcontextprotocol/server-basic-svelte

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-svelte@540

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vanillajs@540

@modelcontextprotocol/server-basic-vue

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vue@540

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/@modelcontextprotocol/server-budget-allocator@540

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/@modelcontextprotocol/server-cohort-heatmap@540

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/@modelcontextprotocol/server-customer-segmentation@540

@modelcontextprotocol/server-debug

npm i https://pkg.pr.new/@modelcontextprotocol/server-debug@540

@modelcontextprotocol/server-map

npm i https://pkg.pr.new/@modelcontextprotocol/server-map@540

@modelcontextprotocol/server-pdf

npm i https://pkg.pr.new/@modelcontextprotocol/server-pdf@540

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/@modelcontextprotocol/server-scenario-modeler@540

@modelcontextprotocol/server-shadertoy

npm i https://pkg.pr.new/@modelcontextprotocol/server-shadertoy@540

@modelcontextprotocol/server-sheet-music

npm i https://pkg.pr.new/@modelcontextprotocol/server-sheet-music@540

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/@modelcontextprotocol/server-system-monitor@540

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/@modelcontextprotocol/server-threejs@540

@modelcontextprotocol/server-transcript

npm i https://pkg.pr.new/@modelcontextprotocol/server-transcript@540

@modelcontextprotocol/server-video-resource

npm i https://pkg.pr.new/@modelcontextprotocol/server-video-resource@540

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/@modelcontextprotocol/server-wiki-explorer@540

commit: 74c4784

@ochafik ochafik marked this pull request as ready for review March 6, 2026 19:30
@ochafik ochafik requested a review from jonathanhefner March 6, 2026 19:31
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