lockfile_audit 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b293cff106a2d7cdf8214e512965e2d0155ac740d7d26eec9840c8d9634a795
4
- data.tar.gz: f02bf84ccdd70a87cf2d7bada32ffcba4b343ff553cbc016a7c152df5e7b9f81
3
+ metadata.gz: f54f64dd70f25a217ccd3c29bd0ea985f420f3d947074a57b033f711950d08df
4
+ data.tar.gz: bd3edfc6cb9c823955aa7e81d895f36f1207a95475c4dd4b5cd4c05ec29ff496
5
5
  SHA512:
6
- metadata.gz: 69158c852ae5786a6181f4df7f871accf34a3d42868a153a818186398bd188aad0f3d5ba7ebdfb1049d1c10692f0e57861f294f3ae6d335d0c5df4c9f88d7772
7
- data.tar.gz: 3491f0634af4b8caea832f88e7eadfc5b71cf4e4db0dca98ae57a60e27a8c011125b7c42f66e9b2f1f98a8afa782279f4e249d561920e3f0697931f6a9bd6e3b
6
+ metadata.gz: 46de93997759fd386a2f11bf5aa6520c640299b4faaf8d0f4a7e82cfebfc52617f5577b25bffa9ffece99bd589108905d3dcea5ed10112a9134a193fbf344e30
7
+ data.tar.gz: 0342efc962e99aab09b78aa848028eab33bafbba188f148227607ac90ab015b370ab5b1029711e2de01cd256286e311100aeeb2bffbe9f7ae1d61ecc5b785339
data/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-10-04
11
+
12
+ ### Changed
13
+ - `VulnerabilityCollector` now scans the lockfile in-process with `Bundler::Audit::Scanner` instead of spawning the `bundle-audit` executable in a child Ruby process. The subprocess approach failed with `Bundler::GemfileError ... is already registered` on machines that have more than one Bundler version installed, because two copies of Bundler were loaded into the child process.
14
+
15
+ - `bundler-audit` must now be in the host application's bundle (add `gem "bundler-audit"` to your Gemfile). Having it installed globally or on `PATH` is no longer enough. Passing audit_json: still works without bundler-audit.
16
+
17
+ - A failing scan now raises `Errors::BundlerAuditFailed` with the underlying error and a hint to run `bundle-audit update`. `Errors::BundlerAuditNotInstalled` is raised only when `bundler-audit` cannot be loaded.
18
+
19
+ ### Removed
20
+ - The subprocess code path and its environment scrubbing (`BUNDLER_ENV_KEYS`, `Open3`, `RbConfig.ruby` / `Gem.bin_path` lookup).
21
+
22
+ ### Notes
23
+ - Insecure-source findings from `bundler-audit` (for example an `http://` gem source) are not included in `audit.vulnerabilities`; only advisories for unpatched gems are reported.
24
+
25
+ ## [0.1.2] - 2026-10-03
26
+
27
+ ### Changed
28
+
29
+ - Source moved to the `betterwebsites/ror_gems` monorepo. Gemspec metadata
30
+ links updated. No code changes.
10
31
  ## [0.1.1] - 2026-09-26
11
32
 
12
33
  ### Fixed
data/README.md CHANGED
@@ -10,6 +10,8 @@ feed into a dashboard, or archive for compliance.
10
10
 
11
11
  ## Installation
12
12
 
13
+ `gem "bundler-audit"` must be in the app's Gemfile
14
+
13
15
  Add it to your Gemfile:
14
16
 
15
17
  gem "lockfile_audit"
@@ -116,16 +118,12 @@ All errors inherit from `LockfileAudit::Errors::Error`.
116
118
 
117
119
  ## Development
118
120
 
121
+ cd lockfile_audit
119
122
  bundle install
120
- bundle exec rspec
121
- bundle exec standardrb
122
-
123
- To release a new version:
123
+ bundle exec rake
124
124
 
125
- 1. Bump `lib/lockfile_audit/version.rb`.
126
- 2. Update `CHANGELOG.md`.
127
- 3. `gem build lockfile_audit.gemspec`
128
- 4. `gem push lockfile_audit-<version>.gem`
125
+ To release, see the "Releasing a gem" section of the
126
+ [repo README](../README.md).
129
127
 
130
128
  ## License
131
129
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LockfileAudit
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -1,53 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
- require "open3"
5
4
 
6
5
  module LockfileAudit
7
6
  # Collects vulnerability findings from bundler-audit.
8
7
  #
9
- # Either accepts pre-computed JSON (via +audit_json:+) or shells out to
10
- # `bundle-audit check --format json` when no JSON is supplied.
8
+ # Either accepts pre-computed JSON (via +audit_json:+) or, when no JSON is
9
+ # supplied, scans the lockfile in-process using Bundler::Audit::Scanner.
11
10
  #
