[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
Bundler accepts a Gemfile that declares the same gem more than once with the same requirement. It only prints "Your Gemfile lists the gem … more than once". A common example is a gem listed in both group :development and group :test. scan --mode hosted / get --mode hosted rewrite only the first matching gem line into the per-dep source "<patch registry>" do gem "x", "<ver>" end block, and leave the other declaration as it was. The two declarations then have different requirements (= 1.0.0 vs >= 0). Bundler refuses to parse the Gemfile, so every later bundle install (frozen or not) and every bundle exec exits 4.
The scan exits 0 with redirected: 1, no warning, and an in-run --vex that attests not_affected.
Vendored mode already detects this shape and fails closed with gemfile_declaration_not_editable: gem "x" is declared more than once in the Gemfile (crates/socket-patch-core/src/vendor/gem.rs:1371). Hosted mode has no equivalent check.
Impact
A project that commits the hosted rewrite can no longer install at all: CI breaks, the deploy breaks, and the VEX document from the same run claims the CVE is not exploitable. This is a different root cause from #482. #482 appends a second declaration when the first one isn't visible (eval_gemfile / a loop). Here both declarations are plain lines that the rewriter can see, but it edits only one of them.
Repro
This uses a hold-open copy of crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs (the redirect_scanned_project fixture with a CHECKSUMS lock: real gem build, mock upstream + patch registry + API on loopback), then a real Bundler:
cat > Gemfile <<'EOF'
source "$UPSTREAM"
group :development do
gem "vuln-gem"
end
group :test do
gem "vuln-gem"
end
EOF
bundle config set --local path vendor/bundle
bundle lock # warns "lists the gem vuln-gem (>= 0) more than once", writes the lock
socket-patch scan --mode hosted --json --yes --api-url $API --org test-org --api-token fake \
--vex out.vex.json --vex-product pkg:gem/app@1.0.0
# exit 0, redirect.redirected = 1, warnings = [], VEX: GHSA-redirect-gem-real not_affected
# fresh checkout (Gemfile, Gemfile.lock, .socket, .bundle):
bundle install # exit 4
The Gemfile after the scan:
source "http://127.0.0.1:33109/upstream"
group :development do
source "http://127.0.0.1:33109/patch-registry/gem/<token>/<uuid>/" do
gem "vuln-gem", "1.0.0"
end
end
group :test do
gem "vuln-gem"
end
The output of bundle install:
[!] There was an error parsing `Gemfile`: You cannot specify the same gem twice with different version requirements.
You specified: vuln-gem (= 1.0.0) and vuln-gem (>= 0). Bundler cannot continue.
# from …/Gemfile:10
> gem "vuln-gem"
A top-level declaration plus one inside group :test do … end gives the same result (the top-level line is rewritten and the group line is left alone, so install exits 4).
Expected vs actual
- Expected: the hosted rewrite must leave a Gemfile that Bundler installs. docs/ecosystems.md describes hosted gem wiring as a "per-dep
source block", and CLI_CONTRACT.md requires that an envelope never attests a CVE that its own wiring doesn't fix. For a gem declared more than once, hosted mode should either rewrite every declaration consistently or fail closed before writing anything, as vendored mode does (gemfile_declaration_not_editable). In the fail-closed case it should also leave the purl out of the in-run --vex set.
- Actual: only the first declaration is rewritten; the scan exits 0 with no warning;
--vex attests not_affected; every install exits 4.
Matrix (Linux, Ruby 3.3.6)
| Bundler |
Lock |
Two group blocks |
Top-level + group |
Vendored, same shape |
| 4.0.17 |
CHECKSUMS |
fail (install exit 4, ×2) |
fail (install exit 4) |
pass (refused, nothing written) |
| 2.6.9 |
CHECKSUMS (--add-checksums) |
fail (install exit 4) |
untested |
untested |
| 2.4.22 |
no CHECKSUMS |
fail (install exit 4) |
untested |
untested |
The bug isn't OS-specific: it comes from the Gemfile text rewriter, and macOS / Windows weren't probed. It also reproduces with the released v4.0.0 (npm @socketsecurity/socket-patch@4.0.0) and with the head of draft PR #532, so it isn't a recent regression.
Suspect code
crates/socket-patch-core/src/patch/redirect/mod.rs:5007: if let Some(m) = gem_line_re.captures(gf) takes only the first match of the gem-line regex, so later declarations of the same name are never examined.
- Compare with
crates/socket-patch-core/src/vendor/gem.rs:1371 (if found.len() > 1 { return Err("… declared more than once …") }).
No probe branch was needed (the bug doesn't depend on the OS).
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
Bundler accepts a Gemfile that declares the same gem more than once with the same requirement. It only prints "Your Gemfile lists the gem … more than once". A common example is a gem listed in both
group :developmentandgroup :test.scan --mode hosted/get --mode hostedrewrite only the first matchinggemline into the per-depsource "<patch registry>" do gem "x", "<ver>" endblock, and leave the other declaration as it was. The two declarations then have different requirements (= 1.0.0vs>= 0). Bundler refuses to parse the Gemfile, so every laterbundle install(frozen or not) and everybundle execexits 4.The scan exits 0 with
redirected: 1, no warning, and an in-run--vexthat attestsnot_affected.Vendored mode already detects this shape and fails closed with
gemfile_declaration_not_editable: gem "x" is declared more than once in the Gemfile(crates/socket-patch-core/src/vendor/gem.rs:1371). Hosted mode has no equivalent check.Impact
A project that commits the hosted rewrite can no longer install at all: CI breaks, the deploy breaks, and the VEX document from the same run claims the CVE is not exploitable. This is a different root cause from #482. #482 appends a second declaration when the first one isn't visible (
eval_gemfile/ a loop). Here both declarations are plain lines that the rewriter can see, but it edits only one of them.Repro
This uses a hold-open copy of
crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs(theredirect_scanned_projectfixture with a CHECKSUMS lock: realgem build, mock upstream + patch registry + API on loopback), then a real Bundler:The Gemfile after the scan:
The output of
bundle install:A top-level declaration plus one inside
group :test do … endgives the same result (the top-level line is rewritten and the group line is left alone, so install exits 4).Expected vs actual
sourceblock", and CLI_CONTRACT.md requires that an envelope never attests a CVE that its own wiring doesn't fix. For a gem declared more than once, hosted mode should either rewrite every declaration consistently or fail closed before writing anything, as vendored mode does (gemfile_declaration_not_editable). In the fail-closed case it should also leave the purl out of the in-run--vexset.--vexattestsnot_affected; every install exits 4.Matrix (Linux, Ruby 3.3.6)
groupblocksgroup--add-checksums)The bug isn't OS-specific: it comes from the Gemfile text rewriter, and macOS / Windows weren't probed. It also reproduces with the released v4.0.0 (npm
@socketsecurity/socket-patch@4.0.0) and with the head of draft PR #532, so it isn't a recent regression.Suspect code
crates/socket-patch-core/src/patch/redirect/mod.rs:5007:if let Some(m) = gem_line_re.captures(gf)takes only the first match of the gem-line regex, so later declarations of the same name are never examined.crates/socket-patch-core/src/vendor/gem.rs:1371(if found.len() > 1 { return Err("… declared more than once …") }).No probe branch was needed (the bug doesn't depend on the OS).