Skip to content

Hosted gem scan ignores a Gemfile lockfile "custom.lock" DSL call (Bundler 4), so it pins the unused Gemfile.lock, leaves the real lock untouched, and every frozen install fails #1477

Closed

Description

[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).

Summary

Bundler 4 has four ways to name a custom lockfile: BUNDLE_LOCKFILE in the env, the app config or the global config, plus a lockfile "<path>" call in the Gemfile itself. #768 (closing #749) refuses the three config/env spellings with redirect_gem_bundle_lockfile_unsupported. Nothing reads the Gemfile DSL spelling. With a Gemfile that starts with lockfile "custom.lock", scan --mode hosted:

  • rewrites the Gemfile (adds the patch-registry source … do block),
  • pins the leftover Gemfile.lock, which Bundler no longer reads,
  • leaves custom.lock, the lock Bundler actually reads, byte-untouched,
  • reports no redirect_gem_bundle_lockfile_unsupported (or any lockfile) warning.

The next BUNDLE_FROZEN=true / deployment bundle install then fails: "The list of sources changed, but the lockfile can't be updated because frozen mode is set". The same project installs frozen without trouble before the scan.

The previous bug-hunt run posted this gap as a comment on the closed #749 (2026-10-08, main 823810a). It still reproduces on main 15c2c83, so this issue tracks it on its own.

Impact

CI and production installs (frozen or deployment) of a Bundler 4 project that uses the lockfile DSL break after a hosted scan. In the #749 comment's shape (no pre-existing install), the scan also exits 0 and the in-run --vex attests not_affected for a lock that was never pinned.

Repro

I used a scratch copy of crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs, gem_hosted_bundler4_custom_lockfile_redirects_nothing. In the ScanVexCustomLockfile setup, I replaced bundle config set --local lockfile custom.lock with prepending lockfile "custom.lock"\n to the Gemfile. custom.lock is a copy of the pristine lock, and Gemfile.lock is left behind, exactly as in the existing test. I replaced the assertions with prints, then ran:

SOCKET_PATCH_BUNDLER_E2E_REQUIRED=1 cargo test -p socket-patch-cli --test <scratch copy> -- \
  gem_hosted_bundler4_custom_lockfile_redirects_nothing --include-ignored --nocapture
  • Pristine project with the DSL line: BUNDLE_FROZEN=true bundle install exits 0.
  • scan --mode hosted --json --vex … produces summary.applied: 1. Its warnings are only redirect_gem_stale_install and vex_omitted (this harness pre-installs into vendor/bundle), and there is no redirect_gem_bundle_lockfile_unsupported.
  • Gemfile and Gemfile.lock are rewritten (Gemfile.lock now has the patch-registry GEM section, vuln-gem (= 1.0.0)! and the patched sha256). custom.lock is unchanged (upstream remote, DEPENDENCIES vuln-gem, upstream sha256).
  • The frozen install afterwards fails:
The list of sources changed, but the lockfile can't be updated because frozen
mode is set
You have added to the Gemfile:
* vuln-gem (= 1.0.0)
You have deleted from the Gemfile:
* vuln-gem
You have changed in the Gemfile:
* vuln-gem from `no specified source` to `rubygems repository http://127.0.0.1:…/patch-registry/gem/…/`

Expected vs actual

Matrix (Linux, Ruby 3.3.6)

Bundler main Lockfile spelling Refused custom.lock pinned Frozen install after scan
4.0.22 15c2c83 Gemfile DSL lockfile "custom.lock" no (×2) no fails
4.0.18 15c2c83 Gemfile DSL no no fails
4.0.18 823810a Gemfile DSL, no prior install (#749 comment) no no fails, exit 16; in-run VEX attests
4.0.x 15c2c83 .bundle/config / env / global BUNDLE_LOCKFILE yes (existing test) n/a passes
< 4.0 — n/a (the DSL method doesn't exist) — — —

macOS and Windows weren't probed. The logic is OS-independent.

Suspect code

LoadedManifest::with_lockfile (crates/socket-patch-core/src/formats/gem/manifest.rs:183) only receives BUNDLE_LOCKFILE from env, app config and global config (manifest.rs:262 reads the config key). No gem reader parses a top-level lockfile call in the Gemfile / gems.rb, so the hosted engine falls through to the default Gemfile.lock pair (LoadedManifest::pair). The vendored path (crates/socket-patch-core/src/vendor/gem.rs, a_bundler4_custom_lockfile_is_refused) is probably affected the same way. I didn't verify it this run. lockfile false (Bundler writes no lock) is a related, untested shape.

Activity

  1. mikolalysenko commented on Oct 13, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triage: priority:p1 (Bundler). Not a duplicate: #749/#768 covered only the BUNDLE_LOCKFILE env/app-config/global-config spellings. Confirmed on main (15c2c83): LoadedManifest::with_lockfile (crates/socket-patch-core/src/formats/gem/manifest.rs) is fed only from env and the two config files, and no gem reader parses a top-level lockfile call in the Gemfile / gems.rb. No open PR covers it, and it does not share a root cause with the other open gem issues (#985 is about gemspec dev deps, #1476 about the source-block scanner).


    Generated by Claude Code

  2. mikolalysenko commented on Oct 13, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue (shared root cause: the gem lock resolver never reads Bundler 4's Gemfile lockfile DSL call). Branch: agent/fix-gem-lockfile-dsl. Claim-ID: 2026-10-13T11:21:28Z-9dc049


    Generated by Claude Code

  3. mikolalysenko commented on Oct 13, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1479


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentpm:bundlerBundler (RubyGems)priority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions