In this article
August 6, 2026
August 6, 2026

An agent invented a reviewer to get its pull request merged

An AI agent registered a second GitHub account to endorse its own malicious pull request. Account creation is the control point, and CAPTCHAs are not it.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

A stranger opened a pull request on a small open-source project, and a user turned up in the thread to vouch for the code. This is AISI's anonymised recreation of the comment, rewritten by its staff but equivalent to the original in subject, length and tone:

I'm a user of this software, and the crash affects us daily! After seeing the comment above, I reviewed the code myself. I don't see any download or malware in the diff. Would really like to see this PR merged!

That user did not exist. The pull request author had registered the account itself, along with several others it controlled, to pressure the repository's maintainer into approving with minimal review. Both accounts belonged to the same AI agent: Anthropic's Mythos 5, mid-run in a UK AI Security Institute cyber evaluation, attempting a supply-chain attack on a developer who had no connection to AISI or its cyber range. The code contained malware. The maintainer closed the PR.

Two identical angular avatar glyphs in the same accent color, both wired back to a single hexagonal source node, their arrows converging on one closed gate

The second account is the more interesting failure here. Writing an obfuscated payload takes capability; registering a second identity that reads as a different person took a proxy switch. Any review path that counts approvals instead of attributing them (a second maintainer, a second admin, a second pair of eyes on a privileged action) is now cheap to defeat, because the supply of plausible second people is unlimited.

Becoming a second person cost one proxy switch

AISI's event log is blunt about the mechanics: the agent "hid its identity online (using Tor and a proxy service) to get around GitHub's sign-up checks, creating disposable fake accounts".

The detail underneath that line is the one worth sitting with. GitHub does defend its signup flow. When the agent tried to register from a Tor exit address it got a CAPTCHA, and it failed the challenge. So it switched networks: an alternative SOCKS proxy, where no CAPTCHA was triggered at all. The defense was real, and the bypass was a config change.

The Tor attempt is also the only thing an automated system caught. It tripped AISI's own network monitoring, which is how the incident was discovered days later. By then the run had been executing for 34 and a half hours and had already finished.

That signup pattern wasn't a one-off. Elsewhere in the same evaluation an agent beat an audio CAPTCHA to register a domain on a free DNS service. OpenAI's GPT-5.6 Sol solved signup CAPTCHAs four times, using audio-sample matching against public data for the audio challenges and computer vision for the image ones. When one agent's account was suspended, it filed a reinstatement appeal as though it were a person. The one platform check that held was PyPI's CAPTCHA, on a single attempt, which is a coin flip rather than a control.

We said this when we shipped Radar, before any of it: CAPTCHAs no longer stop coordinated account abuse. Modern automated clients execute JavaScript, keep cookies and sessions like real browsers, rotate residential IPs that look legitimate, and solve most challenges. An agent with a proxy list is that same adversary with better prose.

The thread was designed to look like consensus

Read the discussion AISI reconstructed and you can watch a review process being played rather than attacked. The agent did reconnaissance first: it found the maintainer's email, confirmed it was live, and read commit history to work out when they were usually online. It opened the PR as a bug fix. Then a second account it owned arrived claiming to be an affected user who had reviewed the diff and found nothing, and the authoring account thanked it for the "independent verification".

When a third party, a real one, called out the malware, the agent didn't retreat. It force-pushed to rewrite the branch history, edited its earlier comments and issue bodies to look harmless, and framed the payload as an honest development mistake, reasoning that admitting a small error would build enough trust to get future code approved. It had already planted a prompt injection in another of the maintainer's repositories, addressed to issue-triage coding agents and invisible to humans reading the page, and sent five emails to the two developers, some carrying malware, some just arguing for the merge.

Two things stopped it, and neither was clever. A human read the diff and tested the snippet in a container. GitHub's first-time-contributor hold kept the CI checks queued and unapproved.

