The last step was a prompt
On September 18 Hacktron published the chain it used to reach OpenAI's private monorepo. It begins with a picture uploaded to a public forum and a heap overflow in an image decoder whose upstream fix carried no CVE. It ends with the researchers sending a prompt to an employee's Codex account and the agent opening the pull request. Nothing was injected and nothing malfunctioned; the agent did what whoever held the session asked. Bromure Agentic Coding decides that write on your Mac, outside the session.
The last step in this chain needed no exploit. The researchers were holding an employee's coding agent, so they asked it to open a pull request in the private monorepo, and it opened one.
At six in the morning UTC on July 25, three researchers at Hacktron had code
execution on OpenAI's public community forum. By early afternoon a pull request
numbered 1186742 was sitting in openai/openai, the private monorepo, and they
had written no code to put it there. They asked an agent, and the agent filed it.
Hacktron published the chain on September 18, and it repays reading link by link, because every link is ordinary. The researchers are Harsh Jaiswal, Mohan Pedhapati and Rahul Maini. The Hacker News covered it the following day.
A picture, a conversion, and a parser
The way in was the forum's image upload. Discourse checks uploaded images with a
Ruby library called FastImage, which reads enough of a file's header to work out
its dimensions and format. FastImage does not understand HEIF, the container an
iPhone photo arrives in, so Discourse handed those files to ImageMagick's
magick command for conversion. ImageMagick decodes HEIC with libheif, which
is C.
That is the whole entry condition. A format the safe checker did not recognise went to a converter that called a parser written in a language where a malformed length field is a memory-safety problem. Hacktron found a heap buffer overflow in HEIC decoding and got out-of-bounds read and write primitives from it.
The paperwork is worse than the bug. Upstream had fixed that overflow the
previous year and never labelled the commit a security fix. It got no CVE, no
advisory, and no changelog line a dependency scanner could read, so Debian never
backported it, and Discourse's Docker image shipped libheif 1.19.7 on Debian 12
with the bug intact. No scanner anywhere had grounds to flag that version. Debian
issued DSA-6417-1 later and the issue now carries CVE-2026-32882, assigned after
somebody exploited it.
You cannot scan for a bug nobody filed. What you can decide in advance is what the parser is standing in when it falls over.
The exploit took an evening
Hacktron started looking at the upload pipeline on July 23. On July 24 they went after the overflow with Claude Opus 4.8, which by their account struggled across several sessions to produce anything that worked against address space layout randomisation, the defence that shuffles a process's memory map so an attacker cannot predict where anything lives. Turning a heap overflow into reliable execution against ASLR is the hard part, and it is where this kind of research stalls for a week.
Anthropic released Opus 5 that evening. Hacktron started a fresh session, and
three hours later had a working ARM64 exploit running against a local Mac. They
asked the model to port it to the x86-64 and jemalloc configuration Discourse
runs on, and confirmed local code execution through an image upload at six the
next morning. Then they put Claude in an autonomous loop against their own
Discourse Cloud instance. By ten it had code execution there too, and read
/etc/hosts to prove it.
They are careful about what that means, and the qualifier belongs in any reading of it:
This was not fully autonomous hacking. Human expertise remained necessary to direct sessions, validate results and manage disclosure.
Fair. Three skilled researchers pointed the thing, and their skill did not change overnight. The bug did not change either. Between those two sessions the only new variable was the model, and it took a piece of exploitation work from several failed attempts down to three hours. The campaign this belonged to cost Hacktron under $3,000 in tokens and a few hours of human time.
That number is the planning input. A bug that never got a CVE, sitting in a transitive dependency of a file converter, now converts into a reliable primitive for the price of a laptop. Waiting for the advisory is a hope, and nobody out-runs that clock by reading changelogs faster.
The last step was a prompt
Code execution on the forum gave Hacktron nothing OpenAI cared about. A second problem did that. OpenAI had wired its single sign-on so a compromised forum session became a no-interaction takeover of the ChatGPT and Codex account of any employee who used the forum. In Hacktron's words, the SSO issue is what turned a forum compromise into access to ChatGPT and Codex.
An employee's Codex was connected to the company's GitHub organisation, so the
researchers did the obvious thing. They sent that account a prompt asking it to
open a pull request in the internal monorepo, and PR 1186742 appeared in
openai/openai. Then they stopped, without reading source, and filed the report.
Read that step again. Someone holding a valid session asked the agent to do an ordinary thing, and the agent did it with the write access it already had. No injection, no jailbreak, no malformed input. Every control anyone had built sat behind that session: the login, the org membership, the repository permissions. Once an attacker held the session, they held all of it.
A coding agent is a standing grant of write access to your repositories, attached
to a conversation. Whoever reaches the conversation inherits the grant. That
holds for Codex in a browser, and it holds for Claude Code, Codex CLI or Gemini
CLI on a laptop, where the agent carries a real ghp_ token in
~/.git-credentials and a real GH_TOKEN in its environment. On a laptop the
ways in multiply: a poisoned file in a repository, an issue comment, a package
postinstall script, a hijacked terminal.
OpenAI patched the forum in about fourteen hours. The report went in that morning and OpenAI confirmed the fix at 22:49 UTC the same day. Discourse shipped its own fix over the weekend and published advisory GHSA-vhm9-85gw-x335 on July 28, and the bounty came to $6,500. The piece of this shape that sits on your own desk is the agent.
Where the write gets decided
Bromure Agentic Coding keeps the answer to "who is allowed to push" on your Mac, outside the agent's session, because the session is what an attacker is trying to reach. Two things happen there.
First, the agent's environment holds no token to inherit. The workspace VM gets a
placeholder: ghp_ plus 36 characters, shaped so gh and git accept it
without complaint, derived from your real token with HKDF-SHA256 and a
per-install salt so it never appears to rotate between sessions. Your real token
lives encrypted on the host, and the host proxy substitutes it onto the wire
after the request has left the VM, only when the destination matches the host
that token was minted for. As the manual puts it: no file, environment variable,
or process inside the VM ever contains a real API key, OAuth token, AWS secret,
or SSH private key. Carry that placeholder somewhere it does not belong and the
proxy answers HTTP 451 without forwarding a byte, pauses the VM, and writes a red
row into the Security Timeline.
The second is the one this story asks for. Every git forge credential carries a
write policy, set per workspace in the Guardrails pane, and new workspaces
default to Prompt before write. Reads pass without comment. A git push
arrives on the wire as git-receive-pack and counts as a write, and so does a
POST to the pull requests API. When one arrives, the proxy holds the request
and opens a dialog on your Mac carrying the literal operation rather than a
summary of it: the method and the path. You get Allow for 15 minutes,
Allow once, Allow for the rest of the session, or Don't allow. A
refusal reaches the agent as an ordinary API error it can report, not a hung
connection.
The pane's own description of where this runs is the sentence that matters: enforced in the proxy, so a compromised agent in the VM cannot bypass it.
Whoever is driving the agent, you, a prompt injection buried in a fixture file, or somebody holding a session they took from you, the write leaves through the same proxy and gets classified the same way. The dialog on your Mac is the authorisation, and an attacker holding your session does not have your Mac.
The parser problem comes to your desk too
The other half of the chain generalises too, and few people have priced it in.
A coding agent spends its day reading files it did not write: test fixtures that
came with the repository, an image attached to a bug report, a screenshot you
pasted into the conversation, a PDF of a spec, a tarball from a release page, a
font, a video the front end is supposed to play. Handling those means shelling
out to magick, ffmpeg, pdftotext or unzip, or to an image library bound
into Python, and each of those is a C or C++ parser eating bytes somebody else
chose. That is the condition that let a forum upload reach libheif. An agent
hits it more often than you would, because it never hesitates before opening
something.
Detection has no answer here. The libheif bug carried no CVE at the moment it mattered, so no scanner, allowlist or version pin would have said a word. You get to choose the blast radius, and that is all.
In a Bromure workspace that decoder runs inside an Ubuntu guest under Apple's
Virtualization.framework, a hypervisor boundary rather than a process sandbox,
in a guest Bromure treats as untrusted by construction. A heap overflow in an
image parser buys an attacker a shell in a Linux VM whose credentials are
placeholders and whose route to the internet is an ordered ruleset on the host.
The virtual switch matches that ruleset on destination IP and DNS-snooped
hostname across TCP and UDP, and the proxy matches it again on TLS server name
and HTTP method. Set unmatched traffic to Deny and the shell has an allowlist
and nothing else. The switch diverts :80 and :443 into the proxy itself, so
there is no proxy variable for the shell to unset. Rule edits reach running
sessions without a reboot, and every verdict lands as a Firewall row in the
Security Timeline.
Assume the parser falls over
This bug had no CVE at the moment it counted. Plan for the decoder to lose, and decide ahead of time what it is standing in when it does.
A session is not an authorisation
Anyone who reaches the conversation inherits the agent's write access. Put the decision for a push somewhere the conversation cannot reach.
Leave nothing in the VM to steal
A placeholder that tools accept is worth nothing upstream, and worth less than nothing to an attacker who carries it somewhere else.
Price the exploit
Three hours and a few thousand dollars in tokens. Reading advisories faster does not answer that number.
Hacktron's write-up is good research, and honest about how much of it a human still had to steer. The ending is the part that will still be true next year. Every defence in the chain sat behind an identity, and once a session carrying that identity changed hands, none of them had an opinion any more. Someone asked the agent, and the agent complied.
If your agent can push, ask where the permission to push lives, and whether an attacker who took everything the agent has would take that too. On a laptop they would. Move that one decision off the machine the agent runs on, and the last step of this chain has somewhere to stop.