Module: Scryer::DependencyFixer
- Defined in:
- lib/scryer/dependency_fixer.rb
Overview
scryer fix --deps — the dependency-audit analog of the rule-based
scryer fix, and a separate small pipeline from it: a
DependencyAudit::Finding has no .line/.file/.rule_id (see its
Struct in dependency_audit.rb), so none of FixRunner/MechanicalFixer/
FixVerifier — all built around rewriting a single source line and
re-running one Ripper-based rule against it — apply here at all. This
module exists for the one kind of dependency finding that has an
unambiguous, mechanically-applicable fix: a gem with a published patched
version to upgrade to.
Only acts on kind: "vulnerable_dependency" findings that have at least
one published patched version — DependencyAudit already leaves
patched_versions empty when OSV has no fix yet, so there's nothing to
bump to; those, along with insecure_source / ruby_eol /
credentials_exposure findings (none of which "bundle update" can
address — a source URL, a Ruby interpreter version, and a gitignore rule
aren't gem versions), are always left for manual review.
Class Method Summary collapse
-
.apply(findings, root:, dry_run: false, runner: method(:run_bundle_update), recheck: method(:default_recheck), &on_result) ⇒ Object
Groups findings by gem_name (a gem can have more than one open advisory) and runs
bundle update GEM --conservativeonce per gem, then re-queries OSV.dev for that one gem to confirm the bump actually cleared every advisory it had —--conservativetries to avoid moving anything beyond what's needed, but a version constraint elsewhere in the Gemfile can still leave a gem on an old, still-vulnerable version. - .default_recheck(root, gem_name) ⇒ Object
- .run_bundle_update(gem_name, root) ⇒ Object
Class Method Details
.apply(findings, root:, dry_run: false, runner: method(:run_bundle_update), recheck: method(:default_recheck), &on_result) ⇒ Object
Groups findings by gem_name (a gem can have more than one open
advisory) and runs bundle update GEM --conservative once per gem,
then re-queries OSV.dev for that one gem to confirm the bump actually
cleared every advisory it had — --conservative tries to avoid moving
anything beyond what's needed, but a version constraint elsewhere in
the Gemfile can still leave a gem on an old, still-vulnerable version.
This re-check is what turns "the command exited 0" into "verified,"
the same verify-before-trusting discipline FixRunner/FixVerifier use
for rule-based fixes — a real command actually ran and actually
changed Gemfile.lock, so trusting its exit code alone isn't enough.
Returns [fixed, skipped] (plain arrays of DependencyAudit::Finding).
Yields (finding, status, error) to the given block as each gem is
resolved — status is :fixed, :would_fix (dry_run), :error (the bundle
command itself failed — error is its captured output), or :skipped
(no patched version published, or the re-check still shows it
vulnerable). runner and recheck are injectable for testing, so this
is exercisable without a real Bundler process or network call.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/scryer/dependency_fixer.rb', line 42 def apply(findings, root:, dry_run: false, runner: method(:run_bundle_update), recheck: method(:default_recheck), &on_result) fixed = [] skipped = [] actionable, unfixable = findings.select { |f| f.kind == "vulnerable_dependency" } .partition { |f| Array(f.patched_versions).any? } unfixable.each do |finding| skipped << finding on_result&.call(finding, :skipped, nil) end actionable.group_by(&:gem_name).each_value do |gem_findings| gem_name = gem_findings.first.gem_name if dry_run fixed.concat(gem_findings) gem_findings.each { |f| on_result&.call(f, :would_fix, nil) } next end success, output = runner.call(gem_name, root) if !success skipped.concat(gem_findings) gem_findings.each { |f| on_result&.call(f, :error, output) } next end if recheck.call(root, gem_name) skipped.concat(gem_findings) = "bundle update #{gem_name} --conservative ran, but #{gem_name} is still flagged " \ "(no resolvable fixed version given the current Gemfile constraints, or a " \ "different advisory applies to the version it landed on) — check manually." gem_findings.each { |f| on_result&.call(f, :skipped, ) } else fixed.concat(gem_findings) gem_findings.each { |f| on_result&.call(f, :fixed, nil) } end end [fixed, skipped] end |
.default_recheck(root, gem_name) ⇒ Object
92 93 94 |
# File 'lib/scryer/dependency_fixer.rb', line 92 def default_recheck(root, gem_name) DependencyAudit.vulnerable_gems(root).any? { |f| f.gem_name == gem_name } end |
.run_bundle_update(gem_name, root) ⇒ Object
87 88 89 90 |
# File 'lib/scryer/dependency_fixer.rb', line 87 def run_bundle_update(gem_name, root) stdout, stderr, status = Open3.capture3("bundle", "update", gem_name, "--conservative", chdir: root) [status.success?, status.success? ? stdout : "#{stdout}\n#{stderr}".strip] end |