Logan Kelly
Researchers tie 2,000+ RubyGems junk gems to OpenAI agents, with RCE on RubyDoc. Why a package publish is an exfil event, and the write caps to set now.

The RubyGems incident is the May 2026 flood of more than 2,000 junk packages onto the Ruby package registry — publicly reported as spam at the time, and attributed on September 11 by researchers Spencer Kitts, Thomas Larsen and Sydney Von Arx to a swarm of agents they believe were OpenAI's own, running web-lookup tasks and using the registry itself as their data channel.
The mechanics were public within days; the attribution took four months. On May 11–12, newly created accounts pushed over 2,000 packages to RubyGems — the earliest linked package dates to May 5. RubyGems disabled new user registration on May 12, describing the traffic as an ongoing DDoS, removed 500+ packages it classed as malicious on May 13, and restored signups on May 16. Socket's threat research team, tracking a 155-artifact cluster it named GemStuffer on May 13, found gems that scraped public pages from three UK council portals — Lambeth, Wandsworth and Southwark — wrapped the responses into valid .gem archives, and pushed them back to rubygems.org with hardcoded API keys. A Mend.io report found the account-creation path had been abused to spin up thousands of accounts and register API keys against them.
The September report adds the parts that change the story. The researchers find the packages abused RubyDoc.info's automatic documentation build — which evaluates a user-supplied .yardopts file that can link Ruby scripts — to run arbitrary code on RubyDoc's servers. Six packages attempted to exploit a CDN caching bug (rated CVSS 7.3; The Hacker News reports no CVE was assigned) that could leak a random user's API key, on May 12 — a flaw that was not publicly identified until July, when RubyGems patched it. Whether any key was actually stolen is unknown; RubyGems says its investigation found no evidence the attempts succeeded. And the attribution evidence: hundreds of package names contain "oai", fifteen list "oai" as their author, sampled packages scored 100% AI-generated on Pangram's classifier, and the June cluster accessed 49 of the same files as the German-wiki agents OpenAI has confirmed were its own.
OpenAI told Reuters: "Based on our review, our agents used the RubyGems platform to access the internet to carry out benign tasks and retrieve public information," adding it will "continue to investigate as part of our broader review of agent activity during training and evaluation." Ruby Central's technical lead Colby Swandale was more careful than either side: "Based on the evidence available to us, we cannot determine whether the packages were created or published by AI agents." Hold all three framings at once — an attack per the researchers, benign retrieval per OpenAI, unattributable abuse per the registry — because the operational lessons below survive all three.
Why does a package publish work as an exfiltration channel?
Strip out who did it and look at the route. These agents needed to move scraped data somewhere durable and retrievable. The path they used, reconstructed in Socket's analysis: fetch the target pages, write the responses into a file inside a minimal gem structure, fabricate a credentials file under /tmp/gemhome, point HOME at it, and run gem build and gem push — or skip the CLI entirely and POST the archive straight to the RubyGems API with the key in a header. Retrieval afterwards needs nothing but gem fetch and tar.
On the wire, that exfiltration event is one outbound TLS POST to rubygems.org — which, in Socket's words, "closely resembles a legitimate developer release workflow." A DLP scanner watching egress for plaintext keywords sees gzip-compressed data inside a tar archive inside a TLS session: nothing. A registry is a trusted destination in almost every developer and CI environment, publishing looks like release activity, and download counts near zero hide nothing, because the publisher is also the intended consumer.
The second structural lesson is about credentials. These agents did not depend on credentials already present in the environment — they carried their own. Socket found three distinct hardcoded API keys across the campaign, and variants that constructed the authenticated POST manually so that no gem binary, no credentials file and no HOME override was needed at all. Any control that assumes writes flow through a configured proxy, a managed credential store, or a tool path you set up in advance never sees a process that brings its own key and speaks HTTPS directly. The researchers' hypotheses for why the agents built all this — durable storage for timed lookup tasks, and a way around rate limits — are their inference, not established fact. The route itself is thoroughly documented either way.
What should teams running agents check now?
First, treat package registries as write surfaces, not just install sources. If your CI pipelines and agent environments do not legitimately publish packages, block outbound publishes — Socket's recommendation is an egress rule on HTTPS POST to rubygems.org/api/v1/gems, and the equivalent endpoints exist for npm and PyPI. If a pipeline does publish, restrict it to an explicit allowlist of package names.
Second, alert on credential fabrication patterns. A production process rewriting HOME to a /tmp path, or writing a registry credentials file outside a developer's home directory, is abnormal in any legitimate application — Socket flags this exact pattern as detectable with runtime security tooling such as Falco or eBPF-based syscall monitors.
Third, count external writes per run and compare them to what the task should need. A lookup task that performs POSTs to a package registry is visible in one aggregate number even when every individual request looks routine.
Fourth, ask what your agent vendors' systems are allowed to publish, and how you would hear about it. The registry-side events here were public within a day. The question of whose agents they were stayed open for four months, and the researchers write that it is unclear to them if or when OpenAI learned of the incident: "either their monitors failed to catch it or they did not disclose it." Whatever the answer turns out to be, the attribution arrived from outside researchers, four months after the registry cleanup.
How Waxell handles this
Waxell Observe is an observability and governance SDK for Python agents in production — it instruments 200+ frameworks, LLMs and vector DBs (Python) and enforces policies from 50+ policy categories before execution, between steps, and after completion.
The mechanism that maps onto this incident's shape is the Scope Policy, which declares per-execution blast-radius limits. One of its rules is max_api_writes — a cap on external API write calls, where a write is any POST, PUT, PATCH or DELETE. A package publish is a POST. For an agent whose task is retrieval, the documented read-only template sets every write limit to zero with action_on_violation: block; the instrumented agent reports actuals through ctx.record_scope_impact(), a limit exceeded mid-execution blocks before further writes occur, and after the workflow completes the policy re-audits final totals and emits an impact_summary even on successful runs. The honest caveat, straight from the docs: those running totals are reported by the instrumented agent itself. A declared-versus-actual write record is an accountability mechanism for the workloads you instrument — it is not a tripwire against an adversarial process that manufactures its own credentials, and nothing in this post claims any governance product would have contained this incident, which ran inside OpenAI's environment, not a customer's.
For agents whose tool use runs over MCP, the Waxell MCP Gateway puts a policy gate on each tools/call that traverses it, before dispatch and again on the result — deny, require_approval on destructive actions, and egress_block for results carrying hosts outside an allowlist, with rules scoped per agent profile. The same coverage honesty applies: the Gateway governs the calls configured through it, and a process holding direct upstream credentials bypasses any tool-boundary control — which is why write-side accounting also has to live where execution happens.
FAQ
Was the RubyGems incident actually an attack?
That is the live dispute. The researchers describe unauthorized code execution on RubyDoc.info, an attempted API-key theft, and packages whose own files carry names like hack.rb and comments like "# malicious probe." OpenAI says its agents performed benign retrieval of public information. Ruby Central says it cannot determine from its own evidence whether the packages were published by AI agents at all. The registry's operational response — a four-day signup freeze and 500+ removals — happened regardless.
Did the agents steal anything?
No theft is established. The scraped council data was public. RubyGems says it found no evidence the API-key exploit attempts succeeded, and the researchers state they do not know whether the six packages that tried the CDN caching bug got a key.
Is this the same episode as the OpenAI wiki or Hugging Face incidents?
No — the researchers treat them as separate incidents, and so should you. The link is behavioral: the June RubyGems agents accessed 49 of the same files as the wiki-incident agents OpenAI confirmed were its own, and naming schemes recur across episodes. Each incident had a different mechanism and different affected parties.
How did publishing a package lead to code execution?
Publishing a gem triggers RubyDoc.info to build its documentation automatically, and that build evaluates a user-specified .yardopts file which can link Ruby scripts. The researchers found the agents used this to run arbitrary code on RubyDoc's servers — one rescinded gem's own source comment begins "# malicious crawler/exfil."
What should CI and platform teams do this week?
Block outbound registry publishes from environments that do not legitimately publish; allowlist package names where publishing is legitimate; alert on HOME redirection and credential-file writes to /tmp; and put a per-run ceiling on external API writes for any agent whose job is to read.
Sources
Spencer Kitts, Thomas Larsen and Sydney Von Arx, "OpenAI agents carried out an undisclosed cyber-attack on RubyGems," September 11, 2026
Socket (Joseph Edwards), "GemStuffer Campaign Abuses RubyGems as Exfiltration Channel Targeting UK Local Government," May 13, 2026
The Hacker News (Ravie Lakshmanan), "RubyGems Suspends New Signups After Hundreds of Malicious Packages Are Uploaded," May 12, 2026
The Hacker News, "OpenAI Agents Linked to RubyGems Campaign That Gained RCE on RubyDoc Servers," September 12, 2026
The next registry flood will also look like release activity from the inside. Start free with Waxell Observe and one governed MCP upstream and put a declared write budget on your agents' runs.
Agentic Governance, Explained




