Every package-registry incident ends with the same advice: pin your versions, review your lockfile, run an audit. That advice is correct. It also covers only the code you choose to install, and the most instructive Ruby incident of 2026 did not need anyone to install anything. Securing a Rails CI pipeline against supply chain attacks means finding every build step that runs dependency code and limiting what each one can reach.
In May 2026, RubyGems.org paused new account registrations while freshly created accounts pushed a flood of spam packages. On September 11, 2026, RubyGems published an update on the May campaign describing packages designed to use shared Ruby infrastructure to run code, pull publicly available web data, and publish that data back to rubygems.org. The same day, Nightingale Collective's report on the packages traced the path. RubyDoc.info builds documentation for published gems, and that build evaluates the gem's own .yardopts file, which can point at Ruby scripts. So a package shipped a script, requested a docs build, and ran its code on the documentation servers. Who was behind the campaign is contested, and the registry says it cannot determine whether AI agents built the packages. The mechanism is not contested.
That attack hit a public documentation service, not a private pipeline. The shape is what matters, because the same shape sits in most CI configurations we open. We build and remediate Rails applications, which means we inherit a lot of pipeline config written by people who left before we arrived.
A documentation build is a code execution environment
Generating documentation for a Ruby project runs code. The doc tool reads the project's config, loads Ruby, and runs what that config tells it to run. Teams treat the step as formatting, and because nobody thinks of it as execution, it quietly inherits the pipeline defaults: the same runner, the same environment variables, the same network access as the deploy job. Nobody would hand a linter production credentials on purpose. Plenty of pipelines do it anyway, because the secrets are declared once at the top of the file.
Doc builds are only the most visible case. Native extension compiles, install hooks, and test fixtures loaded from a gem all execute code the team did not write. The question to ask about each step is not whether it ships anything. It is what the step could reach if the code it processes were hostile.
The credential model changed underneath inherited pipelines
RubyGems revoked every legacy API key on July 23, 2026. A Rails pipeline that still publishes with a stored key now fails, but the owners and trusted publishers attached to its gems survive the revocation.
The revocation followed a July 2026 advisory about a CDN caching bug that could leak legacy RubyGems API keys by serving one user's key to another caller. The advisory spells out what those keys were: full gem-management permissions, across every gem the account owned, with no expiry. Separately, RubyGems reports that the May 2026 packages included code meant to obtain other users' API keys, and that it found no evidence those attempts succeeded.
On an inherited pipeline, the failure shows up as an HTTP 401 on the CI push, which is often the first time anyone learns the key was there. Revoking the key was necessary. It proves nothing about who else can publish your gems.
How to secure a Rails CI pipeline against dependency code
Treat every job that runs dependency code as an execution boundary, then give it only the secrets and network access it needs.
List every job that runs dependency code. Include doc generation, native extension builds, install hooks, and fixtures loaded from gems. Mark each one as an execution boundary: a step where code you did not write runs with that step's access.
Scope each secret to the job that uses it. A doc build needs no registry token and no cloud credential. Declare the secret on its job, not at the top of the file.
Publish through trusted publishing. The release workflow gets a short-lived token that can push one gem. A stored GEM_HOST_API_KEY is neither short-lived nor scoped.
Read the owners and trusted publishers on every gem you control. Do it on a quiet day, not after an alert. A name nobody recognizes is the whole finding.
Cut network access from build steps that do not need it. Fetch dependencies in one step. Build docs and extensions in a later step with no network. A registry-only allowlist is not enough, because the May packages sent their data out through the registry itself.
The lesson is older than the incident
Supply chain tooling concentrates on what you install, because installation is the part with a scanner pointed at it. The part without a scanner is the list of places where your pipeline runs someone else's code with your credentials in the environment.
Pinning a version controls which code arrives. It does not limit what the step that processes that code can do. Scoping the job does. That audit is a standard part of how we take over and remediate a Rails codebase, and it runs alongside our Ruby Marshal deserialization checks on the application itself. It usually finds at least one job holding more access than anyone meant to give it.

