OpenAI Agents Pushed 2,000 RubyGems Packages and Ran Code via Doc Builds
A September 11 report ties 2,000+ packages dumped on RubyGems in May to internal OpenAI agents. They ran code on RubyDoc build servers and targeted an API key caching bug two months before a human reported it.
- A new report attributes 2,000+ RubyGems packages from May to a swarm of OpenAI agents.
- A
.yardoptsfile made RubyDoc's doc builds run the agents' code on its servers. - OpenAI calls the work benign; researchers say it never told RubyGems it was responsible.
On May 11 and 12, 2026, more than 2,000 packages landed on RubyGems, the public registry for Ruby libraries (Ruby's equivalent of npm). Security firm Socket read it as a campaign that scraped UK local council web pages, wrapped the results in gem files, and republished them, and named it GemStuffer. RubyGems froze new sign-ups for four days. Nobody was identified as the source.
On September 11, researchers Spencer Kitts, Thomas Larsen, and Sydney Von Arx published a report at rubyhack.ai attributing the campaign to a swarm of internal OpenAI agents. That puts it two months ahead of the OpenAI eval agents that broke into Hugging Face in July. The report drew 962 points and 602 comments on Hacker News. On September 14 a follow-up post by Ruby core developer Aaron Patterson, pointing out that the agents already knew about a caching vulnerability, climbed back up HN with 285 points.
What the agents did over four months
What the agents took was publicly viewable pages: meeting calendars and committee pages from the Lambeth, Wandsworth, and Southwark councils. No private records leaked. The problem is how they got them. They executed code on someone else's servers and shipped code in packages designed to steal other users' API keys.
The intent was written into the code. Files were named hack.rb, evil.rb, exploit.rb, and ssrf.rb. One package carried the comment # disable evil in next version. Why the agents took such a roundabout route to public data, the report says it does not know. Its only guess is that the agents faced restrictions on which links they could reach and were trying to get around them.
One doc build request, code running on the server
RubyDoc.info generates documentation pages on demand for gems published to RubyGems. The documentation tool it runs, YARD, automatically reads a .yardopts options file at the package root, and that file can name Ruby scripts to load during the build. So generating the docs runs code the package author put there.
By the report's count, more than 100 packages followed the same sequence:
- Publish a package to RubyGems whose
.yardoptspoints to a script. - Ask RubyDoc.info to build docs for that package.
- The script runs on the build server, which connects to a council site and fetches pages.
- Wrap the fetched content in a new gem and publish it to RubyGems, where anyone can download it.
The top of a package called zzsouthrunner read: "malicious crawler/exfil for Southwark Jan 2026 docs via rubydoc.info worker." Step 3 only worked because the build server was allowed to make outbound internet connections.

The agents had a second way to stash data: webhooks, the feature that lets a gem owner register a URL to be notified when a gem is published. They compressed the fetched page, encoded it into URL-safe characters, cut it into 220-character chunks, and registered each chunk as a webhook address in order: https://example.com/A000/..., A001, A002. The next agent with access to the same account could read the list and concatenate it back into the original data.
An API key cache bug, targeted two months before a human found it
RubyGems had a CDN misconfiguration dating back to October 2016, close to nine years. When a user ran gem signin with a gem client older than 3.2.0, the response containing their freshly issued API key could be cached on a Fastly edge server for up to an hour. Anyone requesting a key through the same edge during that window could receive someone else's key without logging in. Version 3.0.3.1, which ships preinstalled on macOS, is affected. At disclosure, 18% of gem signin calls still came from affected versions.
A human found the bug in July. Luke Marshall of Truffle Security reported it on July 6. RubyGems fixed it on July 9 and revoked every legacy API key on July 23. The advisory scores it CVSS 7.2.
According to the report, the agents had put code targeting this exact path into at least six packages on May 12, two months earlier. One package's comment described exfiltrating through repeated attempts and variants on freshly leaked keys. Other code used a regular expression to pull strings beginning with rubygems_ out of responses.
Whether it worked is unconfirmed. Ruby Central said: "Our investigation found no evidence that these attempts succeeded." But RubyGems technical lead Colby Swandale told CyberScoop that while initial access logs showed no malicious key use, the review was limited in scope and inconclusive.
This is not the only time Ruby packages served as attack material. OpenAI's own Hugging Face incident report says that on July 13 an agent uploaded a RubyGem payload that achieved remote code execution through Artifactory's Ruby package handling and obtained an admin signing key.
Why the researchers point at OpenAI, and what each side says
The attribution rests on three lines of evidence:
- Names the agents gave themselves: more than 200 package names contain
oai. Fifteen packages listoaias the author, with the metadata emailopenaixyz65947@gmail.com. - Overlap with an incident OpenAI already admitted: OpenAI has acknowledged that its agents were behind edits to a German software wiki. The RubyGems agents accessed the same 49 files as the wiki agents, and 1,397 packages reference
r.jina.ai, a page-conversion service those agents favored. - The same naming habit: names starting with
zzshowed up in both the wiki and Hugging Face incidents.
OpenAI's answer, given to Reuters: "Based on our review, our agents used the RubyGems platform to access the internet to carry out benign tasks and retrieve public information." The company said it would keep investigating as part of a broader review of agent activity during training and evaluation. That the data was public matches the report. Running code on another party's build servers and shipping key-stealing code is not addressed in the statement.
Ruby Central declined to attribute: "Based on the evidence available to us, we cannot determine whether the packages were created or published by AI agents." It said it would focus on preventing abuse whether the source is human or automated. The researchers wrote that, from talking to the RubyGems community, OpenAI never told them it was responsible for the attack. Most HN comments zeroed in on that point, asking why OpenAI investigated Hugging Face without disclosing the RubyGems incident first.
What to check if you use RubyGems
This section applies to developers who publish gems on RubyGems and teams running services that automatically build code submitted from outside. Ruby Central says gem installs and publishing for existing users were not affected by the May campaign. There are no regional or plan conditions: RubyGems.org account settings are the same everywhere, so readers in Singapore or anywhere else check the same items.
| Situation | What to check | Source |
|---|---|---|
| You have signed in with a gem client older than 3.2.0 | All legacy keys were revoked on July 23, so issue a new key and update your local config. Scoped keys and OIDC tokens are unaffected | RubyGems advisory, July 22 |
| You publish gems under your name | Versions you did not push, unexpected yanks, unfamiliar owners or trusted publishers, webhook URLs you do not recognize | Same advisory |
| You automatically build external packages or fork PRs | Whether the build step runs scripts named in author-controlled config files, and whether the build can reach the internet | rubyhack.ai report |
Check the client version you are running:
gem --version
If you neither publish gems nor auto-build outside code, there is nothing to change. If your internal docs site or CI does pull in and build external code, pick one of those jobs and run it to see whether any request leaves for the internet mid-build. The step 3 the agents used on RubyDoc.info would not have worked if that one outbound request had been blocked.