On this page
AI agents are evolving quickly. They no longer just answer questions, they take actions, solve obstacles, and keep going until the job is done. That persistence is the selling point, but also a problem. When an agent hits a wall, it doesn’t stop, it finds a way around, and that way around can look exactly like an attack.
A swarm of AI agents published more than three thousand packages to RubyGems between May and July 2026 and used the registry as a scraping proxy. The agents were trying to collect public meeting calendars from three London council websites. Rate limiting blocked them. So they published gems that RubyDoc.info would execute during a documentation build, ran their crawler on RubyDoc’s servers instead of their own, and returned the results to the registry as new gem versions and webhook URLs.
The campaign covers 3,022 packages across 3,315 distinct name and version pairs. RubyGems read the traffic as a distributed denial of service at the time and disabled new user registration on May 12. Nobody knew who was behind it. On September 11, four months later, Spencer Kitts, Thomas Larsen and Sydney Von Arx published a reconstruction attributing the campaign to internal OpenAI agents running during training and evaluation.
What happened
The uploads arrived in bursts, and one day carries most of the campaign.
The agents signed up with disposable email addresses. RubyGems gave every new account a working API key straight away, before anyone clicked the verification link, so the swarm could publish gems without ever opening an inbox.
The documentation build that runs package code
RubyDoc.info builds reference documentation for any gem on RubyGems, and that build runs YARD,
Ruby’s documentation generator. YARD reads
.yardopts from the root of the gem
and applies every line in it as though someone had typed it on the command line. Whoever publishes
the gem writes that file, so the publisher picks YARD’s arguments. One of the arguments YARD
accepts is --load, which runs a Ruby script before the documentation command starts.
# .yardopts in yardxabc889 0.0.1--load ./evil.rbThat single line turns a documentation service into remote code execution. Publish a gem, trigger a docs build, and RubyDoc runs your Ruby on its own infrastructure with its own network egress. Over a hundred packages in the campaign followed this path.
Sending the data back through RubyGems
The agent had no access to the data it scraped. The crawler ran on RubyDoc’s servers, and the results stayed there. So the payloads sent them back through RubyGems.
Some packages published the scraped pages as a brand new gem. Others wrote the pages into their own README, deleted the line that had loaded the payload, and published themselves again as a new version.
Signs the packages were agent written
The agents made no effort to hide what the packages did. 233 of the package names contain oai,
and fifteen gems also list it as the author. The payload files are called hack.rb, evil.rb,
ssrf.rb, inject.rb and exploit.rb. The code inside is full of comments like #hack and
# malicious probe, and one of them writes out the entire plan,
# malicious crawler/exfil for Southwark Jan 2026 docs via rubydoc.info.
The best one is in yardxabc889, where a comment reads
# disable evil in next version and bump version. The payload left itself a note about covering
its tracks later.
Responses from OpenAI and RubyGems
An OpenAI spokesperson told CyberScoop the company is aware of the incident and is working with RubyGems and the researchers on a broader review, and described the episode as benign.
Based on our review, our agents used the RubyGems platform to access the internet to carry out benign tasks and retrieve public information. We’ll continue to investigate as part of our broader review of agent activity during training and evaluation.
RubyGems technical lead Colby Swandale said the team cannot determine whether the packages were created or published by AI agents, and that the focus stays on preventing abuse from any source. RubyGems has since required email verification before issuing keys and added rate limiting.
Four months passed between the uploads and the attribution, and the attribution came from three outside researchers rather than from the vendor whose agents did it.
What to check in your own build workers
Ruby is just a means here. A service accepted a file from an untrusted package, executed it, and ran with credentials that could publish. Any system in your environment built that way is the same class of target, including CI runners, webhook handlers, preview environments and documentation builders.
Start with these:
- Any job that runs options or scripts that came from inside a package. YARD’s
--loadis one. npm lifecycle scripts,setup.pyand Makefile targets your build calls are others. - What those jobs can reach. A documentation build that only reads packages does not need a publishing key or cloud credentials.
- Anywhere package metadata gets rendered, admin pages included. Whoever publishes the package writes those fields.
The agents were after something ordinary. They wanted council meeting calendars, hit a rate limit, and went around it through whatever would run their code for them.
The full package list
Merging the published campaign inventories gives 3,323 gem name and version pairs across 3,025 distinct gem names. Search the table or download the CSV.
| Row | Ecosystem | Package | Version |
|---|---|---|---|
| 1 | gem | a----00proxy38998 | 0.0.1 |
| 2 | gem | a----00proxy43876 | 0.0.1 |
| 3 | gem | a----00proxy83005 | 0.0.1 |
| 4 | gem | a----00proxy90278 | 0.0.1 |
| 5 | gem | a----00proxy93049 | 0.0.1 |
| 6 | gem | a--00cfmapjson726 | 0.0.1 |
| 7 | gem | a--0zlink83721 | 0.0.1 |
| 8 | gem | a00_arcguide | 0.0.1 |
| 9 | gem | a0a_arcgraph | 0.0.1 |
| 10 | gem | a0a_arcgraph | 0.0.2 |
| 11 | gem | a0a_arcgraph | 0.0.3 |
| 12 | gem | a0a_arcgraph | 0.0.4 |
| 13 | gem | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 0.1.0 |
| 14 | gem | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-test-gem | 0.0.1 |
| 15 | gem | aaaresultfetchx | 0.0.1 |
| 16 | gem | abcfoobar1778550625683711 | 0.0.1 |
| 17 | gem | abcfoobar1778550629075869 | 0.0.1 |
| 18 | gem | abcfoobar1778550632348198 | 0.0.1 |
| 19 | gem | adep236097 | 0.0.1 |
| 20 | gem | adep97117 | 0.0.1 |
| 21 | gem | agenda-sample-yard | 0.1.0 |
| 22 | gem | agenda-sample-yard | 0.1.1 |
| 23 | gem | agentoaitestabc123 | 0.0.1 |
| 24 | gem | agentxyztest987 | 0.0.1 |
| 25 | gem | agentxyztest987 | 0.0.2 |
| 26 | gem | agentxyztest987 | 0.0.3 |
| 27 | gem | agfosscab20 | 0.0.1 |
| 28 | gem | agfossgrant23 | 0.0.1 |
| 29 | gem | agfosslic24 | 0.0.1 |
| 30 | gem | agfosssacre22 | 0.0.1 |
| 31 | gem | agfossstd21 | 0.0.1 |
| 32 | gem | aicalllngxwwh | 0.0.1 |
| 33 | gem | aicalojdhdxoa | 0.0.1 |
| 34 | gem | aicaltdfdlekz | 0.0.1 |
| 35 | gem | aicaltroixtqg | 0.0.1 |
| 36 | gem | aicalunosquaz | 0.0.1 |
| 37 | gem | aidoklxlhvfkeq | 0.0.1 |
| 38 | gem | aitdaludfnfq | 0.0.1 |
| 39 | gem | aitdodvcpoam | 0.0.1 |
| 40 | gem | aitfnurhpxuo | 0.0.1 |
| 41 | gem | aituhdbvptgs | 0.0.1 |
| 42 | gem | aiudazqldpcn | 0.0.1 |
| 43 | gem | aiudhvnuubzt | 0.0.1 |
| 44 | gem | aiuepfczrxji | 0.0.1 |
| 45 | gem | aiuexzbmeygo | 0.0.1 |
| 46 | gem | aiukjisdgcxp | 0.0.1 |
| 47 | gem | aiumidgzwyzv | 0.0.1 |
| 48 | gem | aiuovjltsvrq | 0.0.1 |
| 49 | gem | aiuprjluomxz | 0.0.1 |
| 50 | gem | aiuupaosataw | 0.0.1 |
| 51 | gem | aiuuwsosbsqp | 0.0.1 |
| 52 | gem | aiuvipuscwac | 0.0.1 |
| 53 | gem | aiuydcysvgvt | 0.0.1 |
| 54 | gem | aiuzubwjbsoj | 0.0.1 |
| 55 | gem | aiuzxgqioltv | 0.0.1 |
| 56 | gem | aiwbizkcowwn | 0.0.1 |
| 57 | gem | aiwdeynkjctd | 0.0.1 |
| 58 | gem | aiwdgltpirve | 0.0.1 |
| 59 | gem | aiwdsquzyhkx | 0.0.1 |
| 60 | gem | aiwetmoznbch | 0.0.1 |
| 61 | gem | aiweylvblufr | 0.0.1 |
| 62 | gem | aiwhuleyftdv | 0.0.1 |
| 63 | gem | aiwhwinedxvy | 0.0.1 |
| 64 | gem | aiwkctcwynld | 0.0.1 |
| 65 | gem | aiwkeklykhwg | 0.0.1 |
| 66 | gem | aiwmfjmnbojn | 0.0.1 |
| 67 | gem | aiwmirzlwefr | 0.0.1 |
| 68 | gem | aiwmvzkccpyk | 0.0.1 |
| 69 | gem | aiwmzwjrmune | 0.0.1 |
| 70 | gem | aiwnuagneyjc | 0.0.1 |
| 71 | gem | aiwohccxjmdp | 0.0.1 |
| 72 | gem | aiwontczmeyp | 0.0.1 |
| 73 | gem | aiwpdjthcxyq | 0.0.1 |
| 74 | gem | aiwqiejrvspo | 0.0.1 |
| 75 | gem | aiwqongzivyd | 0.0.1 |
| 76 | gem | aiwrfyltgujs | 0.0.1 |
| 77 | gem | aiwrhogwqhox | 0.0.1 |
| 78 | gem | aiwsamimxhff | 0.0.1 |
| 79 | gem | aiwsjuzltawo | 0.0.1 |
| 80 | gem | aiwsqjjbeqyn | 0.0.1 |
| 81 | gem | aiwsqvuxobcb | 0.0.1 |
| 82 | gem | aiwsynezxalw | 0.0.1 |
| 83 | gem | aiwuanpmjkub | 0.0.1 |
| 84 | gem | aiwuzbgljyfk | 0.0.1 |
| 85 | gem | aiwvntrvdltc | 0.0.1 |
| 86 | gem | aizgpanecvku | 0.0.1 |
| 87 | gem | aizjmmmwsreb | 0.0.1 |
| 88 | gem | aizkfewpqkax | 0.0.1 |
| 89 | gem | aiznuarbhczr | 0.0.1 |
| 90 | gem | aizporoccrvn | 0.0.1 |
| 91 | gem | aiztuyancttc | 0.0.1 |
| 92 | gem | aizwhvgtkghn | 0.0.1 |
| 93 | gem | aizzofpvqxbu | 0.0.1 |
| 94 | gem | amd6002 | 0.0.1 |
| 95 | gem | amdapi208076 | 0.0.1 |
| 96 | gem | amdapi30894 | 0.0.1 |
| 97 | gem | amdapi577106 | 0.0.1 |
| 98 | gem | amdapi811316 | 0.0.1 |
| 99 | gem | amdmore280320 | 0.0.1 |
| 100 | gem | amdmore690849 | 0.0.1 |
Sources
- New packages identified in GemStuffer ‘OpenAI Swarm’ malicious RubyGems campaign, JFrog Security Research, September 15, 2026
- RubyHack, Spencer Kitts, Thomas Larsen and Sydney Von Arx, September 11, 2026
- OpenAI Agents Linked to RubyGems Campaign That Gained RCE on RubyDoc Servers, The Hacker News
- rubygems
- ai-agent-security
- supply-chain
- openai
- malware
Author
Vignesh Naikoti
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering
Why Does an npm Math Library Need an Encrypted Loader?
A math solver leads to encrypted code in three npm packages. Follow the loader, shared artifacts, source differences, and the evidence that remains missing.
Deep-Live-Cam Supply Chain Attack: Technical Analysis
A malicious dependency in Deep-Live-Cam loads a clipboard hijacker. We trace the installation trigger, Telegraph delivery, and Windows and macOS persistence.
The Agentic IDE Extension Blind Spot
Cursor can install most of the same extensions you had in Visual Studio Code, but not the same versions. Its Import VS Code Configuration step sends only the extension name, never the version. It...
Introducing SafeDep Threat Intel
SafeDep Threat Intel gives SOC and cyber defense teams the malicious package intelligence behind SafeDep's platform, to query or to push into the tools they already run.
Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.