dependabot-npm_and_yarn 0.195.0 → 0.196.2
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 +243 -0
- data/helpers/package-lock.json +1192 -882
- data/helpers/package.json +3 -2
- data/helpers/run.js +9 -14
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +43 -19
- data/lib/dependabot/npm_and_yarn/update_checker/vulnerability_auditor.rb +136 -0
- data/lib/dependabot/npm_and_yarn/update_checker.rb +71 -4
- metadata +6 -4
data/helpers/package.json
CHANGED
@@ -10,15 +10,16 @@
|
|
10
10
|
},
|
11
11
|
"dependencies": {
|
12
12
|
"@dependabot/yarn-lib": "^1.21.1",
|
13
|
-
"@npmcli/arborist": "^5.2.
|
13
|
+
"@npmcli/arborist": "^5.2.3",
|
14
14
|
"detect-indent": "^6.1.0",
|
15
|
+
"nock": "^13.2.7",
|
15
16
|
"npm": "6.14.17",
|
16
17
|
"semver": "^7.3.7"
|
17
18
|
},
|
18
19
|
"devDependencies": {
|
19
20
|
"eslint": "^8.18.0",
|
20
21
|
"eslint-config-prettier": "^8.5.0",
|
21
|
-
"jest": "^28.1.
|
22
|
+
"jest": "^28.1.1",
|
22
23
|
"prettier": "^2.7.1",
|
23
24
|
"rimraf": "^3.0.2"
|
24
25
|
}
|
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
|
});
|
@@ -291,7 +291,7 @@ module Dependabot
|
|
291
291
|
|
292
292
|
if matches_double_glob && !nested
|
293
293
|
dependency_files +=
|
294
|
-
|
294
|
+
find_directories(File.join(path, "*")).flat_map do |nested_path|
|
295
295
|
fetch_lerna_packages_from_path(nested_path, true)
|
296
296
|
end
|
297
297
|
end
|
@@ -309,34 +309,58 @@ module Dependabot
|
|
309
309
|
[] # Invalid lerna.json, which must not be in use
|
310
310
|
end
|
311
311
|
|
312
|
-
paths_array.flat_map
|
313
|
-
# The packages/!(not-this-package) syntax is unique to Yarn
|
314
|
-
if path.include?("*") || path.include?("!(")
|
315
|
-
expanded_paths(path)
|
316
|
-
else
|
317
|
-
path
|
318
|
-
end
|
319
|
-
end
|
312
|
+
paths_array.flat_map { |path| recursive_find_directories(path) }
|
320
313
|
end
|
321
314
|
|
322
315
|
# Only expands globs one level deep, so path/**/* gets expanded to path/
|
323
|
-
def
|
324
|
-
|
316
|
+
def find_directories(glob)
|
317
|
+
return [glob] unless glob.include?("*") || yarn_ignored_glob(glob)
|
318
|
+
|
319
|
+
unglobbed_path =
|
320
|
+
glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*").
|
321
|
+
split("*").
|
322
|
+
first&.gsub(%r{(?<=/)[^/]*$}, "") || "."
|
325
323
|
|
326
324
|
dir = directory.gsub(%r{(^/|/$)}, "")
|
327
|
-
path = path.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*")
|
328
|
-
unglobbed_path = path.split("*").first&.gsub(%r{(?<=/)[^/]*$}, "") ||
|
329
|
-
"."
|
330
325
|
|
331
|
-
|
326
|
+
paths =
|
332
327
|
repo_contents(dir: unglobbed_path, raise_errors: false).
|
333
328
|
select { |file| file.type == "dir" }.
|
334
|
-
map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") }
|
335
|
-
|
329
|
+
map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") }
|
330
|
+
|
331
|
+
matching_paths(glob, paths)
|
332
|
+
end
|
333
|
+
|
334
|
+
def matching_paths(glob, paths)
|
335
|
+
ignored_glob = yarn_ignored_glob(glob)
|
336
|
+
glob = glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*")
|
337
|
+
|
338
|
+
results = paths.select { |filename| File.fnmatch?(glob, filename) }
|
339
|
+
return results unless ignored_glob
|
336
340
|
|
337
|
-
|
341
|
+
results.reject { |filename| File.fnmatch?(ignored_glob, filename) }
|
342
|
+
end
|
343
|
+
|
344
|
+
def recursive_find_directories(glob, prefix = "")
|
345
|
+
return [prefix + glob] unless glob.include?("*") || yarn_ignored_glob(glob)
|
346
|
+
|
347
|
+
glob = glob.gsub(%r{^\./}, "")
|
348
|
+
glob_parts = glob.split("/")
|
349
|
+
|
350
|
+
paths = find_directories(prefix + glob_parts.first)
|
351
|
+
next_parts = glob_parts.drop(1)
|
352
|
+
return paths if next_parts.empty?
|
353
|
+
|
354
|
+
paths = paths.flat_map do |expanded_path|
|
355
|
+
recursive_find_directories(next_parts.join("/"), "#{expanded_path}/")
|
356
|
+
end
|
357
|
+
|
358
|
+
matching_paths(prefix + glob, paths)
|
359
|
+
end
|
338
360
|
|
339
|
-
|
361
|
+
# The packages/!(not-this-package) syntax is unique to Yarn
|
362
|
+
def yarn_ignored_glob(glob)
|
363
|
+
glob.match?(/!\(.*?\)/) && glob.gsub(/(!\((.*?)\))/, '\2')
|
340
364
|
end
|
341
365
|
|
342
366
|
def parsed_package_json
|
@@ -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,86 @@ 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
|
111
123
|
|
112
|
-
|
113
|
-
return false unless dependency.top_level?
|
124
|
+
return version_resolver.latest_version_resolvable_with_full_unlock? if dependency.top_level?
|
114
125
|
|
115
|
-
|
126
|
+
return false unless transitive_security_updates_enabled? && security_advisories.any?
|
127
|
+
|
128
|
+
vulnerability_audit["fix_available"]
|
129
|
+
end
|
130
|
+
|
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 = top_level_dependency_lookup
|
146
|
+
|
147
|
+
updated_deps = []
|
148
|
+
vulnerability_audit["fix_updates"].each do |update|
|
149
|
+
dependency_name = update["dependency_name"]
|
150
|
+
requirements = top_level_dependencies[dependency_name]&.requirements || []
|
151
|
+
conflicting_dep = Dependency.new(
|
152
|
+
name: dependency_name,
|
153
|
+
package_manager: "npm_and_yarn",
|
154
|
+
requirements: requirements
|
155
|
+
)
|
156
|
+
|
157
|
+
updated_deps << build_updated_dependency(
|
158
|
+
dependency: conflicting_dep,
|
159
|
+
version: update["target_version"],
|
160
|
+
previous_version: update["current_version"]
|
161
|
+
)
|
162
|
+
end
|
163
|
+
|
164
|
+
# We don't need to update this but need to include it so it's described
|
165
|
+
# in the PR and we'll pass validation that this dependency is at a
|
166
|
+
# non-vulnerable version.
|
167
|
+
if updated_deps.none? { |dep| dep.name == dependency.name }
|
168
|
+
updated_deps << build_updated_dependency(
|
169
|
+
dependency: dependency,
|
170
|
+
version: vulnerability_audit["target_version"],
|
171
|
+
previous_version: dependency.version
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Target dependency should be first in the result to support rebases
|
176
|
+
updated_deps.select { |dep| dep.name == dependency.name } +
|
177
|
+
updated_deps.reject { |dep| dep.name == dependency.name }
|
178
|
+
end
|
179
|
+
|
180
|
+
def top_level_dependency_lookup
|
181
|
+
top_level_dependencies = FileParser.new(
|
182
|
+
dependency_files: dependency_files,
|
183
|
+
credentials: credentials,
|
184
|
+
source: nil
|
185
|
+
).parse.select(&:top_level?)
|
186
|
+
|
187
|
+
top_level_dependencies.map { |dep| [dep.name, dep] }.to_h
|
188
|
+
end
|
189
|
+
|
123
190
|
def build_updated_dependency(update_details)
|
124
191
|
original_dep = update_details.fetch(:dependency)
|
125
192
|
version = update_details.fetch(:version).to_s
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dependabot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-06-
|
11
|
+
date: 2022-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dependabot-common
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.196.2
|
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.2
|
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:
|