Skip to content

Gem .bundle/config reader keeps a trailing # comment in the value, so a commented BUNDLE_PATH is missed, agent apply patches the system copy and vex attests not_affected while Bundler loads the unpatched project copy #951

Description

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

Summary

Bundler's config loader drops a trailing # comment from a .bundle/config value. socket-patch's line scraper (bundle_config_setting* / parse_bundle_config_path → unquote_bundle_config_value) keeps it. Take BUNDLE_PATH: .gems # project-local gems. Bundler installs into and loads from .gems/ruby/<abi>/. The crawler resolves the root to a directory literally named .gems # project-local gems, which doesn't exist, and falls back to the gem env homes.

Agent apply therefore patches the system copy of the gem and reports success. vex then attests not_affected while bundle exec loads the unpatched project copy. Nothing warns.

Bundler 4.1 writes .bundle/config values unquoted (BUNDLE_PATH: .gems), so a hand-added trailing comment produces exactly this shape. Bundler 2.4 through 4.1 all honour the commented value (verified below).

Impact

Repro (Linux, Ruby 3.3.6; no API needed)

mkdir app && cd app && mkdir .bundle
printf 'source "https://rubygems.org"\ngem "colorize", "0.8.1"\n' > Gemfile
printf -- '---\nBUNDLE_PATH: .gems # project-local gems\n' > .bundle/config
gem install colorize -v 0.8.1          # a system copy also exists (common on dev machines / images)
bundle install                          # installs into ./.gems/ruby/3.3.0
# Hand-written agent manifest: one patch to package/lib/colorize.rb (appends a marker line),
# .socket/manifest.json + .socket/blobs/<afterHash> (git-sha256 hashes).
socket-patch apply --offline            # exit 0, "applied"
socket-patch vex --product pkg:gem/app@1.0.0 --output vex.json   # exit 0, not_affected
grep -c SOCKET_PATCHED_MARKER .gems/ruby/3.3.0/gems/colorize-0.8.1/lib/colorize.rb   # 0  (loaded copy)
grep -c SOCKET_PATCHED_MARKER "$(gem env gemdir)/gems/colorize-0.8.1/lib/colorize.rb" # 1  (unused copy)
bundle exec ruby -e 'require "colorize"; puts $LOADED_FEATURES.grep(/colorize.rb$/)'   # → ./.gems/... (unpatched)

Bundler's own view: bundle config get path → ".gems", and Bundler::YAMLSerializer.load("---\nBUNDLE_PATH: .gems # c") → {"BUNDLE_PATH"=>".gems"} (strip_comment in bundler/yaml_serializer.rb, 2.5+; 2.4.22's loader gives the same result).

Expected vs actual

  • Expected: install-root discovery follows Bundler's settings (CLI_CONTRACT.md: the bundle path, cache path and gemfile are resolved "in Bundler::Settings priority", and .bundle/config is read like Bundler reads it). So apply patches .gems/ruby/3.3.0/gems/colorize-0.8.1, and vex attests only if that copy is patched.
  • Actual: the comment becomes part of the path. The project copy is never crawled, the gem env copy is patched instead, and vex exits 0 with not_affected.

OS × version

OS Ruby Bundler BUNDLE_PATH: .gems # c Control BUNDLE_PATH: .gems
Linux 3.3.6 4.1.0.beta1 (written by bundle config set --local path .gems, comment appended) fail (loaded copy unpatched, VEX not_affected) —
Linux 3.3.6 4.0.22 fail (×2) pass
Linux 3.3.6 2.6.9 fail pass
Linux 3.3.6 2.4.22 fail pass
macOS / Windows — — untested (the parsing is OS-independent) —

The v4.0.0 release (npm @socketsecurity/socket-patch-linux-x64-gnu@4.0.0) behaves the same, so this isn't a regression. Tested on main 9c43dfc.

Suspect code

No probe runs: the logic is OS-independent string parsing.

No activity

Activity on this issue will appear here.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions