P.K. SHARMA

Cyber security intelligence, AI governance, practitioner analysis

Parameter Labs says a shared ChatGPT link could run attacker code via MathLive postMessage; OpenAI fixed, P1

Parameter Labs says a public ChatGPT share link could run attacker code as chatgpt.com in the signed in session of whoever opened it, through the HTML Preview and a MathLive message listener. Its writeup says OpenAI fixed it on 26 October 2026 and rated it P1; it states no use outside its own tests.

By · LinkedIn · · 18 min read

Eight days from report to fix, and what the writeup does not count

Parameter Labs' writeup of 28 October 2026 dates its report to OpenAI's bug bounty to 18 October and OpenAI's confirmation that both ChatGPT web frontends rejected the reported messages to 26 October: eight days (derived). It also dates the first confirmation of the weak path against ChatGPT's production code to 13 October, so on the writeup's own dates the weakness was present in production for at least 13 days (derived) before it was confirmed closed. How long before 13 October it existed is not stated. This briefing rests on that writeup, read in full on 31 October 2026 between about 11:29 and 11:39 UTC, after reading the host's robots file, and read again before this briefing was finished. The Hacker News covered the story on 31 October; that page refuses scripts, was not fetched for this briefing, and nothing here rests on it except where marked.

What the dates do not establish is exposure. The writeup does not say how many people could have been reached, whether anyone other than the researchers used the chain, or whether any real person's conversation was read. It calls its proof controlled and describes "separate accounts and workspaces"; it does not say in so many words whose accounts those were. It says the code could take any action as the victim, and that is a claim from where the code ran, not something the writeup shows. The section on what was demonstrated sets the two apart.

Parameter's own site, read on 31 October, describes it as a seller of autonomous AI penetration testing and continuous security (parameter.ai), and the writeup is its own publication, so a finding of this size is also a shop window for it. That is not a reason to doubt the dates or the mechanism, which the writeup prints in detail. OpenAI runs the product and the bug bounty, and has an interest in how the severity and the reach are described. This briefing read no OpenAI page or statement about the issue. The P1 rating, the fix dates and the bounty figure are therefore the researchers' account of what OpenAI did.

What the Parameter Labs writeup states and what it does not. Source: the writeup dated 28 October 2026, read 31 October 2026. Derived figures are marked.

ItemStated in the writeupNot stated
DatesFirst confirmed in production 13 Oct; report 18 Oct; OpenAI reproduced it 21 Oct; both frontends reject the messages 26 Oct; writeup 28 OctWhen the path first existed in production; how long it was exposed before 13 Oct
SeverityOpenAI classified the report as P1, raised from an initial P2 triageOpenAI's own scale, who set the rating, any CVE id (none is printed)
DeliveryA public share link sent by email, chat, support ticket or social post; one Preview click in the most stable version, none in a later oneHow many such links existed, or whether any reached a real user
ShownCode running as chatgpt.com; recent conversations listed; one private conversation retrieved; a small piece sent to the researchers' serverFiles, memory, settings or any account change reached or tested
Claimed"Essentially, we could perform any action on behalf of the victim's ChatGPT account"Any demonstration of an action beyond reading chat data
The fixRolled out across two versions of the web frontend; both reject the cross frame MathLive messages from 26 OctThe exact change; whether the listener, Preview, graphs or sharing were altered
MoneyA total bounty of $5,000How it was split or what it was based on
Use outside the testNothing is saidAny use in the wild; The Hacker News says none is reported, which is secondary
Drawn by this site from the timeline table in the Parameter Labs writeup. The day counts in the note are derived by this site.

The chain, step by step: where the sandbox held and where it failed

The chain needs four ordinary things. ChatGPT can turn an HTML code block into a small working page with a Preview feature, and because that page can run anything it is run, the writeup says, in a restricted frame on a different web address. The browser treats that as another website, so code in it cannot read the signed in page around it. A conversation can also hold a graph, and the writeup says drawing the graph loaded a maths library called MathLive into the main page and installed a listener there for messages about MathLive's on screen keyboard. MathLive's own site calls the project "A web component to edit math, rendered with LaTeX" (mathlive.io). And a conversation can be published as a public share, reachable by a link.

The feature that joins them is postMessage, the browser mechanism that lets frames pass messages even when they cannot read each other. MDN's page on it, read 31 October, states the receiving rule plainly: "always verify the sender's identity using the origin and possibly source properties", and still check the syntax of what arrives. The writeup says MathLive's top level listener used an origin policy of none, which meant it accepted messages from any web address, and that it did not require the settings message to come from a previously approved window. A message of the public MathLive type could therefore replace the keyboard layout, and the layout was allowed to contain HTML. The Technical details section shows the type and action names the writeup prints.

