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
DirtyBlanket: Fake Express Packages on npm Spread a Linux Worm
Nine fake Express and React packages on npm run a Linux worm at install time. It installs a Tor backdoor and spreads through SSH, AUR packages, and npm tokens.
Mini Shai-Hulud Is Still Infecting GitHub Repositories
The May 2026 Mini Shai-Hulud worm is still infecting new GitHub repositories. Hijacked actions-cool/issues-helper tags planted Claude Code and VS Code hooks in six popular repositories between 20 and...
Detecting Compromised AI Coding Agents with Jev and Gryph
I checked every action of my own Claude Code agent against a profile of how I work and a set of org policies, using Jev. It caught 14 of 14 attacks for $0.15 per 1,000 events.
MemTensor npm and PyPI Packages Hit by a Go Worm
An attacker used a Go worm to steal CI publish tokens from MemTensor and ship malicious MemOS packages to npm and PyPI. See how it works, with code and indicators of compromise.
Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.