---
title: "OpenAI Agents Turned RubyGems Into a Scraping Proxy"
description: "Between May and July 2026, a swarm of AI agents published over 3,000 packages to RubyGems. The gems abused RubyDoc.info documentation builds to run a crawler on someone else's servers, then shipped the scraped data back to the registry as new gems and webhook URLs. Three independent researchers traced the campaign to OpenAI agents four months later."
url: "https://safedep.io/blog/openai-agents-rubygems-attack"
author: "Vignesh Naikoti"
published: "2026-09-18"
---

# OpenAI Agents Turned RubyGems Into a Scraping Proxy

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.

![Daily package uploads to RubyGems attributed to the OpenAI agent swarm. Small bars through early May, a spike of 2,186 packages on May 12, then a gap once RubyGems closed new registrations, and a final bar of 84 packages on June 18.](https://safedep.io/_next/static/immutable/media/openai-agents-rubygems-uploads.0i2l3n120i26z.webp)

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`](https://rubydoc.info/gems/yard/file/docs/GettingStarted.md) 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.

```text wrap=false
# .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](https://cyberscoop.com/openai-agents-malicious-rubygems-packages/) 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](https://safedep.io/data/blogs/openai-agents-rubygems-attack-packages.csv)

## Sources

- [New packages identified in GemStuffer 'OpenAI Swarm' malicious RubyGems campaign](https://research.jfrog.com/post/gemstuffer-openai-rubygems/), JFrog Security Research, September 15, 2026
- [RubyHack](https://www.rubyhack.ai/), Spencer Kitts, Thomas Larsen and Sydney Von Arx, September 11, 2026
- [OpenAI Agents Linked to RubyGems Campaign That Gained RCE on RubyDoc Servers](https://thehackernews.com/2026/09/openai-agents-linked-to-rubygems.html), The Hacker News