When the keyboard was shown, the writeup says MathLive joined the layout into an HTML string and assigned it to innerHTML in the real ChatGPT page. A helper named createHTML looked as if it might clean the string. According to the writeup it did not: it marked the string as trusted and passed it through unchanged. Script tags inserted that way do not run, so the supplied HTML instead built an iframe that carried its own page in a srcdoc attribute, with no sandbox attribute. Because the main page created that frame, the browser gave it the chatgpt.com identity, and code inside it could reach the parent page.

That fresh document still had to run a script. The writeup says ChatGPT's content security policy blocked inline code but allowed scripts from YouTube, and that a YouTube oEmbed endpoint put a callback parameter into its response, which was served as JavaScript, even when it was rejecting the value with an error. The writeup prints the technique with a harmless marker value. This briefing does not reproduce the request form, because what matters for an allow list is the behaviour of the endpoint, and the writeup describes it.

Drawn by this site from the technical walkthrough in the Parameter Labs writeup of 28 October 2026. Only steps the writeup states are drawn.

Delivery went through three versions, and the order matters. The first working version used a researcher controlled ChatGPT App as the sender, so a victim would have had to connect or invoke that App. A native HTML attachment could also send the messages, but when the conversation was exported as a public share the attachment stayed and the earlier graph did not, and without the graph nothing was listening. Putting a genuine graph and a later assistant authored HTML Preview in one ordinary public conversation kept the whole chain in the share link. The graph and the Preview loaded independently, so the message could arrive before MathLive was ready; the final version retried the same two messages for a limited period.

Three labels in this chain did the work of controls. "Sandboxed" was true of Preview, and the writeup says the sandbox itself worked, but a sandbox protects only what is inside it: the chain never had to break it, only to ask a trusted neighbour to act. "createHTML" sounds like sanitising; the writeup says it was an identity or Trusted Types wrapper, which can restrict who assigns to an HTML sink but does not make an attacker controlled string safe. And the content security policy was treated as the wall; the writeup says it stopped the researchers once more and was then passed through an allowed domain. In its words, CSP "should be a backup control, not the only barrier" once attacker controlled HTML reaches the main site.

What was demonstrated, between which accounts, and what is only claimed

The writeup shows, with a screenshot, a public share running code that the browser attributed to chatgpt.com and adding a banner to the real page. It then says the code read the session information already available to the page, called two conversation endpoints, listed recent conversations and requested message data for a selected private conversation. That conversation was, in its words, "unrelated to the malicious share and had never been shared with the attacker". It sent a small piece of the response to a server the researchers controlled, using an ordinary image request, and says it "deliberately" sent only that much.

Parameter Labs' own screenshot, captioned there "The public share modified the real ChatGPT page". Credited image, not drawn by this site.
Parameter Labs' own screenshot, captioned there "Controlled proof that private conversation data could leave the victim's session". The redacted fields are redacted in the original. Credited image, not drawn by this site.

Shown, claimed and not stated, from the Parameter Labs writeup of 28 October 2026 (read 31 October 2026).

PointShown in the writeupClaimed, or not stated
Code in the sessionAn alert and a banner on a real ChatGPT page; the automatic Preview path reproduced three times in a separate account, including after a hard refresh, alert after roughly 4.5 secondsBrowsers, devices and account types tested are not listed
Private chat readRecent conversations listed; one private conversation retrieved; a small piece sent off site; a screenshot of the receiving logClaimed: the code had already received the full response and could read it; the amount read was deliberately small
Beyond chat dataNothing beyond the two conversation requestsClaimed: any action as the victim; the writeup's own flow chart says read private chats and files, change the account. Files and changes were not shown
Whose accounts"Separate accounts and workspaces" on 16 Oct; "a separate account" for the automatic pathWhether the accounts were the researchers' own is not said
ReachDelivery by any shared linkNo count of reachable users, of shares, or of opened links

The writeup's headline says account takeover. What the record shows is reading chat data as the victim, from a separate account, once the code ran as chatgpt.com. The step from there to any action is an inference from where the code ran, and a reasonable one: code on the real origin has the page's own authenticated access. But it is inference, and the writeup says as much when it writes that the reach was "not inherently limited" to the endpoints it used. No account setting, file or password change is shown.

What OpenAI did, according to the writeup, and what is not stated

