dependabot-npm_and_yarn 0.195.0 → 0.196.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 +4 -4
- data/helpers/lib/npm/index.js +3 -0
- data/helpers/lib/npm/vulnerability-auditor.js +206 -0
- data/helpers/package-lock.json +377 -229
- data/helpers/package.json +1 -0
- data/helpers/run.js +9 -14
- data/lib/dependabot/npm_and_yarn/update_checker/vulnerability_auditor.rb +136 -0
- data/lib/dependabot/npm_and_yarn/update_checker.rb +65 -4
- metadata +5 -3
    
        data/helpers/package.json
    CHANGED
    
    
    
        data/helpers/run.js
    CHANGED
    
    | @@ -18,18 +18,13 @@ process.stdin.on("end", () => { | |
| 18 18 | 
             
                process.exit(1);
         | 
| 19 19 | 
             
              }
         | 
| 20 20 |  | 
| 21 | 
            -
               | 
| 22 | 
            -
                 | 
| 23 | 
            -
             | 
| 24 | 
            -
                   | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
                   | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
                  });
         | 
| 31 | 
            -
              } catch (e) {
         | 
| 32 | 
            -
                output({ error: `Error calling function: ${func.name}: ${e}` });
         | 
| 33 | 
            -
                process.exit(1);
         | 
| 34 | 
            -
              }
         | 
| 21 | 
            +
              func
         | 
| 22 | 
            +
                .apply(null, request.args)
         | 
| 23 | 
            +
                .then((result) => {
         | 
| 24 | 
            +
                  output({ result: result });
         | 
| 25 | 
            +
                })
         | 
| 26 | 
            +
                .catch((error) => {
         | 
| 27 | 
            +
                  output({ error: error.message });
         | 
| 28 | 
            +
                  process.exit(1);
         | 
| 29 | 
            +
                });
         | 
| 35 30 | 
             
            });
         | 
| @@ -0,0 +1,136 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "stringio"
         | 
| 4 | 
            +
            require "dependabot/dependency"
         | 
| 5 | 
            +
            require "dependabot/errors"
         | 
| 6 | 
            +
            require "dependabot/logger"
         | 
| 7 | 
            +
            require "dependabot/npm_and_yarn/file_parser"
         | 
| 8 | 
            +
            require "dependabot/npm_and_yarn/helpers"
         | 
| 9 | 
            +
            require "dependabot/npm_and_yarn/native_helpers"
         | 
| 10 | 
            +
            require "dependabot/npm_and_yarn/update_checker"
         | 
| 11 | 
            +
            require "dependabot/npm_and_yarn/update_checker/dependency_files_builder"
         | 
| 12 | 
            +
            require "dependabot/shared_helpers"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            module Dependabot
         | 
| 15 | 
            +
              module NpmAndYarn
         | 
| 16 | 
            +
                class UpdateChecker < Dependabot::UpdateCheckers::Base
         | 
| 17 | 
            +
                  class VulnerabilityAuditor
         | 
| 18 | 
            +
                    def initialize(dependency_files:, credentials:)
         | 
| 19 | 
            +
                      @dependency_files = dependency_files
         | 
| 20 | 
            +
                      @credentials = credentials
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    # Finds any dependencies in the `package-lock.json` or `npm-shrinkwrap.json` that have
         | 
| 24 | 
            +
                    # a subdependency on the given dependency that is locked to a vuln version range.
         | 
| 25 | 
            +
                    #
         | 
| 26 | 
            +
                    # NOTE: yarn is currently not supported.
         | 
| 27 | 
            +
                    #
         | 
| 28 | 
            +
                    # @param dependency [Dependabot::Dependency] the dependency to check
         | 
| 29 | 
            +
                    # @param security_advisories [Array<Dependabot::SecurityAdvisory>] advisories for the dependency
         | 
| 30 | 
            +
                    # @return [Hash<String, [String, Array<Hash<String, String>>]>] the audit results
         | 
| 31 | 
            +
                    #   * :dependency_name [String] the name of the dependency
         | 
| 32 | 
            +
                    #   * :fix_available [Boolean] whether a fix is available
         | 
| 33 | 
            +
                    #   * :current_version [String] the version of the dependency
         | 
| 34 | 
            +
                    #   * :target_version [String] the version of the dependency after the fix
         | 