12
- # The normalizer tolerates both known shapes of bundler-audit output:
11
+ # Scanning in-process (instead of spawning the `bundle-audit` executable)
12
+ # avoids loading a second copy of Bundler in a child Ruby process, which
13
+ # fails on machines that have more than one Bundler version installed.
14
+ #
15
+ # Requirements for the in-process scan:
16
+ # - the `bundler-audit` gem must be in the host application's bundle
17
+ # - the advisory database must exist (run `bundle-audit update`)
18
+ #
19
+ # For +audit_json:+ the normalizer tolerates both known shapes of
20
+ # bundler-audit JSON output:
13
21
  # - a bare Array of findings (newer versions)
14
22
  # - a Hash with a "results" key holding the Array (older versions)
15
- #
16
- # When invoking the CLI, the collector runs with a scrubbed environment so
17
- # the parent process's Bundler state (BUNDLE_*, RUBYOPT, etc.) does not leak
18
- # into the subprocess. It also invokes bundler-audit using the *current*
19
- # Ruby (RbConfig.ruby) and the exact installed binary path (Gem.bin_path),
20
- # so the subprocess uses the same Ruby as the host application.
21
23
  class VulnerabilityCollector
22
- # Environment variables that must not leak into the bundler-audit
23
- # subprocess. These are set by Bundler when the parent process is a
24
- # bundled Ruby app, and their presence forces Bundler to re-initialize
25
- # inside the subprocess.
26
- BUNDLER_ENV_KEYS = %w[
27
- BUNDLE_GEMFILE
28
- BUNDLE_BIN_PATH
29
- BUNDLE_PATH
30
- BUNDLE_APP_CONFIG
31
- BUNDLE_FROZEN
32
- BUNDLE_WITHOUT
33
- BUNDLE_WITH
34
- BUNDLE_JOBS
35
- BUNDLE_RETRY
36
- BUNDLER_VERSION
37
- BUNDLER_ORIG_BUNDLE_BIN_PATH
38
- BUNDLER_ORIG_BUNDLE_GEMFILE
39
- BUNDLER_ORIG_BUNDLE_PATH
40
- BUNDLER_ORIG_MANPATH
41
- BUNDLER_ORIG_RUBYLIB
42
- BUNDLER_ORIG_RUBYOPT
43
- RUBYOPT
44
- RUBYLIB
45
- ].freeze
46
-
47
24
  # @param audit_json [String, nil] raw JSON from bundler-audit; if nil,
48
- # bundler-audit is invoked directly
49
- # @param lockfile_path [String, Pathname] path to Gemfile.lock; used to
50
- # derive the working directory for the CLI invocation
25
+ # the lockfile is scanned in-process
26
+ # @param lockfile_path [String, Pathname] path to Gemfile.lock
51
27
  def initialize(audit_json: nil, lockfile_path: "Gemfile.lock")
52
28
  @audit_json = audit_json
53
29
  @lockfile_path = lockfile_path
@@ -55,13 +31,14 @@ module LockfileAudit
55
31
 
56
32
  # @return [Array<Hash>] normalized findings, each with keys
57
33
  # `:gem`, `:version`, `:criticality`, `:description`
58
- # @raise [Errors::InvalidAuditJson] if the JSON cannot be parsed
59
- # @raise [Errors::BundlerAuditNotInstalled] if bundler-audit is missing
34
+ # @raise [Errors::InvalidAuditJson] if the supplied JSON cannot be parsed
35
+ # @raise [Errors::BundlerAuditNotInstalled] if bundler-audit cannot be loaded
36
+ # @raise [Errors::BundlerAuditFailed] if the in-process scan raises
60
37
  def call
61
- raw = @audit_json || run_bundler_audit
62
- return [] if raw.nil? || raw.strip.empty?
38
+ return scan_in_process if @audit_json.nil?
39
+ return [] if @audit_json.strip.empty?
63
40
 
64
- parsed = JSON.parse(raw)
41
+ parsed = JSON.parse(@audit_json)
65
42
  results = parsed.is_a?(Hash) ? parsed.fetch("results", []) : parsed
66
43
 
67
44
  Array(results).map { |finding| normalize(finding) }
@@ -71,62 +48,42 @@ module LockfileAudit
71
48
 
72
49
  private
73
50
 
74
- def run_bundler_audit
75
- stdout, stderr, status = invoke
76
- return stdout if usable?(stdout, status)
51
+ # Runs Bundler::Audit::Scanner against the lockfile and maps unpatched
52
+ # gem results onto the public schema. Insecure-source results (e.g. an
53
+ # http:// gem source) have no gem or advisory and are skipped.
54
+ def scan_in_process
55
+ require "bundler/audit/scanner"
77
56
 
