A ChatGPT Share Link Could Run Code Through MathLive. MathLive 0.111.2 Now Checks Origins
Book: Event-Driven Architecture Pocket Guide Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub Someone sen

Posted on Oct 31 • Edited on Oct 31 • AI-assisted
Parameter's writeup, published on October 28, says its team was able to take over anyone's account by sending them a link. OpenAI fixed it on October 26, eight days after the report, and no user action is needed. Parameter reports no real-world use, and its private-chat proof ran between separate accounts, not against real users.
The part that reaches past ChatGPT is the first link in the chain: a postMessage listener in MathLive, an open-source math input library, running with origin validation set to "none". That is the default the MathLive API reference documents. On October 31, MathLive 0.111.2 changed it.
Parameter describes the chain step by step:
With that, the researchers listed a victim's recent conversations, fetched a private conversation through ChatGPT's authenticated API, and sent part of it to a server they controlled. Their summary: "Essentially, we could perform any action on behalf of the victim's ChatGPT account." They demonstrated the conversation read, not the rest.
Each piece looked reasonable on its own: a public share, a preview, a math keyboard, a frame, a CSP allowlist.
From Parameter's timeline and MathLive's changelog:
The MathLive 0.111.2 entry doesn't mention ChatGPT, OpenAI or Parameter, and lists no CVE. I can't tell you whether the report prompted it. Parameter's writeup doesn't say it reported anything to MathLive's maintainers.
The release has one section, Security. Its first line: "Virtual keyboard, proxy and mathfield messages now default to same-origin validation." The rest of the entry closes the other doors the chain used:
If your app ships MathLive, find out which version you have, then upgrade to 0.111.2 or later:
npm ls mathlive npm install mathlive@latest Enter fullscreen mode Exit fullscreen mode The API reference still lists "none" as the default for originValidator, so set the policy explicitly instead of relying on either version's default. The type is ((origin) => boolean) | "same-origin" | "none":
import ' mathlive '; // Explicit policy: no reliance on a default. mathVirtualKeyboard. originValidator = ' same-origin '; Enter fullscreen mode Exit fullscreen mode If your keyboard really does talk across origins, pass a function that allows only the ones you trust:
const TRUSTED = new Set ([ ' https://app.example.com ' ]); mathVirtualKeyboard. originValidator = ( origin ) => TRUSTED. has ( origin ); Enter fullscreen mode Exit fullscreen mode Check the listeners you wrote yourself MathLive is one library. Any message listener on your page has the same exposure. MDN's postMessage page is blunt about it: "Failure to check the origin and possibly source properties enables cross-site scripting attacks." Start by finding them:
grep -rnE 'addEventListener\(.message|onmessage' src/ Enter fullscreen mode Exit fullscreen mode Parameter's lessons map onto a short checklist: check both event.origin and event.source before acting on a state-changing message, and never accept raw HTML across windows as configuration. A listener that does both looks like this:
const kbd = document. querySelector ( ' #keyboard-frame ' ); const TRUSTED = ' https://keyboard.example.com '; const LAYOUTS = new Set ([ ' numeric ', ' alphabetic ' ]); window. addEventListener ( ' message ', ( event ) => { if ( event. origin!== TRUSTED ) return; if ( event. source!== kbd. contentWindow ) return; const msg = event. data; if ( msg?. type!== ' set-layout ' ) return; // A name from a fixed list, never markup. if (! LAYOUTS. has ( msg. layout )) return; applyLayout ( msg. layout ); // your own function }); Enter fullscreen mode Exit fullscreen mode Check who sent the message, then check what it says. This chain started at a listener that checked neither.
Parameter's other lesson is about frames you create at runtime. Per MDN, a sandboxed frame without allow-same-origin "is treated as being from a special origin that always fails the same-origin policy." MDN also warns that combining allow-scripts and allow-same-origin on a same-origin document lets it remove its own sandbox:
const frame = document. createElement ( ' iframe ' ); // No allow-same-origin: the frame gets an opaque origin. frame. setAttribute ( ' sandbox ', ' allow-scripts ' ); frame. srcdoc = untrustedHtml; document. body. append ( frame ); Enter fullscreen mode Exit fullscreen mode And keep your expectations of CSP honest. ChatGPT had one, and an allowlisted host with a callback endpoint got the script through. Parameter puts it this way: "CSP is valuable defense in depth, but it cannot make an unsafe rendering path safe."
AI products render a lot of untrusted output: shared chats, model-written HTML, artifacts, previews. This chain existed "in the seams between public sharing, Preview, MathLive, frame creation, and CSP", and no single piece looked dangerous in review. A preview pane was also the way in for Noma's SneakPeek report on Claude Code Desktop, published in October.
If your app renders model output next to a third-party widget, list every message listener on that page and who can reach it. If npm ls mathlive prints something older than 0.111.2, start with that one.
This bug comes down to a handler that acted on a message without checking who sent it. Webhooks, queue consumers and event handlers raise the same question every day: who produced this, and should I act on it? The Event-Driven Architecture Pocket Guide is my short guide to systems that run on messages: sagas, CQRS, the outbox, and the traps nobody warns you about.
I also build Hermes IDE, a free, source-available IDE for running Claude Code, Codex and other coding agents side by side on macOS, Windows and Linux. Download it or star it on GitHub.