| 35 | 
            +
                    #   * :fix_updates [Array<Hash<String, String>>] a list of dependencies to update in order to fix
         | 
| 36 | 
            +
                    #     * :dependency_name [String] the name of the blocking dependency
         | 
| 37 | 
            +
                    #     * :current_version [String] the current version of the blocking dependency
         | 
| 38 | 
            +
                    #     * :target_version [String] the target version of the blocking dependency
         | 
| 39 | 
            +
                    def audit(dependency:, security_advisories:)
         | 
| 40 | 
            +
                      fix_unavailable = {
         | 
| 41 | 
            +
                        "dependency_name" => dependency.name,
         | 
| 42 | 
            +
                        "fix_available" => false
         | 
| 43 | 
            +
                      }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      SharedHelpers.in_a_temporary_directory do
         | 
| 46 | 
            +
                        dependency_files_builder = DependencyFilesBuilder.new(
         | 
| 47 | 
            +
                          dependency: dependency,
         | 
| 48 | 
            +
                          dependency_files: dependency_files,
         | 
| 49 | 
            +
                          credentials: credentials
         | 
| 50 | 
            +
                        )
         | 
| 51 | 
            +
                        dependency_files_builder.write_temporary_dependency_files
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                        # `npm-shrinkwrap.js`, if present, takes precedence over `package-lock.js`.
         | 
| 54 | 
            +
                        # Both files use the same format. See https://bit.ly/3lDIAJV for more.
         | 
| 55 | 
            +
                        lockfile = (dependency_files_builder.shrinkwraps + dependency_files_builder.package_locks).first
         | 
| 56 | 
            +
                        return fix_unavailable unless lockfile
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                        vuln_versions = security_advisories.map do |a|
         | 
| 59 | 
            +
                          {
         | 
| 60 | 
            +
                            dependency_name: a.dependency_name,
         | 
| 61 | 
            +
                            affected_versions: a.vulnerable_version_strings
         | 
| 62 | 
            +
                          }
         | 
| 63 | 
            +
                        end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                        audit_result = SharedHelpers.run_helper_subprocess(
         | 
| 66 | 
            +
                          command: NativeHelpers.helper_path,
         | 
| 67 | 
            +
                          function: "npm:vulnerabilityAuditor",
         | 
| 68 | 
            +
                          args: [Dir.pwd, vuln_versions]
         | 
| 69 | 
            +
                        )
         | 
| 70 | 
            +
                        return fix_unavailable unless valid_audit_result?(audit_result, security_advisories)
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                        audit_result
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
                    rescue SharedHelpers::HelperSubprocessFailed => e
         | 
| 75 | 
            +
                      log_helper_subprocess_failure(dependency, e)
         | 
| 76 | 
            +
                      fix_unavailable
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    private
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    attr_reader :dependency_files, :credentials
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                    def valid_audit_result?(audit_result, security_advisories)
         | 
| 84 | 
            +
                      # we only need to check results that indicate a fix is available
         | 
| 85 | 
            +
                      return true unless audit_result["fix_available"]
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      return false if vulnerable_dependency_removed?(audit_result)
         | 
| 88 | 
            +
                      return false if dependency_still_vulnerable?(audit_result, security_advisories)
         | 
| 89 | 
            +
                      return false if downgrades_dependencies?(audit_result)
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                      true
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    def vulnerable_dependency_removed?(audit_result)
         | 
| 95 | 
            +
                      !audit_result["target_version"]
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    def dependency_still_vulnerable?(audit_result, security_advisories)
         | 
| 99 | 
            +
                      version = Version.new(audit_result["target_version"])
         | 
| 100 | 
            +
                      security_advisories.any? { |a| a.vulnerable?(version) }
         | 
| 101 | 
            +
                    end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    def downgrades_dependencies?(audit_result)
         | 
| 104 | 
            +
                      return true if downgrades_version?(audit_result["current_version"], audit_result["target_version"])
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                      audit_result["fix_updates"].any? do |update|
         | 
| 107 | 
            +
                        downgrades_version?(update["current_version"], update["target_version"])
         | 
| 108 | 
            +
                      end
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    def downgrades_version?(current_version, target_version)
         | 
| 112 | 
            +
                      current = Version.new(current_version)
         | 
| 113 | 
            +
                      target = Version.new(target_version)
         | 
