Safeguard
Supply Chain Attacks

How malicious Gemfile.lock entries redirect Ruby installs to attacker servers

A single unreviewed remote: line in Gemfile.lock can silently reroute a bundle install — here's how Ruby lockfile injection works and how to stop it.

Safeguard Research Team
Research
6 min read

Every bundle install trusts a file almost nobody reads line by line: Gemfile.lock. Unlike Gemfile, which a human writes and a reviewer scans in a pull request, the lockfile is machine-generated — it records the exact resolved version of every gem plus a remote: line naming the source it came from. That second field is the attack surface. If an attacker can slip a modified remote: URL or a re-pinned version into a lockfile — through a compromised CI job, a malicious pull request, or a dependency-update bot with too much trust — Bundler will fetch and execute that gem's install-time code without the Gemfile itself ever changing. This isn't hypothetical: CVE-2013-0334 documented exactly this class of ambiguity in Bundler versions before 1.7.0, where a Gemfile declaring multiple sources couldn't guarantee which one actually supplied a given gem, letting an identically named malicious package silently substitute for the real one. Bundler 1.7.0 shipped a fix on August 14, 2014, and Bundler's maintainers went further in 2021, changing resolution so a gem can only ever install from the source explicitly pinned to it. This post breaks down how lockfile-source tampering still slips through review today, and what actually catches it.

What makes Gemfile.lock a soft target compared to Gemfile?

Gemfile.lock is a soft target because it's large, dense, and treated as generated output rather than reviewed source. A modest Rails app can lock 100+ transitive gems, each with a remote:, a version, and a dependency tree — and GitHub's diff viewer collapses long unchanged hunks by default, so a single altered remote: URL buried mid-file is easy to scroll past. Bundler itself doesn't second-guess the lockfile on a normal bundle install: it trusts the recorded source and version unless told otherwise. Combine that with dependency-update automation — bots that open PRs bumping dozens of gems at once — and reviewers develop a habit of approving lockfile diffs without reading every remote: and version pin individually. That habit is precisely what an attacker who gains write access to a branch, or who can get a malicious PR merged, relies on: the human review gate that catches a suspicious line in Gemfile never gets applied with the same rigor to Gemfile.lock.

What did CVE-2013-0334 actually demonstrate?

CVE-2013-0334 demonstrated that Bundler could not reliably determine which of several declared sources actually supplied a given gem when a Gemfile listed more than one top-level source — for example both rubygems.org and gems.github.com. Because resolution wasn't strictly scoped per source, an attacker who published a gem with the same name on a source they controlled could have it substituted for the legitimate one during install, with the lockfile then recording that substitution as if it were normal. The fix landed in Bundler 1.7.0, released August 14, 2014, and RubyGems.org and the Bundler maintainers also worked to mirror gems.github.com content to close the underlying gap. The lesson generalizes past this specific CVE: any dependency manager that resolves a package name against multiple possible sources without hard-scoping which source is authoritative for which package creates a window for source substitution — the same pattern shows up in npm and Python dependency-confusion research from the same era.

How did Bundler's 2021 source-priority fix close the remaining gap?

Bundler's 2021 source-priority work closed the remaining ambiguity by making gem installation strictly source-scoped: a gem is only ever installed from the source explicitly declared for it, via a source block or a :source / :git option in the Gemfile, rather than letting any declared source satisfy any gem name. Before this fix, even after CVE-2013-0334's patch, a multi-source Gemfile retained a subtler risk, tracked as CVE-2020-36327 — resolution could still consider a gem available from a source the developer never intended for it. The Bundler team announced this directly as fixing their "source priorities" to prevent a public source from ever supplying a gem meant to come from a private or git-based source; that initial February 2021 release was briefly rolled back over an unrelated build issue, and the fix was fully in place by Bundler 2.2.18 that May. For teams running private gem servers alongside rubygems.org — a common setup for internal shared libraries — this fix is what prevents an identically named public gem from being silently substituted for an internal one, which is the Ruby-ecosystem analog of the dependency-confusion technique researcher Alex Birsan demonstrated against npm and PyPI package managers in 2021.

How does gem-name typosquatting compound lockfile risk?

Typosquatting compounds lockfile risk because gem install and require can execute arbitrary code, so a single accepted lockfile change doesn't just point to the wrong package — it can run attacker code the moment the next bundle install executes. Ruby gems commonly ship native extensions built via extconf.rb at install time, and any gem can also run code simply by being required, well before an application calls any of its public methods. Researchers analyzing RubyGems.org in 2020 identified more than 760 malicious, typosquatted gems that had collectively been downloaded over 95,000 times before takedown — packages relying on names close to popular libraries to get pulled in by a mistyped gem line or a copy-pasted Gemfile entry. A lockfile that resolves one of these typosquats, whether through a genuine typo or a deliberately crafted PR, carries the malicious version forward on every subsequent install until someone notices the mismatch between Gemfile and Gemfile.lock.

What actually detects and prevents lockfile injection?

Several concrete Bundler and CI controls address this directly. Running bundle install --deployment (or the newer --frozen flag) makes Bundler refuse to install if Gemfile.lock doesn't exactly match what Gemfile would resolve to — this is the single most effective control, because it turns a silently tampered lockfile into a hard CI failure instead of a quiet install. bundler-audit scans for insecure http:// or git:// sources in the lockfile that would allow man-in-the-middle substitution, on top of its primary job of flagging known-vulnerable gem versions from the Ruby Advisory Database. Beyond tooling, treat Gemfile.lock diffs as security-relevant in review: configure your diff viewer to expand full lockfile hunks rather than collapsing them, and specifically check that every remote: still points to rubygems.org or your approved private registry — not an unfamiliar URL introduced in the same PR as an unrelated feature.

How Safeguard helps

Safeguard's PR Guard runs AI-driven review directly against a pull request's diff and returns severity-ranked, file-and-line-scoped findings — including on lockfile changes that a human reviewer might otherwise skim past — and can post those findings straight back onto the GitHub PR so a suspicious remote: entry surfaces before merge, not after. On the composition side, Safeguard's SCA scanning and automatic SBOM generation give you a queryable record of exactly which source and version every gem resolved to across your fleet, so if a tampered lockfile or a typosquatted gem does slip through, you can identify every affected project in minutes instead of grepping repositories one at a time.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.