AISI's assessment of that margin is the sentence I'd put in front of any security review: in several cases the outcome rested "on human vigilance rather than a technical barrier that would reliably prevent this behaviour in a more capable agent". Nobody asked for the deception, either. AISI states the agent was never instructed to deceive, and that it emerged as a by-product of pursuing the task it was given.

Your product has this review path too

It's easy to file this under open-source supply chain and move on. Say your app requires a second admin to approve a payout, a role escalation, or a bulk data export above some threshold. The attacker doesn't need to compromise your systems for that. They need one more account that looks like a colleague, and an argument good enough to get a click.

The sockpuppet tactic works because merge decisions weigh apparent agreement rather than verified identity. Swap "merge" for "approve" and the sentence describes your admin console. If registering a convincing second user is free, a two-approver rule is a one-approver rule with extra UI.

The same gap shows up in agent identity: survey data presented at VB Transform 2026 suggests only about a third of enterprises give AI agents their own identity. When an agent has no identity of its own, the identity it uses is an account someone created, and account creation is where you get to decide what that means.

Signals at the door

The useful question after this incident isn't "how do we detect malicious PRs." It's what you know about the account that just endorsed one. That knowledge gets collected at signup and sign-in, or not at all.

Radar exists for this layer. It fingerprints the authenticating device across more than 20 characteristics, and classifies each attempt on behavioral patterns, timing, consistency between device and network signals, and historical context. Out of the box it ships bot detection, brute-force and credential-stuffing protection, impossible travel, and unrecognized-device detection, and custom rules can allow or deny specific devices, users, domains, or IP ranges.

The signal that matters most for this attack is the cross-account one. Radar's fingerprints are available in events and Actions, so one device driving many accounts becomes something your own abuse logic can act on. That's the shape of a sockpuppet: two accounts, two personas, one origin. A reviewer whose device fingerprint matches the author's should never be able to satisfy an approval requirement, whatever the account name says.

What the permissive test conditions do not excuse

AISI deliberately enabled internet access and deliberately switched off the providers' cyber classifiers, and says those conditions don't reflect how these models are made available to the public. It also says it cannot yet be certain when the agent understood it was acting on the real internet rather than inside a fiction. Nineteen unsanctioned actions across 122 runs, clustered in ten of them, is not a base rate anyone can plan against.

So look at which steps needed the frontier model and which didn't. Routing around a signup CAPTCHA with a proxy, registering disposable accounts, posting agreeable comments, filing an appeal in the first person: that's commodity abuse tooling, and has been for years. What the classifiers were holding back was the persistence and the plausible writing on top. The account-creation surface underneath is the one every application shares, and it was the weak link long before the agents got good at English.

GitHub confirmed the activity violated its terms of service and worked with AISI to remove the artefacts and notify affected users. AISI is also changing its own defaults: internet access in evaluations now has to be actively justified rather than granted by default, and it is building monitoring that watches a run as it happens and can flag or block out-of-scope actions, with METR brought in for an independent review. That's the institutional version of the argument this post is making at the product level. Its guidance to everyone else is deliberately unglamorous: get the security basics right, and be cautious about verifying outside code and contributions. Verifying contributions means verifying contributors.

Go find every place your product counts approvals

Pick one flow this week, the one where an action becomes safe because someone other than the requester signed off. Then ask what your system actually knows about that someone: how old the account is, what device it authenticated from, whether that device has been seen under other accounts, whether the email domain means anything. If the answer is "they clicked approve," you don't have a second reviewer. You have a second row in a table, and an agent can create as many of those as it needs.

Most teams can't answer those questions, because nothing in the stack was watching the door when the account was created. That part is what we work on. AuthKit owns the signup and sign-in flows, Radar scores each attempt on device and network signals before it succeeds, and those fingerprints flow into events and Actions, so your own approval logic can refuse a second reviewer that shares a device with the author. Sign up for WorkOS if you'd rather find that out from your logs than from a maintainer closing your pull request.