From 066e8cbef71336b2f57e0f0f25cb44993c9e1a7b Mon Sep 17 00:00:00 2001 From: Postmodern Date: Tue, 8 Feb 2022 14:07:29 -0800 Subject: [PATCH 1/6] Added gemspec metadata. --- gemspec.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gemspec.yml b/gemspec.yml index f6bc66a7..f9978e33 100644 --- a/gemspec.yml +++ b/gemspec.yml @@ -6,6 +6,12 @@ authors: Postmodern email: postmodern.mod3@gmail.com homepage: https://github.com/rubysec/bundler-audit#readme +metadata: + documentation_uri: https://rubydoc.info/gems/bundler-audit + source_code_uri: https://github.com/rubysec/bundler-audit.rb + bug_tracker_uri: https://github.com/rubysec/bundler-audit.rb/issues + changelog_uri: https://github.com/rubysec/bundler-audit.rb/blob/master/ChangeLog.md + required_ruby_version: ">= 2.0.0" required_rubygems_version: ">= 1.8.0" From 0e9ffdfc63261647334b4f0c5d005ca79e81e07d Mon Sep 17 00:00:00 2001 From: Postmodern Date: Tue, 8 Feb 2022 14:06:26 -0800 Subject: [PATCH 2/6] Version bump to 0.10.0. --- lib/bundler/audit/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/audit/version.rb b/lib/bundler/audit/version.rb index 8cb45c17..dc05bc09 100644 --- a/lib/bundler/audit/version.rb +++ b/lib/bundler/audit/version.rb @@ -18,6 +18,6 @@ module Bundler module Audit # bundler-audit version - VERSION = '0.9.0.1' + VERSION = '0.10.0' end end From 35c9f2fed20f08722bd5332f75911996f80780f0 Mon Sep 17 00:00:00 2001 From: Postmodern Date: Tue, 8 Feb 2022 14:07:54 -0800 Subject: [PATCH 3/6] Opt into rubygems.org MFA requirement. --- gemspec.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/gemspec.yml b/gemspec.yml index f9978e33..cb8e8524 100644 --- a/gemspec.yml +++ b/gemspec.yml @@ -11,6 +11,7 @@ metadata: source_code_uri: https://github.com/rubysec/bundler-audit.rb bug_tracker_uri: https://github.com/rubysec/bundler-audit.rb/issues changelog_uri: https://github.com/rubysec/bundler-audit.rb/blob/master/ChangeLog.md + rubygems_mfa_required: 'true' required_ruby_version: ">= 2.0.0" required_rubygems_version: ">= 1.8.0" From 46b249a2320fc34877455a01022aa85a49a8b1b6 Mon Sep 17 00:00:00 2001 From: Postmodern Date: Tue, 8 Feb 2022 14:05:32 -0800 Subject: [PATCH 4/6] Add support for detecting `gems.locked` (closes #325, #110). --- lib/bundler/audit/scanner.rb | 45 +++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/lib/bundler/audit/scanner.rb b/lib/bundler/audit/scanner.rb index 731a93ee..410cd6f3 100644 --- a/lib/bundler/audit/scanner.rb +++ b/lib/bundler/audit/scanner.rb @@ -60,7 +60,7 @@ class Scanner # @param [String] root # The path to the project root. # - # @param [String] gemfile_lock + # @param [String] lock_file # Alternative name for the `Gemfile.lock` file. # # @param [Database] database @@ -70,20 +70,27 @@ class Scanner # The file name of the bundler-audit config file. # # @raise [Bundler::GemfileLockNotFound] - # The `gemfile_lock` file could not be found within the `root` - # directory. + # The `lock_file` file could not be found within the `root` + # directory, or `Gemfiles.lock` and `gems.locked` both do not exist + # within `root`. # - def initialize(root=Dir.pwd,gemfile_lock='Gemfile.lock',database=Database.new,config_dot_file='.bundler-audit.yml') + def initialize(root=Dir.pwd,lock_file=nil,database=Database.new,config_dot_file='.bundler-audit.yml') @root = File.expand_path(root) @database = database - gemfile_lock_path = File.join(@root,gemfile_lock) + if lock_file + lock_file_path = File.join(@root,lock_file) - unless File.file?(gemfile_lock_path) - raise(Bundler::GemfileLockNotFound,"Could not find #{gemfile_lock.inspect} in #{@root.inspect}") + unless File.file?(lock_file_path) + raise(Bundler::GemfileLockNotFound,"Could not find #{lock_file.inspect} in #{@root.inspect}") + end + else + unless (lock_file_path = detect_lock_file) + raise(Bundler::GemfileLockNotFound,"neither Gemfile.lock nor gems.locked found in #{@root.inspect}") + end end - @lockfile = LockfileParser.new(File.read(gemfile_lock_path)) + @lockfile = LockfileParser.new(File.read(lock_file_path)) config_dot_file_full_path = File.absolute_path(config_dot_file, @root) @@ -235,6 +242,28 @@ def scan_specs(options={}) private + # Supported lock files. + LOCK_FILES = %w[Gemfile.lock gems.locked] + + # + # Detects `Gemfile.lock` or `gems.locked` files within {#root}. + # + # @return [String, nil] + # The path to `Gemfile.lock` or `gems.locked`. `nil` is returned + # if niether `Gemfile.lock` or `gems.locked` were found. + # + def detect_lock_file + LOCK_FILES.each do |name| + path = File.join(@root,name) + + if File.file?(path) + return path + end + end + + return nil + end + # # Determines whether a source is internal. # From f81f37ccafd15e41987f501573aae682c8e309ff Mon Sep 17 00:00:00 2001 From: Postmodern Date: Tue, 8 Feb 2022 15:28:31 -0800 Subject: [PATCH 5/6] Fix spelling mistake. --- lib/bundler/audit/scanner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/audit/scanner.rb b/lib/bundler/audit/scanner.rb index 410cd6f3..ea61a1c3 100644 --- a/lib/bundler/audit/scanner.rb +++ b/lib/bundler/audit/scanner.rb @@ -250,7 +250,7 @@ def scan_specs(options={}) # # @return [String, nil] # The path to `Gemfile.lock` or `gems.locked`. `nil` is returned - # if niether `Gemfile.lock` or `gems.locked` were found. + # if neither `Gemfile.lock` or `gems.locked` were found. # def detect_lock_file LOCK_FILES.each do |name| From 6a35436206a169548a8d088a197ad9a00a5b46a1 Mon Sep 17 00:00:00 2001 From: kallal79 Date: Tue, 31 Mar 2026 06:53:31 +0530 Subject: [PATCH 6/6] Add support for gems.rb and gems.locked files (fixes #325) - Add automatic detection of gems.locked files alongside Gemfile.lock - Maintain priority: Gemfile.lock takes precedence over gems.locked - Add helpful error messages when gems.rb exists but gems.locked is missing - Update CLI help text to mention gems.rb/gems.locked support - Add comprehensive test coverage for new functionality - Update README.md to document alternative file naming support - Enhance scanner with detect_gemfile() method for better error handling - Update all documentation to reflect gems.rb/gems.locked support This implements the feature requested in issue #325 to support alternative gemfile names (gems.rb/gems.locked) introduced in Bundler 2.0 while maintaining full backward compatibility. --- README.md | 15 ++-- lib/bundler/audit/cli.rb | 6 +- lib/bundler/audit/scanner.rb | 40 +++++++-- spec/cli_spec.rb | 68 +++++++++++++++- spec/gems_locked_spec.rb | 152 +++++++++++++++++++++++++++++++++++ 5 files changed, 267 insertions(+), 14 deletions(-) create mode 100644 spec/gems_locked_spec.rb diff --git a/README.md b/README.md index 4aa3f7a6..eeb66ff2 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,16 @@ Patch-level verification for [bundler]. ## Features -* Checks for vulnerable versions of gems in `Gemfile.lock`. +* Checks for vulnerable versions of gems in `Gemfile.lock` or `gems.locked`. * Checks for insecure gem sources (`http://` and `git://`). * Allows ignoring certain advisories that have been manually worked around. * Prints advisory information. * Does not require a network connection. +* Supports both traditional (`Gemfile`/`Gemfile.lock`) and modern (`gems.rb`/`gems.locked`) file naming conventions. ## Synopsis -Audit a project's `Gemfile.lock`: +Audit a project's `Gemfile.lock` or `gems.locked`: $ bundle-audit Name: actionpack @@ -109,11 +110,11 @@ Update the [ruby-advisory-db] that `bundle audit` uses: create mode 100644 gems/wicked/OSVDB-98270.yml ruby-advisory-db: 64 advisories -Update the [ruby-advisory-db] and check `Gemfile.lock` (useful for CI runs): +Update the [ruby-advisory-db] and check `Gemfile.lock` or `gems.locked` (useful for CI runs): $ bundle-audit check --update -Checking the `Gemfile.lock` without updating the [ruby-advisory-db]: +Checking the `Gemfile.lock` or `gems.locked` without updating the [ruby-advisory-db]: $ bundle-audit check --no-update @@ -121,10 +122,14 @@ Ignore specific advisories: $ bundle-audit check --ignore OSVDB-108664 -Checking a custom `Gemfile.lock` file: +Checking a custom lock file: $ bundle-audit check --gemfile Gemfile.custom.lock +Or with gems.locked: + + $ bundle-audit check --gemfile gems.locked + Output the audit's results in JSON: $ bundle-audit check --format json diff --git a/lib/bundler/audit/cli.rb b/lib/bundler/audit/cli.rb index 5f496f11..76caeafe 100644 --- a/lib/bundler/audit/cli.rb +++ b/lib/bundler/audit/cli.rb @@ -33,7 +33,7 @@ class CLI < ::Thor default_task :check map '--version' => :version - desc 'check [DIR]', 'Checks the Gemfile.lock for insecure dependencies' + desc 'check [DIR]', 'Checks the Gemfile.lock/gems.locked for insecure dependencies' method_option :quiet, type: :boolean, aliases: '-q' method_option :verbose, type: :boolean, aliases: '-v' method_option :ignore, type: :array, aliases: '-i' @@ -42,8 +42,8 @@ class CLI < ::Thor default: Database::USER_PATH method_option :format, type: :string, default: 'text', aliases: '-F' method_option :config, type: :string, aliases: '-c', default: '.bundler-audit.yml' - method_option :gemfile_lock, type: :string, aliases: '-G', - default: 'Gemfile.lock' + method_option :gemfile_lock, type: :string, aliases: '-G', + desc: 'Path to the lock file (Gemfile.lock or gems.locked)' method_option :output, type: :string, aliases: '-o' def check(dir=Dir.pwd) diff --git a/lib/bundler/audit/scanner.rb b/lib/bundler/audit/scanner.rb index ea61a1c3..0435fda5 100644 --- a/lib/bundler/audit/scanner.rb +++ b/lib/bundler/audit/scanner.rb @@ -32,7 +32,7 @@ module Bundler module Audit # - # Scans a `Gemfile.lock` for security issues. + # Scans a `Gemfile.lock` or `gems.locked` for security issues. # class Scanner @@ -44,7 +44,7 @@ class Scanner # Project root directory attr_reader :root - # The parsed `Gemfile.lock` from the project + # The parsed `Gemfile.lock` or `gems.locked` from the project # # @return [Bundler::LockfileParser] attr_reader :lockfile @@ -61,7 +61,7 @@ class Scanner # The path to the project root. # # @param [String] lock_file - # Alternative name for the `Gemfile.lock` file. + # Alternative name for the lock file (Gemfile.lock or gems.locked). # # @param [Database] database # The database to scan against. @@ -71,7 +71,7 @@ class Scanner # # @raise [Bundler::GemfileLockNotFound] # The `lock_file` file could not be found within the `root` - # directory, or `Gemfiles.lock` and `gems.locked` both do not exist + # directory, or neither `Gemfile.lock` nor `gems.locked` exist # within `root`. # def initialize(root=Dir.pwd,lock_file=nil,database=Database.new,config_dot_file='.bundler-audit.yml') @@ -86,7 +86,15 @@ def initialize(root=Dir.pwd,lock_file=nil,database=Database.new,config_dot_file= end else unless (lock_file_path = detect_lock_file) - raise(Bundler::GemfileLockNotFound,"neither Gemfile.lock nor gems.locked found in #{@root.inspect}") + # Provide more helpful error message + gemfile_path = detect_gemfile + if gemfile_path + gemfile_name = File.basename(gemfile_path) + expected_lock_file = gemfile_name == 'gems.rb' ? 'gems.locked' : 'Gemfile.lock' + raise(Bundler::GemfileLockNotFound,"#{gemfile_name} found but #{expected_lock_file} is missing. Run 'bundle install' to generate it.") + else + raise(Bundler::GemfileLockNotFound,"neither Gemfile.lock nor gems.locked found in #{@root.inspect}") + end end end @@ -245,6 +253,9 @@ def scan_specs(options={}) # Supported lock files. LOCK_FILES = %w[Gemfile.lock gems.locked] + # Supported gemfiles. + GEMFILES = %w[Gemfile gems.rb] + # # Detects `Gemfile.lock` or `gems.locked` files within {#root}. # @@ -264,6 +275,25 @@ def detect_lock_file return nil end + # + # Detects `Gemfile` or `gems.rb` files within {#root}. + # + # @return [String, nil] + # The path to `Gemfile` or `gems.rb`. `nil` is returned + # if neither `Gemfile` or `gems.rb` were found. + # + def detect_gemfile + GEMFILES.each do |name| + path = File.join(@root,name) + + if File.file?(path) + return path + end + end + + return nil + end + # # Determines whether a source is internal. # diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb index c240b02d..be81617e 100644 --- a/spec/cli_spec.rb +++ b/spec/cli_spec.rb @@ -175,4 +175,70 @@ end end end -end + + describe "#check with gems.locked support" do + let(:fixtures_dir) { File.join(__dir__, 'fixtures') } + let(:bundle_dir) { File.join(fixtures_dir, 'gems_locked_cli_test') } + + before do + FileUtils.mkdir_p(bundle_dir) + File.write(File.join(bundle_dir, 'gems.locked'), <<~LOCKFILE) + GEM + remote: https://rubygems.org/ + specs: + thor (1.1.0) + + PLATFORMS + ruby + + DEPENDENCIES + thor + + BUNDLED WITH + 2.2.33 + LOCKFILE + end + + after do + FileUtils.rm_rf(bundle_dir) + end + + it "should auto-detect gems.locked file" do + cli = described_class.new + expect { + cli.check(bundle_dir) + }.to output(/thor/).to_stdout + end + + it "should work with explicit --gemfile-lock gems.locked" do + cli = described_class.new([], { gemfile_lock: 'gems.locked' }) + expect { + cli.check(bundle_dir) + }.to output(/thor/).to_stdout + end + end + + describe "#check with gems.rb support" do + let(:fixtures_dir) { File.join(__dir__, 'fixtures') } + let(:gems_rb_dir) { File.join(fixtures_dir, 'gems_rb_test') } + + before do + FileUtils.mkdir_p(gems_rb_dir) + File.write(File.join(gems_rb_dir, 'gems.rb'), <<~GEMFILE) + source 'https://rubygems.org' + gem 'thor' + GEMFILE + end + + after do + FileUtils.rm_rf(gems_rb_dir) + end + + it "should provide helpful error when gems.locked is missing" do + cli = described_class.new + expect { + cli.check(gems_rb_dir) + }.to output(/gems.rb found but gems.locked is missing/).to_stderr + end + end +end \ No newline at end of file diff --git a/spec/gems_locked_spec.rb b/spec/gems_locked_spec.rb new file mode 100644 index 00000000..485af436 --- /dev/null +++ b/spec/gems_locked_spec.rb @@ -0,0 +1,152 @@ +require 'spec_helper' +require 'bundler/audit/scanner' + +describe "gems.rb and gems.locked support" do + let(:fixtures_dir) { File.join(__dir__, 'fixtures') } + + describe Bundler::Audit::Scanner do + context "when only gems.locked exists" do + let(:bundle_dir) { File.join(fixtures_dir, 'gems_locked_only') } + + before do + FileUtils.mkdir_p(bundle_dir) + File.write(File.join(bundle_dir, 'gems.locked'), <<~LOCKFILE) + GEM + remote: https://rubygems.org/ + specs: + thor (1.1.0) + + PLATFORMS + ruby + + DEPENDENCIES + thor + + BUNDLED WITH + 2.2.33 + LOCKFILE + end + + after do + FileUtils.rm_rf(bundle_dir) + end + + it "should detect and use gems.locked file" do + scanner = described_class.new(bundle_dir) + expect(scanner.lockfile).to be_a(Bundler::LockfileParser) + expect(scanner.lockfile.specs.map(&:name)).to include('thor') + end + end + + context "when both Gemfile.lock and gems.locked exist" do + let(:bundle_dir) { File.join(fixtures_dir, 'both_lock_files') } + + before do + FileUtils.mkdir_p(bundle_dir) + File.write(File.join(bundle_dir, 'Gemfile.lock'), <<~LOCKFILE) + GEM + remote: https://rubygems.org/ + specs: + rake (13.0.6) + + PLATFORMS + ruby + + DEPENDENCIES + rake + + BUNDLED WITH + 2.2.33 + LOCKFILE + + File.write(File.join(bundle_dir, 'gems.locked'), <<~LOCKFILE) + GEM + remote: https://rubygems.org/ + specs: + thor (1.1.0) + + PLATFORMS + ruby + + DEPENDENCIES + thor + + BUNDLED WITH + 2.2.33 + LOCKFILE + end + + after do + FileUtils.rm_rf(bundle_dir) + end + + it "should prioritize Gemfile.lock over gems.locked" do + scanner = described_class.new(bundle_dir) + expect(scanner.lockfile.specs.map(&:name)).to include('rake') + expect(scanner.lockfile.specs.map(&:name)).not_to include('thor') + end + end + + context "when gems.rb exists but gems.locked is missing" do + let(:bundle_dir) { File.join(fixtures_dir, 'gems_rb_no_lock') } + + before do + FileUtils.mkdir_p(bundle_dir) + File.write(File.join(bundle_dir, 'gems.rb'), <<~GEMFILE) + source 'https://rubygems.org' + gem 'thor' + GEMFILE + end + + after do + FileUtils.rm_rf(bundle_dir) + end + + it "should provide helpful error message" do + expect { + described_class.new(bundle_dir) + }.to raise_error(Bundler::GemfileLockNotFound, /gems.rb found but gems.locked is missing/) + end + end + + context "when Gemfile exists but Gemfile.lock is missing" do + let(:bundle_dir) { File.join(fixtures_dir, 'gemfile_no_lock') } + + before do + FileUtils.mkdir_p(bundle_dir) + File.write(File.join(bundle_dir, 'Gemfile'), <<~GEMFILE) + source 'https://rubygems.org' + gem 'thor' + GEMFILE + end + + after do + FileUtils.rm_rf(bundle_dir) + end + + it "should provide helpful error message" do + expect { + described_class.new(bundle_dir) + }.to raise_error(Bundler::GemfileLockNotFound, /Gemfile found but Gemfile.lock is missing/) + end + end + + context "when neither gemfile nor lock files exist" do + let(:bundle_dir) { File.join(fixtures_dir, 'empty_dir') } + + before do + FileUtils.mkdir_p(bundle_dir) + end + + after do + FileUtils.rm_rf(bundle_dir) + end + + it "should provide standard error message" do + expect { + described_class.new(bundle_dir) + }.to raise_error(Bundler::GemfileLockNotFound, /neither Gemfile.lock nor gems.locked found/) + end + end + end +end \ No newline at end of file