| 114 | 
            +
                      current > target
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    def log_helper_subprocess_failure(dependency, error)
         | 
| 118 | 
            +
                      # See `Dependabot::SharedHelpers.run_helper_subprocess` for details on error context
         | 
| 119 | 
            +
                      context = error.error_context || {}
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                      builder = ::StringIO.new
         | 
| 122 | 
            +
                      builder << "VulnerabilityAuditor: "
         | 
| 123 | 
            +
                      builder << "#{context[:function]} " if context[:function]
         | 
| 124 | 
            +
                      builder << "failed"
         | 
| 125 | 
            +
                      builder << " after #{context[:time_taken].truncate(2)}s" if context[:time_taken]
         | 
| 126 | 
            +
                      builder << " while auditing #{dependency.name}: "
         | 
| 127 | 
            +
                      builder << error.message
         | 
| 128 | 
            +
                      builder << "\n" << context[:trace]
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                      msg = builder.string
         | 
| 131 | 
            +
                      Dependabot.logger.info(msg) # TODO: is this the right log level?
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
              end
         | 
| 136 | 
            +
            end
         | 
| @@ -14,6 +14,7 @@ module Dependabot | |
| 14 14 | 
             
                  require_relative "update_checker/version_resolver"
         | 
| 15 15 | 
             
                  require_relative "update_checker/subdependency_version_resolver"
         | 
| 16 16 | 
             
                  require_relative "update_checker/conflicting_dependency_resolver"
         | 
| 17 | 
            +
                  require_relative "update_checker/vulnerability_auditor"
         | 
| 17 18 |  | 
| 18 19 | 
             
                  def latest_version
         | 
| 19 20 | 
             
                    @latest_version ||=
         | 
| @@ -106,20 +107,80 @@ module Dependabot | |
| 106 107 |  | 
| 107 108 | 
             
                  private
         | 
| 108 109 |  | 
| 110 | 
            +
                  def vulnerability_audit
         | 
| 111 | 
            +
                    @vulnerability_audit ||=
         | 
| 112 | 
            +
                      VulnerabilityAuditor.new(
         | 
| 113 | 
            +
                        dependency_files: dependency_files,
         | 
| 114 | 
            +
                        credentials: credentials
         | 
| 115 | 
            +
                      ).audit(
         | 
| 116 | 
            +
                        dependency: dependency,
         | 
| 117 | 
            +
                        security_advisories: security_advisories
         | 
| 118 | 
            +
                      )
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
             | 
| 109 121 | 
             
                  def latest_version_resolvable_with_full_unlock?
         | 
| 110 | 
            -
                    return unless latest_version
         | 
| 122 | 
            +
                    return false unless latest_version
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                    return version_resolver.latest_version_resolvable_with_full_unlock? if dependency.top_level?
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                    return false unless transitive_security_updates_enabled? && security_advisories.any?
         | 
| 111 127 |  | 
| 112 | 
            -
                     | 
| 113 | 
            -
             | 
| 128 | 
            +
                    vulnerability_audit["fix_available"]
         | 
| 129 | 
            +
                  end
         | 
| 114 130 |  | 
| 115 | 
            -
             | 
| 131 | 
            +
                  def transitive_security_updates_enabled?
         | 
| 132 | 
            +
                    options.key?(:npm_transitive_security_updates)
         | 
| 116 133 | 
             
                  end
         | 
| 117 134 |  | 
| 118 135 | 
             
                  def updated_dependencies_after_full_unlock
         | 
| 136 | 
            +
                    if !dependency.top_level? && transitive_security_updates_enabled? && security_advisories.any?
         | 
| 137 | 
            +
                      return conflicting_updated_dependencies
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
             | 
| 119 140 | 
             
                    version_resolver.dependency_updates_from_full_unlock.
         | 
| 120 141 | 
             
                      map { |update_details| build_updated_dependency(update_details) }
         | 
| 121 142 | 
             
                  end
         | 
| 122 143 |  | 
| 144 | 
            +
                  def conflicting_updated_dependencies
         | 
