|
| 1 | +require 'json' |
| 2 | +require 'versionomy' |
| 3 | + |
| 4 | +module CC |
| 5 | + module Engine |
| 6 | + class BundlerAudit |
| 7 | + def initialize(directory: , io: , engine_config: ) |
| 8 | + @directory = directory |
| 9 | + @engine_config = engine_config |
| 10 | + @io = io |
| 11 | + end |
| 12 | + |
| 13 | + def run |
| 14 | + Dir.chdir(@directory) |
| 15 | + raw_output = `bundle-audit` |
| 16 | + raw_issues = raw_output.split(/\n\n/).select { |chunk| |
| 17 | + chunk =~ /^Name: / |
| 18 | + } |
| 19 | + @gemfile_lock_lines = File.read( |
| 20 | + File.join(@directory, 'Gemfile.lock') |
| 21 | + ).lines |
| 22 | + raw_issues.each do |raw_issue| |
| 23 | + issue = issue_from_raw(raw_issue) |
| 24 | + @io.print("#{issue.to_json}\0") |
| 25 | + end |
| 26 | + end |
| 27 | + |
| 28 | + private |
| 29 | + |
| 30 | + def issue_from_raw(raw_issue) |
| 31 | + raw_issue_hash = {} |
| 32 | + raw_issue.lines.each do |l| |
| 33 | + l =~ /^([^:]+): (.+)\n?/ |
| 34 | + raw_issue_hash[$1] = $2 |
| 35 | + end |
| 36 | + line_number = nil |
| 37 | + @gemfile_lock_lines.each_with_index do |l, i| |
| 38 | + if l =~ /^\s*#{raw_issue_hash['Name']} \([\d.]+\)/ |
| 39 | + line_number = i + 1 |
| 40 | + end |
| 41 | + end |
| 42 | + { |
| 43 | + type: 'Issue', |
| 44 | + check_name: "Insecure Dependency", |
| 45 | + description: raw_issue_hash['Title'], |
| 46 | + categories: ['Security'], |
| 47 | + remediation_points: remediation_points( |
| 48 | + raw_issue_hash['Version'], raw_issue_hash['Solution'] |
| 49 | + ), |
| 50 | + location: { |
| 51 | + path: 'Gemfile.lock', |
| 52 | + lines: { |
| 53 | + begin: line_number, |
| 54 | + end: line_number |
| 55 | + } |
| 56 | + } |
| 57 | + } |
| 58 | + end |
| 59 | + |
| 60 | + def remediation_points(current_version, raw_solution) |
| 61 | + if raw_solution =~ /^upgrade to (.*)/ |
| 62 | + raw_upgrades = $1.scan(/\d+\.\d+\.\d+/) |
| 63 | + current_version = Versionomy.parse(current_version) |
| 64 | + result = 5_000_000_000 |
| 65 | + raw_upgrades.each do |raw_upgrade| |
| 66 | + upgrade_version = Versionomy.parse(raw_upgrade) |
| 67 | + if upgrade_version > current_version |
| 68 | + points_this_upgrade = nil |
| 69 | + if current_version.major == upgrade_version.major |
| 70 | + if current_version.minor == upgrade_version.minor |
| 71 | + points_this_upgrade = 500_000 # patch upgrade |
| 72 | + else |
| 73 | + points_this_upgrade = 5_000_000 # minor upgrade |
| 74 | + end |
| 75 | + else |
| 76 | + points_this_upgrade = 50_000_000 # major upgrade |
| 77 | + end |
| 78 | + result = points_this_upgrade if points_this_upgrade < result |
| 79 | + end |
| 80 | + end |
| 81 | + result |
| 82 | + else |
| 83 | + 500_000_000 # No upgrade of gem possible |
| 84 | + end |
| 85 | + end |
| 86 | + end |
| 87 | + end |
| 88 | +end |
| 89 | + |
0 commit comments