Skip to content

fix: sanitize AI review HTML and verify iframe postMessage origin#3546

Open
TheChosenOne-Sunyuchen wants to merge 1 commit into
tscircuit:mainfrom
TheChosenOne-Sunyuchen:fix/sanitize-ai-review-and-verify-iframe-origin
Open

fix: sanitize AI review HTML and verify iframe postMessage origin#3546
TheChosenOne-Sunyuchen wants to merge 1 commit into
tscircuit:mainfrom
TheChosenOne-Sunyuchen:fix/sanitize-ai-review-and-verify-iframe-origin

Conversation

@TheChosenOne-Sunyuchen
Copy link
Copy Markdown

Fixes the two security issues reported in #3376. Thanks to @Noa-Lia for the report — I independently verified both findings against the current code and implemented the fixes below.

1. Stored/reflected XSS in ViewAiReviewView

lib/components/AiReviewDialog/ViewAiReviewView.tsx passed marked.parse() output directly into dangerouslySetInnerHTML. The markdown comes from the registry's ai_review_text response, and marked does not sanitize by default, so any HTML/script in that response executed in the user's DOM.

Fix: run the parsed HTML through DOMPurify.sanitize() before rendering. Safe markup (links, formatting) is preserved; <script> and event-handler attributes (onerror, etc.) are stripped.

2. postMessage data leak in RunFrameWithIframe

lib/components/RunFrameWithIframe/RunFrameWithIframe.tsx listened for message events with no origin/source check and replied with postMessage(..., "*"). Any page embedding the iframe could send { runframe_type: "runframe_ready_to_receive" } and receive runFrameProps back.

Fix: only respond when event.source is our own iframe's contentWindow, and target the iframe's resolved origin instead of "*". The embed contract documented in the README is preserved. Origin resolution is extracted into resolveIframeTargetOrigin() (handles absolute and relative iframeUrl, returns null on unparseable input) with unit tests.

Note: PreviewEmptyState.tsx also uses dangerouslySetInnerHTML, but its input comes from the static, self-escaping getRandomTipForUser() util, so it was intentionally left unchanged.

Verification

  • bunx tsc --noEmit — passes
  • bun run format:check (biome) — passes
  • bun test — 23 pass / 0 fail (added 5 tests for resolveIframeTargetOrigin)
  • Manually confirmed DOMPurify strips <script> / onerror payloads while keeping legitimate links.

Addresses the two security findings reported in tscircuit#3376.

1. ViewAiReviewView: marked.parse() output was passed straight to
   dangerouslySetInnerHTML. Since the markdown comes from the registry's
   ai_review_text response and marked does not sanitize by default, any
   HTML/script in that response executed in the user's DOM. The parsed
   HTML is now run through DOMPurify before rendering, which strips
   scripts and event-handler attributes while keeping safe markup
   (links, formatting).

2. RunFrameWithIframe: the message handler had no origin/source check and
   replied with postMessage(..., "*"), so any embedding page could send
   "runframe_ready_to_receive" and receive runFrameProps. The handler now
   only responds to messages whose source is our own iframe, and targets
   the iframe's resolved origin instead of "*". Origin resolution is
   extracted into resolveIframeTargetOrigin() with unit tests.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
runframe Ready Ready Preview, Comment May 31, 2026 1:49pm

Request Review

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