test(e2e): fix no-op security injection test via frames[] traversal#540
Open
test(e2e): fix no-op security injection test via frames[] traversal#540
Conversation
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.
@modelcontextprotocol/ext-apps
@modelcontextprotocol/server-basic-preact
@modelcontextprotocol/server-basic-react
@modelcontextprotocol/server-basic-solid
@modelcontextprotocol/server-basic-svelte
@modelcontextprotocol/server-basic-vanillajs
@modelcontextprotocol/server-basic-vue
@modelcontextprotocol/server-budget-allocator
@modelcontextprotocol/server-cohort-heatmap
@modelcontextprotocol/server-customer-segmentation
@modelcontextprotocol/server-debug
@modelcontextprotocol/server-map
@modelcontextprotocol/server-pdf
@modelcontextprotocol/server-scenario-modeler
@modelcontextprotocol/server-shadertoy
@modelcontextprotocol/server-sheet-music
@modelcontextprotocol/server-system-monitor
@modelcontextprotocol/server-threejs
@modelcontextprotocol/server-transcript
@modelcontextprotocol/server-video-resource
@modelcontextprotocol/server-wiki-explorer
commit: |
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
The cross-app message injection test at
security.spec.ts:313has been a no-op since it was added in 2411c71. This PR makes it actually exercise theevent.sourcecheck inPostMessageTransport.The bug
The old test tried to reach the inner app iframe via:
But host runs on :8080 and the sandbox iframe on :8081, so
contentDocumentisnullcross-origin. The?.short-circuited toundefined, the earlyreturnat :341 fired, andpostMessagenever 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[]worksPer HTML spec,
window.frames[]is one of the few properties that remains accessible across origins — it's the mechanism by which cross-origin windows canpostMessageeach other at all. This is also the actual attack path from the threat model comment at :309: a malicious sibling app reacheswindow.parent.parent.frames[i].frames[0].postMessage(...). The message does get delivered.PostMessageTransport'sevent.source !== window.parentcheck (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
rejectionLogs.length > 0(was captured but never checked — see old :359 comment "ideally we see rejection")expect.poll()instead ofwaitForTimeout(500)captureConsoleLogshelperallow-top-navigation. The outer frame does haveallow-same-origin(implementation.ts:167); isolation comes from the separate port.Verification
npx tsc --noEmitclean. Not run locally — Playwright can't run headless outside docker on my machine. CI green (19/19 checks).