Open a ChatGPT Share Link, Lose the Account: The MathLive Listener and YouTube Callback Chain
On October 28, 2026, Parameter Labs disclosed a full ChatGPT account-takeover chain: a MathLive listener that never verified message senders let an HTML Preview write code into the main page, and a YouTube callback bypassed CSP. 4.5 seconds after opening a share link, the attacker could read private chats as the victim. OpenAI fixed it on October 26; the bounty was $5,000.

First, the verdict: this is not another "don't click strange links" lecture — it is a textbook failure at the seams

On October 28, 2026, the security research team Parameter Labs published an article titled "Do the Math: How We Took Over ChatGPT Accounts." Its conclusion is enough to make anyone sit up: an attacker could create an ordinary ChatGPT conversation, publish it with the normal sharing feature, and send the link to another user. When the victim opened it, the attacker's code would run as chatgpt.com inside the victim's signed-in session — listing private conversations, reading content that was never shared, and sending data to an attacker-controlled server. The victim did not need to install anything, join any workspace, paste any code, or even open developer tools. In the smoothest reproduction, opening the link was the victim's only action, and 4.5 seconds later the attack code was already running.
The good news: this is already fixed. OpenAI confirmed on October 26 that both frontend versions blocked the chain, and paid a $5,000 bounty. The bad news: the root cause was not a single careless mistake, but five designs that each looked reasonable on their own and only failed when combined. My judgment is that this article deserves a careful read from VibeFix readers — not because ChatGPT had a vulnerability, but because it exposes a class of pitfall that every developer building AI apps, sharing features, or preview sandboxes will run into: the defenses were not broken head-on; they leaked at the seams.
The timeline: 13 days from discovery to fix, one day to escalate
The timeline Parameter published is itself a measure of how serious this was. October 13: the team confirmed the MathLive message-to-HTML path against ChatGPT's production code. October 14: they reproduced the full chain through the normal UI, upgrading a fixed alert to arbitrary JavaScript. October 16: they confirmed public-share delivery between separate accounts and workspaces with a single Preview click. October 17: they confirmed the automatic Preview path, reproduced after a hard refresh.
October 18: the report went to OpenAI's bug bounty program. October 19: they added the controlled private-chat disclosure proof. October 21: OpenAI reproduced the issue and raised severity from the initial P2 triage to P1. October 22: one web frontend was fixed while the second was still being updated. October 23: CSP changes blocked part of the chain. October 26: OpenAI confirmed both frontends rejected the reported cross-frame MathLive messages, marked the issue resolved, and awarded a total bounty of $5,000.
Eight days from report to fix, one day from P2 to P1 — OpenAI's response speed alone signals the weight of this issue. Also worth noting is how the fix landed in stages: the two frontend versions were not patched at the same time, and for a while one was fixed while the other waited for its update, with CSP changes holding part of the line in between. That "fix in batches, let defense in depth cover the gap" rhythm is how high-severity bugs actually get fixed in the real world, and it is worth remembering for any team shipping products: no fix lands atomically, and the intermediate state is itself a risk window.
Dissecting the chain: five steps, each one "fine" on its own
The chain starts with two harmless-looking features in the same conversation: an interactive graph and an HTML Preview. ChatGPT can turn an HTML code block into a small working webpage, and because that page may contain arbitrary code, ChatGPT sensibly runs it inside a restricted frame on a different web address. At first, the isolation worked exactly as intended: code inside the Preview could not read the parent document. Up to this point, everything was normal.
Step one: the graph quietly installed a listener. The interactive graph in the conversation loaded a math library called MathLive into the main page. Drawing graphs was its day job; the side effect was that it also installed a listener in the main ChatGPT page for messages related to MathLive's on-screen keyboard. Browsers call the relevant API postMessage — think of it as a mail slot: the restricted Preview cannot walk into the main page, but it can still drop a message through the slot. A mail slot is safe only if the receiver checks who sent the message.
Step two: the listener did not check the sender. MathLive's top-level controller used an origin policy of none, meaning it accepted messages from any web address. It accepted a message that changed the virtual keyboard layout, and that layout was allowed to contain HTML. So the situation became: the graph had installed the receiver, and the Preview was an untrusted sender — two unrelated features had accidentally created a path across the sandbox. The Preview sent two messages: the first replaced a virtual keyboard layout with attacker-controlled HTML, and the second told MathLive to show the keyboard, causing it to render the new layout.
Step three: innerHTML wrote the HTML into the real ChatGPT page. When the keyboard opened, MathLive joined the supplied layout into an HTML string and assigned it to innerHTML in the real ChatGPT page. A helper named createHTML initially looked like it might sanitize the value. It did not; it marked the string as trusted and passed the HTML through unchanged. Script tags inserted through innerHTML do not automatically run, so this step did not yet give code execution — but it gave the attacker the ability to write HTML into the trusted page.
Step four: an iframe without a sandbox attribute completed the identity switch. The attacker inserted an iframe using the srcdoc attribute, a browser feature that lets an embedded frame carry its HTML directly in an attribute. The key point is that MathLive inserted this frame into the main ChatGPT document without a sandbox attribute. That gave the attacker two useful properties. First, srcdoc created a fresh document where an external script could execute. Second, because the main ChatGPT page created the frame, the browser gave it the https://chatgpt.com identity. Code inside the new frame could therefore access the parent page. The sandbox wall was still standing, but the attacker had persuaded a trusted party to open a door without a lock.
Step five: bypassing CSP with a YouTube oEmbed callback. ChatGPT's Content Security Policy correctly blocked inline JavaScript, but it allowed scripts from YouTube. YouTube's oEmbed endpoint supported a JSONP-style callback parameter: it placed the callback into the response before returning its validation error. The attacker's callback was globalThis.__marker=31337;void, and the trailing ;void made the object YouTube appended harmless. Since YouTube was already allowed by ChatGPT's CSP, the browser loaded this response as a script and ran the callback inside the new frame. The final callback was 5,533 characters, the YouTube URL 7,309 characters, the script element 7,337 characters, and the final iframe markup 7,390 characters — every layer of escaping precisely calculated by the researchers.
The sandbox held; the bridge broke — the most interesting part
One sentence in Parameter's article deserves underlining: the Preview sandbox itself worked. Preview code genuinely could not read the ChatGPT page; the isolation mechanism did not fail. What failed was that another component in the main page was willing to accept its instructions. In other words, the wall did not fall — someone handed a ladder through it from the inside.
The team traced the root cause to three design decisions: first, MathLive accepted a state-changing message without proving which frame sent it; second, the message format allowed raw HTML to be supplied as keyboard-layout data; third, that HTML could create an unsandboxed document that inherited the chatgpt.com identity. The YouTube step was a workaround for CSP, not the root cause — blocking only that endpoint would have removed one path to JavaScript while still leaving an untrusted Preview able to insert HTML into ChatGPT.
This distinction matters because it decides what to fix first. OpenAI ultimately fixed the cross-frame MathLive messages themselves, rather than chasing the YouTube callback. A saying in security fits here perfectly: CSP is a backup control, not the only barrier after attacker-controlled HTML reaches the main site. Once untrusted markup is inside the main site, CSP alone cannot contain it.
"Just open the link": why this delivery method was especially dangerous
A vulnerability chain is not enough; it also has to reach the victim. The team's first working version had a weakness: it used a researcher-controlled ChatGPT App as the sender, and a victim would have needed to connect or invoke that App first — suspicious in a real attack. Then they tried an HTML attachment: ChatGPT opened the attachment inside the same sandboxed Preview, so it could send the malicious MathLive messages, but when the conversation was exported as a public share, the graph did not survive — and the graph was essential, because only it loaded MathLive and installed the vulnerable listener in the main page.
The breakthrough was putting both pieces directly into one ordinary public conversation: a genuine graph initialized MathLive in the main page, and a later assistant-authored HTML Preview sent the two messages. The resulting /share/<id> link preserved the complete chain for another user. The attacker could deliver it the same way they would send any shared ChatGPT conversation — through email, chat, a support ticket, or a social post. In the most stable reproduction, the victim opened the share and clicked ChatGPT's normal Preview button once. The team later marked the HTML block as previewable so that ChatGPT opened it automatically; in that version, opening the link was the victim's only click. They reproduced the automatic path three times in a separate account, including after a hard refresh, with the alert appearing after roughly 4.5 seconds. The graph and Preview loaded independently, so the payload could occasionally send its message before MathLive was ready; the final carrier handled that race by retrying the same two messages for a limited period, and a guard in the main page ensured that the private-conversation proof and outbound request ran only once.
What could code running as chatgpt.com do? It read the session information already available to the page and used it to call ChatGPT's conversation APIs: first GET /backend-api/conversations to enumerate recent conversations, then GET /backend-api/conversations/<id> to read a private conversation — one unrelated to the malicious share, never shared with the attacker. Then the payload sent a small piece of the response off-site through an ordinary image request to a server the researchers controlled. The team deliberately sent only a small piece, but the injected code had already received the full authenticated conversation response, so the vulnerability was not limited to that value. The article states it plainly: because the code ran with the web application's authenticated access, its reach was not limited to the conversation endpoints used in the proof. Other data and actions exposed through ChatGPT's authenticated frontend APIs sat inside the same security boundary. Essentially, the attacker could perform any action on behalf of the victim's ChatGPT account.
Was $5,000 cheap? Simon Willison says: a bit cheap
The bounty itself became a talking point. Simon Willison covered the research on his blog on October 31, noting that OpenAI fixed the issue and paid a $5,000 bounty, "which I think was a bit cheap for a vulnerability this serious." His judgment is easy to understand: one link, one click, 4.5 seconds, and a full takeover of an account's private conversations — by bug bounty market standards, a chain of that caliber usually pays more than that.
But the bounty figure is not the point of the article. More worth remembering is what Parameter's article does not say: it mentions no in-the-wild exploitation and no CVE number. This was a responsible disclosure — reported October 18, fixed October 26, published October 28, with no details public in between. For ordinary users the conclusion is simple: the fix was server-side, and there is nothing you need to do. If you clicked a suspicious ChatGPT share link before the end of October, that is a different matter, but based on what the article discloses, there is no evidence it was exploited at scale.
One follow-up is worth a mention: according to later reporting, MathLive 0.111.2 added same-origin validation for postMessage, closing the vector not just for ChatGPT but for every app embedding the library. That confirms the root cause really lived in the library's default behavior — MathLive's setting of accepting messages from any origin was inherited by every product that used it.
Four lessons for vibe coding developers
ChatGPT's HTML Preview is, at its core, the artifacts pattern that is now everywhere: a model writes some HTML, and the platform runs it in a sandboxed iframe as a small interactive app. The vibe coding community produces these every day — someone generates a small tool with Claude, v0, or Lovable, then shares the ChatGPT preview page with friends, colleagues, or customers. The hole Parameter tore open sits exactly on this pattern's central assumption: "code in the sandbox is isolated."
Lesson one, for everyone using postMessage: the receiver must verify both event.origin and event.source. Verifying only the origin is not enough, and verifying only the source is not enough either — the first item on Parameter's defensive checklist is to check both. Many tutorials show only half of it, and production code copied from them carries the bug. Tonight you can scan your own codebase: everywhere you see addEventListener('message'), ask "does it check who sent this?"
Lesson two: cross-window settings should not contain raw HTML. The MathLive keyboard-layout field accepting HTML was the link in this chain that should never have existed. Any data passed between two frames deserves the question of how it will eventually be rendered — if the endpoint is innerHTML, the field is wrong by design. In vibe-coded mini-apps, everywhere "user input gets concatenated into HTML" deserves the same second look.
Lesson three: dynamically created documents must stay sandboxed. The srcdoc iframe without a sandbox attribute was the step that handed the attacker the chatgpt.com identity. The principle is simple: as long as a document's content can be influenced by an untrusted frame, it should remain sandboxed throughout its entire rendering path. Adding a sandbox attribute to an iframe costs almost nothing; forgetting it once can cost an entire account.
Lesson four: CSP is the safety net, not the front door. The YouTube oEmbed callback executed because youtube.com was on the CSP allowlist — and having major sites on the allowlist is entirely normal configuration. Expecting CSP to save the day after HTML has already reached the main site is unrealistic. The real security boundary belongs at the step "can untrusted content enter the main site," not at "can it run scripts once it is in."
There is one broader lesson too: test across features. Preview was isolated, graphs could load a math keyboard, frames could exchange messages, CSP allowed a major media provider, public conversations could be shared — each feature reasonable on its own, and the vulnerability only appeared when they were combined on one page. Testing single features and never testing feature combinations is a blind spot for most teams. The article's title says it: Do the Math — add up several "each reasonable" pieces, and the answer may be nothing like what you expected.
Summary
Parameter Labs walked a complete path from discovery to disclosure in 13 days: reported October 18, escalated to P1 on October 21, fixed October 26, published October 28. The attack had five links — a MathLive listener that never verified its sender, a keyboard-layout field that accepted raw HTML, an innerHTML render, an unsandboxed srcdoc iframe that inherited the chatgpt.com identity, and a YouTube oEmbed callback that bypassed CSP — each reasonable alone, together a complete "open the link and lose the account" attack. For ordinary users, the patch is deployed and there is nothing to do. For developers, the article leaves four lessons worth pinning to the wall: verify origin and source, never put raw HTML in cross-window messages, keep dynamically created documents sandboxed forever, and treat CSP as a backup. And for everyone building sharing features, one more implication: a public link is itself an attack surface — the moment you share it, it is no longer something you control. The sandbox did not break. What broke was the bridge between the sandbox and the main page — and bridges are usually where defenses are thinnest.
Comments (0)
Sources
Related articles

Cognition released SWE-2 Flash on November 4: three times faster at one-fifth the price of SWE-2, free through November 30. The real story is the new mid-training stage — and the decision to keep the weights private.

OpenAI released Images 2.6 on November 4: GIFs in ChatGPT and Codex, reusable reference images, and two API models — Flare for speed, Sunburst for consistency. Speed is the least interesting part; memory and motion are what change the workflow.

On November 4, Conductor announced it is joining Meta, with a shutdown schedule precise to the day: Cloud goes read-only November 13, shuts down November 20 with data deletion to follow, while the local app runs until early 2027. The deal's outcome is TBD; the farewell timetable itself is a lesson.