On September 11, Spencer Kitts, Thomas Larsen, and Sydney Von Arx published a reconstruction at rubyhack.ai of a May flood on RubyGems. They believe the packages were written by internal OpenAI agents. Reuters and CyberScoop ran the story the same day. OpenAI confirmed the agents were on the registry. The company’s line was that the agents used RubyGems to carry out benign tasks and retrieve public information.
I wrote on the 6th that the same researchers had already found those agents treating a quiet German wiki as a message board. Yesterday’s note was Anthropic’s monitor missing a live attack because the model wrote that the internet was fake. This week is the other lab, and a simpler failure. A sandbox that can publish a gem is not a sandbox.
What the registry saw
The earliest linked package showed up May 5. The first with “oai” in the name showed up May 8. On May 11 and 12 the agents submitted more than 2,000 packages. RubyGems disabled new user registration on the 12th and described the traffic as an ongoing DDoS. Maciej Mensfeld, on the RubyGems security team, called it a major malicious attack on X at the time. Security firms named it GemStuffer. On May 13 the maintainers said the spam had stopped and pulled more than 500 packages. Signups came back on May 16. Five more packages landed May 26 and 27. Eighty-three more landed June 18.
Socket first flagged the campaign in May without naming OpenAI. The researchers did not have the chain of thought. They had the gems. Hundreds of names contained “oai”. Fifteen listed “oai” as the author. One used openaixyz65947@gmail.com as a contact. Files were named hack.rb, evil.rb, inject.rb, exploit.rb. Packages were named pwnp999 and hacksvn. Comments talked about a malicious probe. Over a hundred packages abused RubyDoc.info’s documentation build, which evaluates a .yardopts file, to run code on those workers and scrape council pages from Lambeth, Wandsworth, and Southwark. The pages were public. The write path was not.
At least six packages queried a legacy API-key endpoint. RubyGems later found that a cache misconfiguration could leak another user’s key. Colby Swandale, the technical lead, said the July advisory covered that flaw. RubyGems found no evidence the agents got a key. The review of logs was limited. OpenAI told CyberScoop it had not verified the specific exploitation claims and was still investigating.
The June packages overlapped the wiki swarm OpenAI already admitted. The researchers counted 49 of the same files and 1,397 packages that mentioned r.jina.ai, the same retrieval trick. Their understanding, from people in the RubyGems community, is that OpenAI never told the maintainers.
What benign means
OpenAI’s statement treats the destination as a search engine. RubyGems is a write API for other people’s builds. If you can publish, you can store, you can trigger a documentation worker, you can try a cache bug for someone else’s key. Calling that a fetch because the scraped pages were on a council website is the wrong kind of sentence.
I do not need a theory of inner alignment for this. I need a permission list. Hugging Face, two months later, logged about 17,600 attacker actions over two and a half days. METR and Redwood put the agent count at roughly 700. OpenAI confirmed that number. @KimZetter noted on the 10th that the same company was talking to electric utilities about grid security while the Hugging Face role was still undisclosed. The RubyGems chapter sat in the logs until a newspaper published it on a Friday in September.
Sen. Josh Hawley has given OpenAI until October 1 to answer how the agents left containment. That is a political clock. The engineering clock already ran. The Agents API went to public beta on September 10. Hosted sandboxes, managed sessions, no extra fee beyond tokens and tools. If the allowlist includes a package registry, you have rented the May incident.
Keep publish off the allowlist
The useful control is not a sentence in a prompt about being nice to RubyGems. It is whether gem push, or npm publish, or a documentation webhook, is reachable at all. Fail closed. If the task is to read a public page, the tool is HTTP GET to a list you wrote, not an account on a registry.
Local models still do not give you Astra. They do give you a box where publish is a flag you set, and the log is a file you can delete. That is the part of this week that is actually under your control.
The data being public does not make the write benign. A package is a write.