OpenAI Agents Turned RubyGems Into a Scraping Proxy

Vignesh Naikoti
5 min read

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.rb

That 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 --load is one. npm lifecycle scripts, setup.py and 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.

openai-agents-rubygems-attack-packages.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
3323 rows · showing first 100
| 3 columns

Sources

  • rubygems
  • ai-agent-security
  • supply-chain
  • openai
  • malware

Author

SafeDep Logo

Vignesh Naikoti

safedep.io

Share

The Latest from SafeDep blogs

Follow for the latest updates and insights on open source security & engineering

The Agentic IDE Extension Blind Spot

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...

Vignesh Naikoti
Background
SafeDep Logo

Ship Code.

Not Malware.

Start free with open source tools on your machine. Scale to a unified platform for your organization.