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:
|