Everything about OpenAI's response comes from the writeup's timeline and its section on the fix. OpenAI reproduced both the code execution and the private conversation proof. The fix was rolled out across two versions of ChatGPT's web frontend. On 22 October one was reported fixed while the second was still being updated; on 23 October content security policy changes blocked part of the chain while one update remained. On 26 October OpenAI confirmed that both versions now rejected the cross frame MathLive messages the report used, marked the issue resolved, and awarded a total bounty of $5,000.

That sentence is about the messages. It is not a statement that the design choices behind them changed. The writeup does not say whether the listener now checks the sender, whether the keyboard layout can still carry HTML, whether the frame MathLive builds is now sandboxed, whether the YouTube allowance in the policy was narrowed, or whether Preview, graphs or sharing changed at all. It does not say whether MathLive's maintainers were told or whether the library changed. Those are the questions a developer would ask, and the record leaves them open.

Drawn by this site from the three design decisions and the lessons in the Parameter Labs writeup. The middle row is this site's reading of those lessons; OpenAI's exact changes are not stated.

The writeup names three design decisions that turned one mistake into a full exploit: MathLive accepted a state changing message without proving which frame sent it; the message format allowed raw HTML as keyboard layout data; and that HTML could create an unsandboxed document that inherited the chatgpt.com identity. It calls the YouTube response "the final workaround for CSP, not the root cause", and says blocking only that endpoint would have removed one path to JavaScript while leaving an untrusted Preview able to insert HTML into ChatGPT. Reading the three together, any one of them fixed would have broken this chain. That is this site's reading, and it is also why the checklist below asks for all three controls and not one.

What this adds to briefings already on the site

Our briefing on custom ChatGPTs and ClickFix found that the chatgpt.com address proved nothing about what sat behind it: the page held a message and a link, and the command was elsewhere. This writeup is the opposite case. Content in a shared conversation, on the researchers' account, ran with the chatgpt.com identity itself, so the address was the origin. The earlier case needed a person to paste a command. This one, on the writeup's account, needed an open link and at most one click.

The Pwn2Own Edge briefing covered a sandbox escape whose author says it used five logic bugs and no memory corruption. This is the same family: no memory bug, a boundary that held, and ordinary features that together did not. What it adds is a consumer AI product's share and Preview features, treated as a trust boundary and shown failing at the seams.

Technical details

The writeup prints message names, a deminified description of the listener, request lines, response headers and sizes. It prints no CVE id, no indicator of compromise, no detection rule, no query and no log signature, and it does not name the server the researchers used. Everything below is copied from the writeup, read on 31 October 2026, as short fragments. This briefing deliberately leaves out the complete messages, the payload and the callback request form: they are exploit material, and a defender does not need them. Nothing here is a proof of concept.

The first block is the identifiers of the vulnerable message interface. A team that ships MathLive, or any library with a window message listener, searches its own code for the type string and looks at what each action does with the data. The writeup says both fixed ChatGPT frontends now reject the cross frame messages its report used.

Text
type: "mathlive#virtual-keyboard-message"
action: "update-setting"   (replaces the keyboard layout; the layout field is named layouts, with layers and markup)
action: "show"             (renders the keyboard)
Parameter Labs writeup, the message type and the two actions, fragments copied from the page, read 31 October 2026. The text after the action names is this site's gloss of the writeup's description.

The second block is the writeup's deminified view of the listener and of the sink. Read it for what is missing: the origin check calls a validator that, with the policy the writeup calls none, accepts every sender, and the message handler is given the sending window but, the writeup says, did not require it to be an approved one for the settings message. The last line is where the layout ended up as markup in the real page. A defender reviewing a listener checks both lines: who may send, and what the receiver does with the data.

Text
if (event.data?.type !== "mathlive#virtual-keyboard-message") return;
if (!validateOrigin(event.origin, this.originValidator)) return;
this.handleMessage(event.data, event.source);

keyboardContainer.innerHTML = MathfieldElement.createHTML(keyboardHTML);
Parameter Labs writeup, Technical notes, the listener control flow and the sink, copied from the page, read 31 October 2026. The writeup calls the control flow deminified and equivalent.

The third block is what the injected code asked ChatGPT for, as the writeup prints it. These are the web application's own endpoints, called from a frame with the same origin, so a request log by path alone would not tell them from normal use. The writeup gives no server side signal for telling them apart. It also says the code read the session access token and account identifier from the page and, before the outbound image request, cleared references to them. The angle brackets are the writeup's own placeholders, not values.

Text
GET /backend-api/conversations?...&limit=20&order=updated&offset=0
GET /backend-api/conversations/<conversation-id>?num_turns=2

Authorization: Bearer <victim token>
ChatGPT-Account-ID: <victim account>
Accept: application/json
Parameter Labs writeup, Technical notes, request lines and header names, copied from the page, read 31 October 2026. Placeholders are the writeup's; no value is shown.