78
- raise_not_installed(stderr)
79
- rescue Gem::Exception
80
- raise Errors::BundlerAuditNotInstalled,
81
- "bundler-audit is not installed for #{RbConfig.ruby} (#{RUBY_VERSION}). " \
82
- "Install it for this Ruby: gem install bundler-audit"
83
- rescue Errno::ENOENT
84
- raise_not_installed(nil)
85
- end
57
+ path = File.expand_path(@lockfile_path)
58
+ scanner = Bundler::Audit::Scanner.new(File.dirname(path), File.basename(path))
86
59
 
87
- def invoke
88
- Open3.capture3(
89
- clean_env,
90
- RbConfig.ruby, bundle_audit_path, "check", "--format", "json",
91
- chdir: File.dirname(File.expand_path(@lockfile_path))
92
- )
93
- end
60
+ results = []
61
+ scanner.scan { |result| results << result }
94
62
 
95
- def bundle_audit_path
96
- Gem.bin_path("bundler-audit", "bundle-audit")
97
- end
63
+ results.filter_map do |result|
64
+ next unless result.is_a?(Bundler::Audit::Results::UnpatchedGem)
98
65
 
99
- # Builds an environment hash that removes the inherited Bundler state.
100
- # Passing nil as the value to Open3 unsets the variable in the child.
101
- def clean_env
102
- BUNDLER_ENV_KEYS.each_with_object({}) do |key, acc|
103
- acc[key] = nil
66
+ advisory = result.advisory
67
+ {
68
+ gem: result.gem.name,
69
+ version: result.gem.version.to_s,
70
+ criticality: advisory.criticality&.to_s&.capitalize,
71
+ description: advisory.title
72
+ }
104
73
  end
74
+ rescue LoadError
75
+ raise Errors::BundlerAuditNotInstalled,
76
+ "bundler-audit is not available. " \
77
+ "Add `gem \"bundler-audit\"` to your Gemfile and run `bundle install`."
78
+ rescue Errors::Error
79
+ raise
80
+ rescue => e
81
+ raise Errors::BundlerAuditFailed,
82
+ "bundler-audit scan failed: #{e.class}: #{e.message}. " \
83
+ "Run `bundle-audit update` to fetch the advisory database."
105
84
  end
106
85
 
107
- # bundler-audit exits 1 when it finds vulnerabilities (that's success for
108
- # us), and 0 when the lockfile is clean. Any other exit code, or empty
109
- # stdout, means the invocation didn't produce usable output.
110
- def usable?(stdout, status)
111
- return false if stdout.nil? || stdout.strip.empty?
112
- return true if [0, 1].include?(status.exitstatus)
113
-
114
- false
115
- end
116
-
117
- # Raises BundlerAuditNotInstalled with an actionable message that
118
- # includes stderr when available.
119
- def raise_not_installed(stderr)
120
- detail = stderr.to_s.strip
121
- message = "bundler-audit could not be invoked. " \
122
- "Ensure the `bundler-audit` gem is installed for this Ruby " \
123
- "(#{RUBY_VERSION}) and run `bundle-audit update` to fetch the " \
124
- "advisory database."
125
- message += " stderr: #{detail}" unless detail.empty?
126
- raise Errors::BundlerAuditNotInstalled, message
127
- end
128
-
129
- # Maps a single bundler-audit finding onto the public schema.
86
+ # Maps a single bundler-audit JSON finding onto the public schema.
130
87
  #
131
88
  # bundler-audit has used slightly different key names across versions, so
132
89
  # we fall back to nested "advisory" keys when the top-level ones are absent.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lockfile_audit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - behnam1369
@@ -27,12 +27,13 @@ files:
27
27
  - lib/lockfile_audit/report.rb
28
28
  - lib/lockfile_audit/version.rb
29
29
  - lib/lockfile_audit/vulnerability_collector.rb
30
- homepage: https://github.com/Behnam1369/lockfile_audit
30
+ homepage: https://github.com/betterwebsites/ror_gems/tree/main/lockfile_audit
31
31
  licenses:
32
32
  - MIT
33
33
  metadata:
34
- homepage_uri: https://github.com/Behnam1369/lockfile_audit
35
- changelog_uri: https://github.com/Behnam1369/lockfile_audit/blob/main/CHANGELOG.md
34
+ homepage_uri: https://github.com/betterwebsites/ror_gems/tree/main/lockfile_audit
35
+ source_code_uri: https://github.com/betterwebsites/ror_gems/tree/main/lockfile_audit
36
+ changelog_uri: https://github.com/betterwebsites/ror_gems/blob/main/lockfile_audit/CHANGELOG.md
36
37
  rdoc_options: []
37
38
  require_paths:
38
39
  - lib