fix(security): sandbox voice panel HTML in iframe (CRITICAL XSS) - #818
Closed
AndriiPasternak31 wants to merge 1 commit into
Closed
fix(security): sandbox voice panel HTML in iframe (CRITICAL XSS)#818AndriiPasternak31 wants to merge 1 commit into
AndriiPasternak31 wants to merge 1 commit into
Conversation
Agent-controlled HTML from update_panel was DOMPurify-sanitized with ADD_TAGS:['script'], then _execScripts() cloned surviving <script> tags into live DOM nodes that executed in the parent origin. Since the agent's html argument is influenceable by any external input (chat, files, MCP tool results, webhooks, channel messages, agent-website visitors), this was a generic indirect-prompt-injection XSS sink in the admin UI with JWT-theft / cross-agent-pivot blast radius. Render the panel inside an <iframe sandbox="allow-scripts"> instead. Without allow-same-origin / allow-forms / allow-popups / allow-top- navigation / allow-modals, the iframe has an opaque origin: scripts run (Chart.js still works) but cannot read parent localStorage / cookies / JWT, submit forms, navigate the parent, or open popups. - Drop DOMPurify ADD_TAGS:['script'] and the _execScripts() shim - Drop the parent-side window.Chart global injection - Load Chart.js inside the iframe via Vite ?url asset import (relative path; chart.js 4 doesn't expose the UMD bundle via package exports) - s_open/s_close concat avoids the Vue SFC parser tripping on the inner <script> tag inside the srcdoc template
3 tasks
Contributor
Author
|
Closed by branch rename. Continuation: #824 (same commits, same branch under its new name). |
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
Agent-controlled HTML from
update_panelwas DOMPurify-sanitized withADD_TAGS:['script'], then_execScripts()cloned surviving<script>tags into live DOM nodes that executed in the parent origin. Since the agent'shtmlargument is influenceable by any external input (chat, files, MCP tool results, webhooks, channel messages, agent-website visitors), this was a generic indirect-prompt-injection XSS sink in the admin UI with JWT-theft / cross-agent-pivot blast radius.What changed
Render the panel inside an
<iframe sandbox="allow-scripts">instead. Withoutallow-same-origin/allow-forms/allow-popups/allow-top-navigation/allow-modals, the iframe has an opaque origin: scripts run (Chart.js still works) but cannot read parentlocalStorage/ cookies / JWT, submit forms, navigate the parent, or open popups.ADD_TAGS:['script']and the_execScripts()shimwindow.Chartglobal injection?urlasset import (relative path; chart.js 4 doesn't expose the UMD bundle via package exports)s_open/s_closeconcat avoids the Vue SFC parser tripping on the inner<script>tag inside the srcdoc templateWhy this shape
The previous DOMPurify-then-execScripts pattern conflates content sanitization with code execution: any element that survives sanitization gets cloned into a live script tag. Even tightening the DOMPurify rules wouldn't eliminate the sink — the agent-controlled
htmlis the input boundary, and the parent origin is the wrong place for it to evaluate.The sandbox iframe inverts this: the agent's script runs (preserving the panel's intended Chart.js functionality) but in an origin that has no access to anything that matters. The fix is structural, not a filter tweak.
Test plan
parent.localStorage(verified via test panel attempting to access)Out of scope
This PR was previously bundled into #798 (circuit-breaker fix) — split out for isolated security review.
🤖 Generated with Claude Code