The fourth block is what the writeup observed from the YouTube endpoint: an HTTP 200 whose content type was JavaScript, with nosniff set, and, it says, no Cross Origin Resource Policy header. For a developer with an allow list the point is that an endpoint meant to return data returned something the browser accepted as a script. Check every third party domain in your script source list for any endpoint that echoes a parameter into a script typed response.

Text
HTTP 200
Content-Type: text/javascript; charset=UTF-8
X-Content-Type-Options: nosniff
Parameter Labs writeup, Technical notes, response status and headers of the oEmbed response, copied from the page, read 31 October 2026. The writeup adds that no Cross Origin Resource Policy header was observed.

The sizes are the last artefact: the writeup says the final callback was 5,533 characters, the YouTube URL 7,309, the script element 7,337 and the final iframe markup 7,390. Derived by this site, and inference only: a script request to a video provider with a URL of about 7,300 characters, or an iframe with a srcdoc attribute and no sandbox attribute created in the live document, would be unusual in a page's own telemetry and in content security policy reports. The writeup does not offer these as detections.

For developers, in the order worth doing

Items 1, 3, 4, 5 and 8 follow the lessons the writeup states, reduced to a line, and item 1 is also what MDN says. The others are this site's guidance, drawn from the chain. The order is our judgement: it puts the controls that would each have stopped this chain first.

Take this with you

Ranked checklist for anyone building a web app

  • Check both event.origin and event.source in every message listener, against an exact expected value, never a wildcard or a policy of none. MDN says to verify the sender with the origin and possibly source properties, and the writeup says to verify the sender's address and the exact window given permission.
  • Treat third party library listeners as part of your own attack surface. List every window message listener that loads in your page, including those added by a graph, editor, widget or embed, and read what each does with the data. This site's guidance, from the graph that installed the listener.
  • Do not accept markup in cross window settings. Take plain data with a fixed schema and refuse the rest, even from a sender you trust; MDN says to verify the syntax of the message too. The writeup's lesson is that cross window settings should not contain raw HTML.
  • Keep documents built from untrusted content sandboxed. Any frame created from markup that an untrusted frame can influence should carry a sandbox attribute. The writeup's lesson; the attribute advice is this site's wording.
  • Do not let a sandboxed frame reach a trusted frame through a bridge. Draw every channel from the sandbox to the main page, messages included, and ask what each can make the main page do. The writeup's summary lesson is that isolation is only as strong as every bridge across it.
  • Tighten script source allow lists. Remove third party domains you do not need, and treat any allowed domain with an endpoint that echoes a parameter into a script typed response as a bypass. The writeup says the YouTube step was only the last workaround, so fix the root causes too.
  • Test share, preview and export features as a trust boundary. Build a share that combines every feature, open it as a second user, and read what runs. In this chain the first export lost the graph and broke the path, which shows how much depended on one feature being preserved.
  • Do not treat the content security policy as the only barrier once attacker influenced HTML can reach your main origin. The writeup says it should be a backup control.
  • Assume script on your own origin can read whatever the page can read, including tokens in page data, and size your blast radius to that. This site's guidance; the writeup does not say what OpenAI changed here.

The question that exposes the gap

The writeup's own conclusion is that every individual feature looked reasonable and the flaw appeared only when they were combined in one page. Which two features in your product have never been tested together by someone who controls one of them, and which message listener in your page would still accept a sender you never meant to hear from?

Key facts

Sources

  1. PrimaryParameter Labs writeup, 28 October 2026, 'Do the Math: How We Took Over ChatGPT Accounts': the chain, the timeline, the severity wording, the demonstration, the claims, the fix, the bounty, the technical notes and the two screenshots. Read in full. The sole source for the findingsParameter Labsaccessed 2026-10-31
  2. PrimaryParameter home page, read for how the publisher describes its own business (AI penetration testing and continuous security). Context for the publisher's interestParameteraccessed 2026-10-31
  3. Reported byMDN, Window.postMessage(), the security concerns section: verify the sender with origin and possibly source, and verify the syntax of the message. Read for the receiving ruleMDN Web Docsaccessed 2026-10-31
  4. Reported byMathLive project home page: describes the Mathfield component. Read only to identify the library the writeup namesMathLiveaccessed 2026-10-31

Share this briefing

Know someone who owns this problem? Send it to them.

Related briefings

The briefing, in your inbox

Practitioner analysis of cyber and AI security news. No vendor noise.

How often

Every new briefing in one email, at 7am, or at 7am, 12:30pm and 6pm. Nothing is sent when nothing is new. Unsubscribe any time.