| 145 | 
            +
                    top_level_dependencies = FileParser.new(
         | 
| 146 | 
            +
                      dependency_files: dependency_files,
         | 
| 147 | 
            +
                      credentials: credentials,
         | 
| 148 | 
            +
                      source: nil
         | 
| 149 | 
            +
                    ).parse.select(&:top_level?)
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    top_level_dependency_lookup = top_level_dependencies.map { |dep| [dep.name, dep] }.to_h
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    updated_deps = []
         | 
| 154 | 
            +
                    vulnerability_audit["fix_updates"].each do |update|
         | 
| 155 | 
            +
                      dependency_name = update["dependency_name"]
         | 
| 156 | 
            +
                      requirements = top_level_dependency_lookup[dependency_name]&.requirements || []
         | 
| 157 | 
            +
                      conflicting_dep = Dependency.new(
         | 
| 158 | 
            +
                        name: dependency_name,
         | 
| 159 | 
            +
                        package_manager: "npm_and_yarn",
         | 
| 160 | 
            +
                        requirements: requirements
         | 
| 161 | 
            +
                      )
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                      updated_deps << build_updated_dependency(
         | 
| 164 | 
            +
                        dependency: conflicting_dep,
         | 
| 165 | 
            +
                        version: update["target_version"],
         | 
| 166 | 
            +
                        previous_version: update["current_version"]
         | 
| 167 | 
            +
                      )
         | 
| 168 | 
            +
                    end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                    # We don't need to update this but need to include it so it's described
         | 
| 171 | 
            +
                    # in the PR and we'll pass validation that this dependency is at a
         | 
| 172 | 
            +
                    # non-vulnerable version.
         | 
| 173 | 
            +
                    if updated_deps.none? { |dep| dep.name == dependency.name }
         | 
| 174 | 
            +
                      updated_deps << build_updated_dependency(
         | 
| 175 | 
            +
                        dependency: dependency,
         | 
| 176 | 
            +
                        version: vulnerability_audit["target_version"],
         | 
| 177 | 
            +
                        previous_version: dependency.version
         | 
| 178 | 
            +
                      )
         | 
| 179 | 
            +
                    end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                    updated_deps
         | 
| 182 | 
            +
                  end
         | 
| 183 | 
            +
             | 
| 123 184 | 
             
                  def build_updated_dependency(update_details)
         | 
| 124 185 | 
             
                    original_dep = update_details.fetch(:dependency)
         | 
| 125 186 | 
             
                    version = update_details.fetch(:version).to_s
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: dependabot-npm_and_yarn
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.196.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Dependabot
         | 
| @@ -16,14 +16,14 @@ dependencies: | |
| 16 16 | 
             
                requirements:
         | 
| 17 17 | 
             
                - - '='
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: 0. | 
| 19 | 
            +
                    version: 0.196.0
         | 
| 20 20 | 
             
              type: :runtime
         | 
| 21 21 | 
             
              prerelease: false
         | 
| 22 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 23 | 
             
                requirements:
         | 
| 24 24 | 
             
                - - '='
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: 0. | 
| 26 | 
            +
                    version: 0.196.0
         | 
| 27 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 28 | 
             
              name: debase
         | 
| 29 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -233,6 +233,7 @@ files: | |
| 233 233 | 
             
            - helpers/jest.config.js
         | 
| 234 234 | 
             
            - helpers/lib/npm/conflicting-dependency-parser.js
         | 
| 235 235 | 
             
            - helpers/lib/npm/index.js
         | 
| 236 | 
            +
            - helpers/lib/npm/vulnerability-auditor.js
         | 
| 236 237 | 
             
            - helpers/lib/npm6/helpers.js
         | 
| 237 238 | 
             
            - helpers/lib/npm6/index.js
         | 
| 238 239 | 
             
            - helpers/lib/npm6/peer-dependency-checker.js
         | 
| @@ -307,6 +308,7 @@ files: | |
| 307 308 | 
             
            - lib/dependabot/npm_and_yarn/update_checker/requirements_updater.rb
         | 
| 308 309 | 
             
            - lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb
         | 
| 309 310 | 
             
            - lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb
         | 
| 311 | 
            +
            - lib/dependabot/npm_and_yarn/update_checker/vulnerability_auditor.rb
         | 
| 310 312 | 
             
            - lib/dependabot/npm_and_yarn/version.rb
         | 
| 311 313 | 
             
            homepage: https://github.com/dependabot/dependabot-core
         | 
| 312 314 | 
             
            licenses:
         |