From 49a745ef956bf0229f0cd0952fabdcff585fdc81 Mon Sep 17 00:00:00 2001 From: Maitray Shah Date: Wed, 17 Aug 2022 19:04:32 -0700 Subject: [PATCH 1/4] Init --- lib/salus/autofixers/yarn_audit_v1.rb | 26 +-- lib/salus/autofixers/yarn_audit_v2.rb | 247 ++++++++++++++++++++++++++ lib/salus/scanners/yarn_audit.rb | 9 + 3 files changed, 270 insertions(+), 12 deletions(-) create mode 100644 lib/salus/autofixers/yarn_audit_v2.rb diff --git a/lib/salus/autofixers/yarn_audit_v1.rb b/lib/salus/autofixers/yarn_audit_v1.rb index f9387d99..df8c2b84 100644 --- a/lib/salus/autofixers/yarn_audit_v1.rb +++ b/lib/salus/autofixers/yarn_audit_v1.rb @@ -14,10 +14,11 @@ def initialize(path_to_repo) def run_auto_fix(feed, path_to_repo, package_json, yarn_lock) fix_indirect_dependency(feed, yarn_lock, path_to_repo) fix_direct_dependency(feed, package_json, path_to_repo) - rescue StandardError => e - error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" - raise AutofixError, error_msg end + # rescue StandardError => e + # error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" + # raise AutofixError, error_msg + # end def fix_direct_dependency(feed, package_json, path_to_repo) packages = JSON.parse(package_json) @@ -132,18 +133,13 @@ def update_sub_parent_resolution(blocks, parts, parsed_yarn_lock) group_appends = blocks.group_by { |h| [h[:prev], h[:key]] } group_appends.each do |pair, patch| source = pair.first - target = if pair.last.starts_with? "@" - pair.last.split("@", 2).first - else - pair.last.split("@").first - end - + target = pair.last.reverse.split('@', 2).collect(&:reverse).reverse.first vulnerable_package_info = get_package_info(target) list_of_versions_available = vulnerable_package_info["data"]["versions"] version_to_update_to = Salus::SemanticVersion.select_upgrade_version( patch.first[:patch], list_of_versions_available ) - if !version_to_update_to.nil? + if !version_to_update_to.nil? update_version_string = "^" + version_to_update_to parts.each_with_index do |part, index| match = part.match(/(#{target} .*)/) @@ -156,8 +152,14 @@ def update_sub_parent_resolution(blocks, parts, parsed_yarn_lock) end end section = parsed_yarn_lock[source] - section["dependencies"][target] = update_version_string - parsed_yarn_lock[source] = section + if section.dig("dependencies", target) + section["dependencies"][target] = update_version_string + parsed_yarn_lock[source] = section + end + if section.dig("optionalDependencies", target) + section["optionalDependencies"][target] = update_version_string + parsed_yarn_lock[source] = section + end end end parts diff --git a/lib/salus/autofixers/yarn_audit_v2.rb b/lib/salus/autofixers/yarn_audit_v2.rb new file mode 100644 index 00000000..0b4aac69 --- /dev/null +++ b/lib/salus/autofixers/yarn_audit_v2.rb @@ -0,0 +1,247 @@ +require 'salus/yarn_formatter' +require 'salus/autofixers/base' + +module Salus::Autofixers + class YarnAuditV2 < Base + def initialize(path_to_repo) + @path_to_repo = path_to_repo + end + + # Auto Fix will try to attempt direct and indirect dependencies + # Direct dependencies are found in package.json + # Indirect dependencies are found in yarn.lock + # By default, it will skip major version bumps + def run_auto_fix(feed, path_to_repo, package_json, yarn_lock) + fix_indirect_dependency(feed, yarn_lock, path_to_repo) + fix_direct_dependency(feed, package_json, path_to_repo) + end + # rescue StandardError => e + # error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" + # raise AutofixError, error_msg + # end + + def fix_direct_dependency(feed, package_json, path_to_repo) + packages = JSON.parse(package_json) + feed.each do |vuln| + patch = vuln["target"] + resolves = vuln["resolves"] + package = vuln["module"] + resolves.each do |resolve| + if !patch.nil? && patch != "No patch available" && package == resolve["path"] + update_direct_dependency(package, patch, packages) + end + end + end + write_auto_fix_files(path_to_repo, 'package-autofixed.json', JSON.dump(packages)) + end + + def fix_indirect_dependency(feed, yarn_lock, path_to_repo) + parsed_yarn_lock = Salus::YarnLockfileFormatter.new(yarn_lock).format + subparent_to_package_mapping = [] + + feed.each do |vuln| + puts vuln + patch = vuln["target"] + resolves = vuln["resolves"] + package = vuln["module"] + resolves.each do |resolve| + if !patch.nil? && patch != "No patch available" && package != resolve["path"] + block = create_subparent_to_package_mapping(parsed_yarn_lock, resolve["path"]) + if block.key?(:key) + block[:patch] = patch + subparent_to_package_mapping.append(block) + end + end + end + end + # parts = yarn_lock.split(/^\n/) + # parts = update_package_definition(subparent_to_package_mapping, parts) + # parts = update_sub_parent_resolution(subparent_to_package_mapping, parts, parsed_yarn_lock) + # TODO: Run clean up task + # write_auto_fix_files(path_to_repo, 'yarn-autofixed.lock', parts.join("\n")) + end + + # In yarn.lock, we attempt to update yarn.lock entries for the package + def update_package_definition(blocks, parts) + blocks.uniq { |hash| hash.values_at(:prev, :key, :patch) } + group_updates = blocks.group_by { |h| [h[:prev], h[:key]] } + group_updates.each do |updates, versions| + updates = updates.last + vulnerable_package_info = get_package_info(updates) + list_of_versions_available = vulnerable_package_info["versions"] + version_to_update_to = Salus::SemanticVersion.select_upgrade_version( + versions.first[:patch], list_of_versions_available + ) + package_name = if updates.starts_with? "@" + updates.split("@", 2).first + else + updates.split("@").first + end + if !version_to_update_to.nil? + fixed_package_info = get_package_info(package_name, version_to_update_to) + unless fixed_package_info.nil? + updated_version = "version " + '"' + version_to_update_to + '"' + updated_resolved = "resolved " + '"' + fixed_package_info["dist"]["tarball"] \ + + "#" + fixed_package_info["dist"]["shasum"] + '"' + updated_integrity = "integrity " + fixed_package_info['dist']['integrity'] + updated_name = package_name + "@^" + version_to_update_to + parts.each_with_index do |part, index| + current_v = parts[index].match(/(version .*)/) + version_string = current_v.to_s.tr('"', "").tr("version ", "") + if part.include?(updates) && !is_major_bump( + version_string, version_to_update_to + ) + parts[index].sub!(updates, updated_name) + parts[index].sub!(/(version .*)/, updated_version) + parts[index].sub!(/(resolved .*)/, updated_resolved) + parts[index].sub!(/(integrity .*)/, updated_integrity) + end + end + end + end + end + parts + end + + def get_package_info(package, version = nil) + info = if version.nil? + run_shell("yarn npm info #{package} --json", chdir: File.expand_path(@path_to_repo)) + else + run_shell( + "yarn npm info #{package}@#{version} --json", + chdir: File.expand_path(@path_to_repo) + ) + end + JSON.parse(info.stdout) + rescue StandardError + nil + end + + def is_major_bump(current, updated) + current.gsub(/[^0-9.]/, "") + current_v = current.split('.').map(&:to_i) + updated.sub(/[^0-9.]/, "") + updated_v = updated.split('.').map(&:to_i) + return true if updated_v.first > current_v.first + + false + end + + # In yarn.lock, we attempt to resolve sub parent of the affected package to + # new updated package definition. + def update_sub_parent_resolution(blocks, parts, parsed_yarn_lock) + blocks.uniq { |hash| hash.values_at(:prev, :key, :patch) } + group_appends = blocks.group_by { |h| [h[:prev], h[:key]] } + group_appends.each do |pair, patch| + source = pair.first + target = if pair.last.starts_with? "@" + pair.last.split("@", 2).first + else + pair.last.split("@").first + end + + vulnerable_package_info = get_package_info(target) + list_of_versions_available = vulnerable_package_info["data"]["versions"] + version_to_update_to = Salus::SemanticVersion.select_upgrade_version( + patch.first[:patch], list_of_versions_available + ) + if !version_to_update_to.nil? + update_version_string = "^" + version_to_update_to + parts.each_with_index do |part, index| + match = part.match(/(#{target} .*)/) + if part.include?(source) && !match.nil? && + !is_major_bump(match.to_s, version_to_update_to) + match = part.match(/(#{target} .*)/) + replace = match.to_s.split(" ").first + ' "^' + version_to_update_to + '"' + part.sub!(/(#{target} .*)/, replace) + parts[index] = part + end + end + section = parsed_yarn_lock[source] + section["dependencies"][target] = update_version_string + parsed_yarn_lock[source] = section + end + end + parts + end + + def create_subparent_to_package_mapping(parsed_yarn_lock, path) + section = {} + packages = path.split(">") + packages.each_with_index do |package, index| + break if index == packages.length - 1 + + section = if index.zero? + puts find_section_by_name(parsed_yarn_lock, package, packages[index + 1]) + find_section_by_name(parsed_yarn_lock, package, packages[index + 1]) + else + find_section_by_name_and_version( + parsed_yarn_lock, + section[:key], + packages[index + 1] + ) + end + end + puts section + section + end + + def find_section_by_name(parsed_yarn_lock, name, next_package) + parsed_yarn_lock.each do |key, array| + if key.starts_with? "#{name}@" + %w[dependencies optionalDependencies].each do |section| + if array[section]&.[](next_package) + value = array.dig(section, next_package) + return { "prev": key, "key": "#{next_package}@npm:#{value}" } + end + end + end + end + {} + end + + def find_section_by_name_and_version(parsed_yarn_lock, name, next_package) + parsed_yarn_lock.each do |key, array| + if key == name + %w[dependencies peerDependencies].each do |section| + if array[section]&.[](next_package) + value = array.dig(section, next_package) + return { "prev": key, "key": "#{next_package}@npm:#{value}" } + end + end + end + end + {} + end + + def update_direct_dependency(package, patched_version_range, packages) + if patched_version_range.match(Salus::SemanticVersion::SEMVER_RANGE_REGEX).nil? + raise AutofixError, "Found unexpected: patched version range: #{patched_version_range}" + end + + vulnerable_package_info = get_package_info(package) + list_of_versions = vulnerable_package_info.dig("data", "versions") + + if list_of_versions.nil? + error_msg = "#yarn info command did not provide a list of available package versions" + raise AutofixError, error_msg + end + + patched_version = Salus::SemanticVersion.select_upgrade_version( + patched_version_range, + list_of_versions + ) + + if !patched_version.nil? + %w[dependencies resolutions devDependencies].each do |package_section| + if !packages.dig(package_section, package).nil? + current_version = packages[package_section][package] + if !is_major_bump(current_version, patched_version) + packages[package_section][package] = "^#{patched_version}" + end + end + end + end + end + end +end diff --git a/lib/salus/scanners/yarn_audit.rb b/lib/salus/scanners/yarn_audit.rb index 45290e6b..a2055207 100644 --- a/lib/salus/scanners/yarn_audit.rb +++ b/lib/salus/scanners/yarn_audit.rb @@ -2,6 +2,7 @@ require 'salus/scanners/node_audit' require 'salus/semver' require 'salus/autofixers/yarn_audit_v1' +require 'salus/autofixers/yarn_audit_v2' # Yarn Audit scanner integration. Flags known malicious or vulnerable # dependencies in javascript projects that are packaged with yarn. @@ -85,6 +86,14 @@ def handle_latest_yarn_audit end return report_success if vulns.empty? + v2_autofixer = Salus::Autofixers::YarnAuditV2.new(@repository.path_to_repo) + v2_autofixer.run_auto_fix( + data["actions"], + @repository.path_to_repo, + @repository.package_json, + @repository.yarn_lock + ) + vulns = combine_vulns(vulns) log(format_vulns(vulns)) report_stdout(vulns.to_json) From 97e2e4a607763e5ad8c30b5affaf22246333729b Mon Sep 17 00:00:00 2001 From: Maitray Shah Date: Wed, 17 Aug 2022 23:04:13 -0700 Subject: [PATCH 2/4] Merge --- lib/salus/{autofixers => auto_fix}/base.rb | 2 +- .../{autofixers => auto_fix}/yarn_audit_v1.rb | 11 +++++------ .../{autofixers => auto_fix}/yarn_audit_v2.rb | 14 ++++++-------- lib/salus/scanners/yarn_audit.rb | 8 ++++---- 4 files changed, 16 insertions(+), 19 deletions(-) rename lib/salus/{autofixers => auto_fix}/base.rb (97%) rename lib/salus/{autofixers => auto_fix}/yarn_audit_v1.rb (97%) rename lib/salus/{autofixers => auto_fix}/yarn_audit_v2.rb (96%) diff --git a/lib/salus/autofixers/base.rb b/lib/salus/auto_fix/base.rb similarity index 97% rename from lib/salus/autofixers/base.rb rename to lib/salus/auto_fix/base.rb index a24645af..9813e39b 100644 --- a/lib/salus/autofixers/base.rb +++ b/lib/salus/auto_fix/base.rb @@ -2,7 +2,7 @@ require 'salus/plugin_manager' require 'salus/shell_result' -module Salus::Autofixers +module Salus::Autofix class Base class AutofixError < StandardError; end diff --git a/lib/salus/autofixers/yarn_audit_v1.rb b/lib/salus/auto_fix/yarn_audit_v1.rb similarity index 97% rename from lib/salus/autofixers/yarn_audit_v1.rb rename to lib/salus/auto_fix/yarn_audit_v1.rb index df8c2b84..ce1ece5a 100644 --- a/lib/salus/autofixers/yarn_audit_v1.rb +++ b/lib/salus/auto_fix/yarn_audit_v1.rb @@ -1,7 +1,7 @@ require 'salus/yarn_formatter' -require 'salus/autofixers/base' +require 'salus/auto_fix/base' -module Salus::Autofixers +module Salus::Autofix class YarnAuditV1 < Base def initialize(path_to_repo) @path_to_repo = path_to_repo @@ -14,11 +14,10 @@ def initialize(path_to_repo) def run_auto_fix(feed, path_to_repo, package_json, yarn_lock) fix_indirect_dependency(feed, yarn_lock, path_to_repo) fix_direct_dependency(feed, package_json, path_to_repo) + rescue StandardError => e + error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" + raise AutofixError, error_msg end - # rescue StandardError => e - # error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" - # raise AutofixError, error_msg - # end def fix_direct_dependency(feed, package_json, path_to_repo) packages = JSON.parse(package_json) diff --git a/lib/salus/autofixers/yarn_audit_v2.rb b/lib/salus/auto_fix/yarn_audit_v2.rb similarity index 96% rename from lib/salus/autofixers/yarn_audit_v2.rb rename to lib/salus/auto_fix/yarn_audit_v2.rb index 0b4aac69..f3773daf 100644 --- a/lib/salus/autofixers/yarn_audit_v2.rb +++ b/lib/salus/auto_fix/yarn_audit_v2.rb @@ -1,7 +1,7 @@ require 'salus/yarn_formatter' -require 'salus/autofixers/base' +require 'salus/auto_fix/base' -module Salus::Autofixers +module Salus::Autofix class YarnAuditV2 < Base def initialize(path_to_repo) @path_to_repo = path_to_repo @@ -14,11 +14,10 @@ def initialize(path_to_repo) def run_auto_fix(feed, path_to_repo, package_json, yarn_lock) fix_indirect_dependency(feed, yarn_lock, path_to_repo) fix_direct_dependency(feed, package_json, path_to_repo) + rescue StandardError => e + error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" + raise AutofixError, error_msg end - # rescue StandardError => e - # error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" - # raise AutofixError, error_msg - # end def fix_direct_dependency(feed, package_json, path_to_repo) packages = JSON.parse(package_json) @@ -40,7 +39,6 @@ def fix_indirect_dependency(feed, yarn_lock, path_to_repo) subparent_to_package_mapping = [] feed.each do |vuln| - puts vuln patch = vuln["target"] resolves = vuln["resolves"] package = vuln["module"] @@ -223,7 +221,7 @@ def update_direct_dependency(package, patched_version_range, packages) list_of_versions = vulnerable_package_info.dig("data", "versions") if list_of_versions.nil? - error_msg = "#yarn info command did not provide a list of available package versions" + error_msg = "#yarn npm info command did not provide a list of available package versions" raise AutofixError, error_msg end diff --git a/lib/salus/scanners/yarn_audit.rb b/lib/salus/scanners/yarn_audit.rb index a2055207..4aae7069 100644 --- a/lib/salus/scanners/yarn_audit.rb +++ b/lib/salus/scanners/yarn_audit.rb @@ -1,8 +1,8 @@ require 'json' require 'salus/scanners/node_audit' require 'salus/semver' -require 'salus/autofixers/yarn_audit_v1' -require 'salus/autofixers/yarn_audit_v2' +require 'salus/auto_fix/yarn_audit_v1' +require 'salus/auto_fix/yarn_audit_v2' # Yarn Audit scanner integration. Flags known malicious or vulnerable # dependencies in javascript projects that are packaged with yarn. @@ -86,7 +86,7 @@ def handle_latest_yarn_audit end return report_success if vulns.empty? - v2_autofixer = Salus::Autofixers::YarnAuditV2.new(@repository.path_to_repo) + v2_autofixer = Salus::Autofix::YarnAuditV2.new(@repository.path_to_repo) v2_autofixer.run_auto_fix( data["actions"], @repository.path_to_repo, @@ -138,7 +138,7 @@ def handle_legacy_yarn_audit auto_fix = @config.fetch("auto_fix", false) if auto_fix - v1_autofixer = Salus::Autofixers::YarnAuditV1.new(@repository.path_to_repo) + v1_autofixer = Salus::Autofix::YarnAuditV1.new(@repository.path_to_repo) v1_autofixer.run_auto_fix( generate_fix_feed, @repository.path_to_repo, From fdaf80b96fca7e3ea02142c2c31fb11225ac68e2 Mon Sep 17 00:00:00 2001 From: Maitray Shah Date: Thu, 18 Aug 2022 09:11:45 -0700 Subject: [PATCH 3/4] Updates --- lib/salus/auto_fix/yarn_audit_v1.rb | 11 +++++++---- lib/salus/auto_fix/yarn_audit_v2.rb | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/salus/auto_fix/yarn_audit_v1.rb b/lib/salus/auto_fix/yarn_audit_v1.rb index 1128cc50..54e8dc4f 100644 --- a/lib/salus/auto_fix/yarn_audit_v1.rb +++ b/lib/salus/auto_fix/yarn_audit_v1.rb @@ -53,14 +53,14 @@ def fix_indirect_dependency(feed, yarn_lock, path_to_repo) end end parts = yarn_lock.split(/^\n/) - parts = update_package_definition(subparent_to_package_mapping, parts) + parts = update_package_definition(subparent_to_package_mapping, parts, parsed_yarn_lock) parts = update_sub_parent_resolution(subparent_to_package_mapping, parts, parsed_yarn_lock) # TODO: Run clean up task write_auto_fix_files(path_to_repo, 'yarn-autofixed.lock', parts.join("\n")) end # In yarn.lock, we attempt to update yarn.lock entries for the package - def update_package_definition(blocks, parts) + def update_package_definition(blocks, parts, parsed_yarn_lock) blocks.uniq { |hash| hash.values_at(:prev, :key, :patch) } group_updates = blocks.group_by { |h| [h[:prev], h[:key]] } group_updates.each do |updates, versions| @@ -75,17 +75,20 @@ def update_package_definition(blocks, parts) else updates.split("@").first end - if !version_to_update_to.nil? + # TODO: Check if updated_name is not already present in parsed yarn block + updated_name = package_name + "@^" + version_to_update_to + if !version_to_update_to.nil? && parsed_yarn_lock.key?(updated_name) fixed_package_info = get_package_info(package_name, version_to_update_to) unless fixed_package_info.nil? updated_version = "version " + '"' + version_to_update_to + '"' updated_resolved = "resolved " + '"' + fixed_package_info["data"]["dist"]["tarball"] \ + "#" + fixed_package_info["data"]["dist"]["shasum"] + '"' updated_integrity = "integrity " + fixed_package_info['data']['dist']['integrity'] - updated_name = package_name + "@^" + version_to_update_to + parts.each_with_index do |part, index| current_v = parts[index].match(/(version .*)/) version_string = current_v.to_s.tr('"', "").tr("version ", "") + if part.include?(updates) && !is_major_bump( version_string, version_to_update_to ) diff --git a/lib/salus/auto_fix/yarn_audit_v2.rb b/lib/salus/auto_fix/yarn_audit_v2.rb index f3773daf..6901b6da 100644 --- a/lib/salus/auto_fix/yarn_audit_v2.rb +++ b/lib/salus/auto_fix/yarn_audit_v2.rb @@ -14,10 +14,11 @@ def initialize(path_to_repo) def run_auto_fix(feed, path_to_repo, package_json, yarn_lock) fix_indirect_dependency(feed, yarn_lock, path_to_repo) fix_direct_dependency(feed, package_json, path_to_repo) - rescue StandardError => e - error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" - raise AutofixError, error_msg end + # rescue StandardError => e + # error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" + # raise AutofixError, error_msg + # end def fix_direct_dependency(feed, package_json, path_to_repo) packages = JSON.parse(package_json) @@ -45,6 +46,7 @@ def fix_indirect_dependency(feed, yarn_lock, path_to_repo) resolves.each do |resolve| if !patch.nil? && patch != "No patch available" && package != resolve["path"] block = create_subparent_to_package_mapping(parsed_yarn_lock, resolve["path"]) + puts block if block.key?(:key) block[:patch] = patch subparent_to_package_mapping.append(block) @@ -52,11 +54,11 @@ def fix_indirect_dependency(feed, yarn_lock, path_to_repo) end end end - # parts = yarn_lock.split(/^\n/) - # parts = update_package_definition(subparent_to_package_mapping, parts) - # parts = update_sub_parent_resolution(subparent_to_package_mapping, parts, parsed_yarn_lock) + parts = yarn_lock.split(/^\n/) + parts = update_package_definition(subparent_to_package_mapping, parts) + parts = update_sub_parent_resolution(subparent_to_package_mapping, parts, parsed_yarn_lock) # TODO: Run clean up task - # write_auto_fix_files(path_to_repo, 'yarn-autofixed.lock', parts.join("\n")) + write_auto_fix_files(path_to_repo, 'yarn-autofixed.lock', parts.join("\n")) end # In yarn.lock, we attempt to update yarn.lock entries for the package @@ -170,7 +172,6 @@ def create_subparent_to_package_mapping(parsed_yarn_lock, path) break if index == packages.length - 1 section = if index.zero? - puts find_section_by_name(parsed_yarn_lock, package, packages[index + 1]) find_section_by_name(parsed_yarn_lock, package, packages[index + 1]) else find_section_by_name_and_version( @@ -187,7 +188,7 @@ def create_subparent_to_package_mapping(parsed_yarn_lock, path) def find_section_by_name(parsed_yarn_lock, name, next_package) parsed_yarn_lock.each do |key, array| if key.starts_with? "#{name}@" - %w[dependencies optionalDependencies].each do |section| + %w[dependencies peerDependencies].each do |section| if array[section]&.[](next_package) value = array.dig(section, next_package) return { "prev": key, "key": "#{next_package}@npm:#{value}" } From 73c02f26f2eef0ef59ab196efbeb2ae46b0a4d6c Mon Sep 17 00:00:00 2001 From: Maitray Shah Date: Mon, 22 Aug 2022 08:40:13 -0700 Subject: [PATCH 4/4] Updating sub parent resolution --- lib/salus/auto_fix/yarn_audit_v2.rb | 87 ++++++----------------------- lib/salus/scanners/yarn_audit.rb | 2 +- 2 files changed, 18 insertions(+), 71 deletions(-) diff --git a/lib/salus/auto_fix/yarn_audit_v2.rb b/lib/salus/auto_fix/yarn_audit_v2.rb index 6901b6da..d6d9cb70 100644 --- a/lib/salus/auto_fix/yarn_audit_v2.rb +++ b/lib/salus/auto_fix/yarn_audit_v2.rb @@ -14,11 +14,10 @@ def initialize(path_to_repo) def run_auto_fix(feed, path_to_repo, package_json, yarn_lock) fix_indirect_dependency(feed, yarn_lock, path_to_repo) fix_direct_dependency(feed, package_json, path_to_repo) + rescue StandardError => e + error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" + raise AutofixError, error_msg end - # rescue StandardError => e - # error_msg = "An error occurred while auto-fixing vulnerabilities: #{e}, #{e.backtrace}" - # raise AutofixError, error_msg - # end def fix_direct_dependency(feed, package_json, path_to_repo) packages = JSON.parse(package_json) @@ -46,7 +45,6 @@ def fix_indirect_dependency(feed, yarn_lock, path_to_repo) resolves.each do |resolve| if !patch.nil? && patch != "No patch available" && package != resolve["path"] block = create_subparent_to_package_mapping(parsed_yarn_lock, resolve["path"]) - puts block if block.key?(:key) block[:patch] = patch subparent_to_package_mapping.append(block) @@ -55,54 +53,11 @@ def fix_indirect_dependency(feed, yarn_lock, path_to_repo) end end parts = yarn_lock.split(/^\n/) - parts = update_package_definition(subparent_to_package_mapping, parts) parts = update_sub_parent_resolution(subparent_to_package_mapping, parts, parsed_yarn_lock) - # TODO: Run clean up task + # # TODO: Run clean up task write_auto_fix_files(path_to_repo, 'yarn-autofixed.lock', parts.join("\n")) end - # In yarn.lock, we attempt to update yarn.lock entries for the package - def update_package_definition(blocks, parts) - blocks.uniq { |hash| hash.values_at(:prev, :key, :patch) } - group_updates = blocks.group_by { |h| [h[:prev], h[:key]] } - group_updates.each do |updates, versions| - updates = updates.last - vulnerable_package_info = get_package_info(updates) - list_of_versions_available = vulnerable_package_info["versions"] - version_to_update_to = Salus::SemanticVersion.select_upgrade_version( - versions.first[:patch], list_of_versions_available - ) - package_name = if updates.starts_with? "@" - updates.split("@", 2).first - else - updates.split("@").first - end - if !version_to_update_to.nil? - fixed_package_info = get_package_info(package_name, version_to_update_to) - unless fixed_package_info.nil? - updated_version = "version " + '"' + version_to_update_to + '"' - updated_resolved = "resolved " + '"' + fixed_package_info["dist"]["tarball"] \ - + "#" + fixed_package_info["dist"]["shasum"] + '"' - updated_integrity = "integrity " + fixed_package_info['dist']['integrity'] - updated_name = package_name + "@^" + version_to_update_to - parts.each_with_index do |part, index| - current_v = parts[index].match(/(version .*)/) - version_string = current_v.to_s.tr('"', "").tr("version ", "") - if part.include?(updates) && !is_major_bump( - version_string, version_to_update_to - ) - parts[index].sub!(updates, updated_name) - parts[index].sub!(/(version .*)/, updated_version) - parts[index].sub!(/(resolved .*)/, updated_resolved) - parts[index].sub!(/(integrity .*)/, updated_integrity) - end - end - end - end - end - parts - end - def get_package_info(package, version = nil) info = if version.nil? run_shell("yarn npm info #{package} --json", chdir: File.expand_path(@path_to_repo)) @@ -134,33 +89,26 @@ def update_sub_parent_resolution(blocks, parts, parsed_yarn_lock) group_appends = blocks.group_by { |h| [h[:prev], h[:key]] } group_appends.each do |pair, patch| source = pair.first - target = if pair.last.starts_with? "@" - pair.last.split("@", 2).first - else - pair.last.split("@").first - end + target = pair.last.reverse.split('@', 2).collect(&:reverse).reverse.first vulnerable_package_info = get_package_info(target) - list_of_versions_available = vulnerable_package_info["data"]["versions"] + list_of_versions_available = vulnerable_package_info["versions"] version_to_update_to = Salus::SemanticVersion.select_upgrade_version( patch.first[:patch], list_of_versions_available ) - if !version_to_update_to.nil? - update_version_string = "^" + version_to_update_to - parts.each_with_index do |part, index| - match = part.match(/(#{target} .*)/) - if part.include?(source) && !match.nil? && - !is_major_bump(match.to_s, version_to_update_to) - match = part.match(/(#{target} .*)/) - replace = match.to_s.split(" ").first + ' "^' + version_to_update_to + '"' - part.sub!(/(#{target} .*)/, replace) - parts[index] = part - end + + update_version_string = "^" + version_to_update_to + parts.each_with_index do |part, index| + if part.include?(source) && part.include?(target) && !version_to_update_to.nil? + match = part.match(/(#{target}: .*)/) + replace = match.to_s.split(":").first + ': ^' + version_to_update_to + part.sub!(/(#{target}: .*)/, replace) + parts[index] = part end - section = parsed_yarn_lock[source] - section["dependencies"][target] = update_version_string - parsed_yarn_lock[source] = section end + section = parsed_yarn_lock[source] + section["dependencies"][target] = update_version_string + parsed_yarn_lock[source] = section end parts end @@ -181,7 +129,6 @@ def create_subparent_to_package_mapping(parsed_yarn_lock, path) ) end end - puts section section end diff --git a/lib/salus/scanners/yarn_audit.rb b/lib/salus/scanners/yarn_audit.rb index fae08767..ed060de3 100644 --- a/lib/salus/scanners/yarn_audit.rb +++ b/lib/salus/scanners/yarn_audit.rb @@ -79,7 +79,7 @@ def handle_latest_yarn_audit "Dependency of" => if dependency_of.nil? advisory.dig("module_name") else - dependency_of.join("") + dependency_of.join(", ") end }) end