dependabot-npm_and_yarn 0.212.0 → 0.214.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/.eslintrc +1 -1
- data/helpers/README.md +2 -2
- data/helpers/lib/npm/vulnerability-auditor.js +7 -7
- data/helpers/package-lock.json +2781 -2547
- data/helpers/package.json +5 -5
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json +3 -3
- data/lib/dependabot/npm_and_yarn/file_fetcher/path_dependency_builder.rb +11 -2
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +90 -5
- data/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb +15 -4
- data/lib/dependabot/npm_and_yarn/file_parser.rb +15 -6
- data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +35 -21
- data/lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb +86 -7
- data/lib/dependabot/npm_and_yarn/file_updater/package_json_updater.rb +2 -2
- data/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb +96 -32
- data/lib/dependabot/npm_and_yarn/file_updater.rb +53 -1
- data/lib/dependabot/npm_and_yarn/helpers.rb +94 -0
- data/lib/dependabot/npm_and_yarn/package_name.rb +2 -2
- data/lib/dependabot/npm_and_yarn/requirement.rb +3 -3
- data/lib/dependabot/npm_and_yarn/update_checker/dependency_files_builder.rb +43 -1
- data/lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb +13 -14
- data/lib/dependabot/npm_and_yarn/update_checker/library_detector.rb +16 -3
- data/lib/dependabot/npm_and_yarn/update_checker/registry_finder.rb +77 -23
- data/lib/dependabot/npm_and_yarn/update_checker/requirements_updater.rb +3 -4
- data/lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb +19 -4
- data/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb +74 -30
- data/lib/dependabot/npm_and_yarn/update_checker/vulnerability_auditor.rb +33 -8
- data/lib/dependabot/npm_and_yarn/update_checker.rb +76 -21
- data/lib/dependabot/npm_and_yarn/version.rb +1 -1
- data/lib/dependabot/npm_and_yarn.rb +2 -0
- metadata +13 -56
- data/lib/dependabot/npm_and_yarn/file_parser/yarn_lockfile_parser.rb +0 -59
@@ -12,20 +12,21 @@ module Dependabot
|
|
12
12
|
https://registry.npmjs.org
|
13
13
|
http://registry.npmjs.org
|
14
14
|
https://registry.yarnpkg.com
|
15
|
+
http://registry.yarnpkg.com
|
15
16
|
).freeze
|
16
|
-
NPM_AUTH_TOKEN_REGEX =
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
/^(?:--)?registry\s+['"](?<registry>.*)['"]/.freeze
|
17
|
+
NPM_AUTH_TOKEN_REGEX = %r{//(?<registry>.*)/:_authToken=(?<token>.*)$}
|
18
|
+
NPM_GLOBAL_REGISTRY_REGEX = /^registry\s*=\s*['"]?(?<registry>.*?)['"]?$/
|
19
|
+
YARN_GLOBAL_REGISTRY_REGEX = /^(?:--)?registry\s+((['"](?<registry>.*)['"])|(?<registry>.*))/
|
20
|
+
NPM_SCOPED_REGISTRY_REGEX = /^(?<scope>@[^:]+)\s*:registry\s*=\s*['"]?(?<registry>.*?)['"]?$/
|
21
|
+
YARN_SCOPED_REGISTRY_REGEX = /['"](?<scope>@[^:]+):registry['"]\s((['"](?<registry>.*)['"])|(?<registry>.*))/
|
22
22
|
|
23
23
|
def initialize(dependency:, credentials:, npmrc_file: nil,
|
24
|
-
yarnrc_file: nil)
|
24
|
+
yarnrc_file: nil, yarnrc_yml_file: nil)
|
25
25
|
@dependency = dependency
|
26
26
|
@credentials = credentials
|
27
27
|
@npmrc_file = npmrc_file
|
28
28
|
@yarnrc_file = yarnrc_file
|
29
|
+
@yarnrc_yml_file = yarnrc_yml_file
|
29
30
|
end
|
30
31
|
|
31
32
|
def registry
|
@@ -46,15 +47,24 @@ module Dependabot
|
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
50
|
+
def registry_from_rc(dependency_name)
|
51
|
+
return global_registry unless dependency_name.start_with?("@") && dependency_name.include?("/")
|
52
|
+
|
53
|
+
scope = dependency_name.split("/").first
|
54
|
+
scoped_registry(scope)
|
55
|
+
end
|
56
|
+
|
49
57
|
private
|
50
58
|
|
51
|
-
attr_reader :dependency, :credentials, :npmrc_file, :yarnrc_file
|
59
|
+
attr_reader :dependency, :credentials, :npmrc_file, :yarnrc_file, :yarnrc_yml_file
|
52
60
|
|
53
61
|
def first_registry_with_dependency_details
|
54
62
|
@first_registry_with_dependency_details ||=
|
55
63
|
known_registries.find do |details|
|
64
|
+
url = "#{details['registry'].gsub(%r{/+$}, '')}/#{escaped_dependency_name}"
|
65
|
+
url = "https://#{url}" unless url.start_with?("http")
|
56
66
|
response = Dependabot::RegistryClient.get(
|
57
|
-
url:
|
67
|
+
url: url,
|
58
68
|
headers: auth_header_for(details["token"])
|
59
69
|
)
|
60
70
|
response.status < 400 && JSON.parse(response.body)
|
@@ -64,10 +74,12 @@ module Dependabot
|
|
64
74
|
nil
|
65
75
|
end&.fetch("registry")
|
66
76
|
|
67
|
-
@first_registry_with_dependency_details ||= global_registry
|
77
|
+
@first_registry_with_dependency_details ||= global_registry.sub(%r{/+$}, "").sub(%r{^.*?//}, "")
|
68
78
|
end
|
69
79
|
|
70
80
|
def registry_url
|
81
|
+
return registry if registry.start_with?("http")
|
82
|
+
|
71
83
|
protocol =
|
72
84
|
if registry_source_url
|
73
85
|
registry_source_url.split("://").first
|
@@ -134,10 +146,13 @@ module Dependabot
|
|
134
146
|
npmrc_file.content.scan(NPM_AUTH_TOKEN_REGEX) do
|
135
147
|
next if Regexp.last_match[:registry].include?("${")
|
136
148
|
|
149
|
+
registry = Regexp.last_match[:registry]
|
150
|
+
token = Regexp.last_match[:token]&.strip
|
151
|
+
|
137
152
|
registries << {
|
138
153
|
"type" => "npm_registry",
|
139
|
-
"registry" =>
|
140
|
-
"token" =>
|
154
|
+
"registry" => registry.gsub(/\s+/, "%20"),
|
155
|
+
"token" => token
|
141
156
|
}
|
142
157
|
end
|
143
158
|
|
@@ -146,7 +161,8 @@ module Dependabot
|
|
146
161
|
|
147
162
|
registry = Regexp.last_match[:registry].strip.
|
148
163
|
sub(%r{/+$}, "").
|
149
|
-
sub(%r{^.*?//}, "")
|
164
|
+
sub(%r{^.*?//}, "").
|
165
|
+
gsub(/\s+/, "%20")
|
150
166
|
next if registries.map { |r| r["registry"] }.include?(registry)
|
151
167
|
|
152
168
|
registries << {
|
@@ -168,7 +184,8 @@ module Dependabot
|
|
168
184
|
|
169
185
|
registry = Regexp.last_match[:registry].strip.
|
170
186
|
sub(%r{/+$}, "").
|
171
|
-
sub(%r{^.*?//}, "")
|
187
|
+
sub(%r{^.*?//}, "").
|
188
|
+
gsub(/\s+/, "%20")
|
172
189
|
registries << {
|
173
190
|
"type" => "npm_registry",
|
174
191
|
"registry" => registry,
|
@@ -190,26 +207,56 @@ module Dependabot
|
|
190
207
|
end
|
191
208
|
end
|
192
209
|
|
210
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
193
211
|
def global_registry
|
212
|
+
return @global_registry if defined? @global_registry
|
213
|
+
|
194
214
|
npmrc_file&.content.to_s.scan(NPM_GLOBAL_REGISTRY_REGEX) do
|
195
215
|
next if Regexp.last_match[:registry].include?("${")
|
196
216
|
|
197
|
-
|
198
|
-
sub(%r{/+$}, "").
|
199
|
-
sub(%r{^.*?//}, "")
|
200
|
-
return registry
|
217
|
+
return @global_registry = Regexp.last_match[:registry].strip
|
201
218
|
end
|
202
219
|
|
203
220
|
yarnrc_file&.content.to_s.scan(YARN_GLOBAL_REGISTRY_REGEX) do
|
204
221
|
next if Regexp.last_match[:registry].include?("${")
|
205
222
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
223
|
+
return @global_registry = Regexp.last_match[:registry].strip
|
224
|
+
end
|
225
|
+
|
226
|
+
if parsed_yarnrc_yml&.key?("npmRegistryServer")
|
227
|
+
return @global_registry = parsed_yarnrc_yml["npmRegistryServer"]
|
228
|
+
end
|
229
|
+
|
230
|
+
replaces_base = credentials.find { |cred| cred["type"] == "npm_registry" && cred["replaces-base"] == true }
|
231
|
+
if replaces_base
|
232
|
+
registry = replaces_base["registry"]
|
233
|
+
registry = "https://#{registry}" unless registry.start_with?("http")
|
234
|
+
return @global_registry = registry
|
210
235
|
end
|
211
236
|
|
212
|
-
"registry.npmjs.org"
|
237
|
+
"https://registry.npmjs.org"
|
238
|
+
end
|
239
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
240
|
+
|
241
|
+
def scoped_registry(scope)
|
242
|
+
npmrc_file&.content.to_s.scan(NPM_SCOPED_REGISTRY_REGEX) do
|
243
|
+
next if Regexp.last_match[:registry].include?("${") || Regexp.last_match[:scope] != scope
|
244
|
+
|
245
|
+
return Regexp.last_match[:registry].strip
|
246
|
+
end
|
247
|
+
|
248
|
+
yarnrc_file&.content.to_s.scan(YARN_SCOPED_REGISTRY_REGEX) do
|
249
|
+
next if Regexp.last_match[:registry].include?("${") || Regexp.last_match[:scope] != scope
|
250
|
+
|
251
|
+
return Regexp.last_match[:registry].strip
|
252
|
+
end
|
253
|
+
|
254
|
+
if parsed_yarnrc_yml
|
255
|
+
yarn_berry_registry = parsed_yarnrc_yml.dig("npmScopes", scope.delete_prefix("@"), "npmRegistryServer")
|
256
|
+
return yarn_berry_registry if yarn_berry_registry
|
257
|
+
end
|
258
|
+
|
259
|
+
global_registry
|
213
260
|
end
|
214
261
|
|
215
262
|
# npm registries expect slashes to be escaped
|
@@ -224,6 +271,13 @@ module Dependabot
|
|
224
271
|
|
225
272
|
sources.find { |s| s[:type] == "registry" }&.fetch(:url)
|
226
273
|
end
|
274
|
+
|
275
|
+
def parsed_yarnrc_yml
|
276
|
+
return unless yarnrc_yml_file
|
277
|
+
return @parsed_yarnrc_yml if defined? @parsed_yarnrc_yml
|
278
|
+
|
279
|
+
@parsed_yarnrc_yml = YAML.safe_load(yarnrc_yml_file.content)
|
280
|
+
end
|
227
281
|
end
|
228
282
|
end
|
229
283
|
end
|
@@ -13,10 +13,9 @@ module Dependabot
|
|
13
13
|
module NpmAndYarn
|
14
14
|
class UpdateChecker
|
15
15
|
class RequirementsUpdater
|
16
|
-
VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)
|
17
|
-
SEPARATOR = /(?<=[a-zA-Z0-9*])[\s|]+(?![\s|-])
|
18
|
-
ALLOWED_UPDATE_STRATEGIES =
|
19
|
-
%i(widen_ranges bump_versions bump_versions_if_necessary).freeze
|
16
|
+
VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/
|
17
|
+
SEPARATOR = /(?<=[a-zA-Z0-9*])[\s|]+(?![\s|-])/
|
18
|
+
ALLOWED_UPDATE_STRATEGIES = %i(widen_ranges bump_versions bump_versions_if_necessary).freeze
|
20
19
|
|
21
20
|
def initialize(requirements:, updated_source:, update_strategy:,
|
22
21
|
latest_resolvable_version:)
|
@@ -19,19 +19,21 @@ module Dependabot
|
|
19
19
|
class UpdateChecker
|
20
20
|
class SubdependencyVersionResolver
|
21
21
|
def initialize(dependency:, credentials:, dependency_files:,
|
22
|
-
ignored_versions:, latest_allowable_version:)
|
22
|
+
ignored_versions:, latest_allowable_version:, repo_contents_path:)
|
23
23
|
@dependency = dependency
|
24
24
|
@credentials = credentials
|
25
25
|
@dependency_files = dependency_files
|
26
26
|
@ignored_versions = ignored_versions
|
27
27
|
@latest_allowable_version = latest_allowable_version
|
28
|
+
@repo_contents_path = repo_contents_path
|
28
29
|
end
|
29
30
|
|
30
31
|
def latest_resolvable_version
|
31
32
|
raise "Not a subdependency!" if dependency.requirements.any?
|
32
33
|
return if bundled_dependency?
|
33
34
|
|
34
|
-
|
35
|
+
base_dir = dependency_files.first.directory
|
36
|
+
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
35
37
|
dependency_files_builder.write_temporary_dependency_files
|
36
38
|
|
37
39
|
updated_lockfiles = filtered_lockfiles.map do |lockfile|
|
@@ -53,13 +55,15 @@ module Dependabot
|
|
53
55
|
private
|
54
56
|
|
55
57
|
attr_reader :dependency, :credentials, :dependency_files,
|
56
|
-
:ignored_versions, :latest_allowable_version
|
58
|
+
:ignored_versions, :latest_allowable_version, :repo_contents_path
|
57
59
|
|
58
60
|
def update_subdependency_in_lockfile(lockfile)
|
59
61
|
lockfile_name = Pathname.new(lockfile.name).basename.to_s
|
60
62
|
path = Pathname.new(lockfile.name).dirname.to_s
|
61
63
|
|
62
|
-
updated_files = if lockfile.name.end_with?("yarn.lock")
|
64
|
+
updated_files = if lockfile.name.end_with?("yarn.lock") && Helpers.yarn_berry?(lockfile)
|
65
|
+
run_yarn_berry_updater(path, lockfile_name)
|
66
|
+
elsif lockfile.name.end_with?("yarn.lock")
|
63
67
|
run_yarn_updater(path, lockfile_name)
|
64
68
|
else
|
65
69
|
run_npm_updater(path, lockfile_name, lockfile.content)
|
@@ -109,6 +113,17 @@ module Dependabot
|
|
109
113
|
sleep(rand(3.0..10.0)) && retry
|
110
114
|
end
|
111
115
|
|
116
|
+
def run_yarn_berry_updater(path, lockfile_name)
|
117
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
118
|
+
Dir.chdir(path) do
|
119
|
+
Helpers.run_yarn_commands(
|
120
|
+
"yarn up -R #{dependency.name} #{Helpers.yarn_berry_args}".strip
|
121
|
+
)
|
122
|
+
{ lockfile_name => File.read(lockfile_name) }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
112
127
|
def run_npm_updater(path, lockfile_name, lockfile_content)
|
113
128
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
114
129
|
Dir.chdir(path) do
|
@@ -36,7 +36,16 @@ module Dependabot
|
|
36
36
|
"\s>\s(?<requiring_dep>[^"]+)"\s
|
37
37
|
has\s(incorrect|unmet)\speer\sdependency\s
|
38
38
|
"(?<required_dep>[^"]+)"
|
39
|
-
/x
|
39
|
+
/x
|
40
|
+
|
41
|
+
# Error message from yarn add:
|
42
|
+
# YN0060: │ eve-roster@workspace:. provides jest (p8d618) \
|
43
|
+
# with version 29.3.0, which doesn't satisfy \
|
44
|
+
# what ts-jest requests\n
|
45
|
+
YARN_BERRY_PEER_DEP_ERROR_REGEX =
|
46
|
+
/
|
47
|
+
YN0060:\s|\s.+\sprovides\s(?<required_dep>.+?)\s\((?<info_hash>\w+)\).+what\s(?<requiring_dep>.+?)\srequests
|
48
|
+
/x
|
40
49
|
|
41
50
|
# Error message from npm install:
|
42
51
|
# react-dom@15.2.0 requires a peer of react@^15.2.0 \
|
@@ -46,7 +55,7 @@ module Dependabot
|
|
46
55
|
(?<requiring_dep>[^\s]+)\s
|
47
56
|
requires\sa\speer\sof\s
|
48
57
|
(?<required_dep>.+?)\sbut\snone\sis\sinstalled.
|
49
|
-
/x
|
58
|
+
/x
|
50
59
|
|
51
60
|
# Error message from npm install:
|
52
61
|
# npm ERR! Could not resolve dependency:
|
@@ -59,10 +68,10 @@ module Dependabot
|
|
59
68
|
/
|
60
69
|
npm\s(?:WARN|ERR!)\sCould\snot\sresolve\sdependency:\n
|
61
70
|
npm\s(?:WARN|ERR!)\speer\s(?<required_dep>\S+@\S+(\s\S+)?)\sfrom\s(?<requiring_dep>\S+@\S+)
|
62
|
-
/x
|
71
|
+
/x
|
63
72
|
|
64
73
|
def initialize(dependency:, credentials:, dependency_files:,
|
65
|
-
latest_allowable_version:, latest_version_finder:)
|
74
|
+
latest_allowable_version:, latest_version_finder:, repo_contents_path:)
|
66
75
|
@dependency = dependency
|
67
76
|
@credentials = credentials
|
68
77
|
@dependency_files = dependency_files
|
@@ -70,6 +79,7 @@ module Dependabot
|
|
70
79
|
|
71
80
|
@latest_version_finder = {}
|
72
81
|
@latest_version_finder[dependency] = latest_version_finder
|
82
|
+
@repo_contents_path = repo_contents_path
|
73
83
|
end
|
74
84
|
|
75
85
|
def latest_resolvable_version
|
@@ -135,7 +145,7 @@ module Dependabot
|
|
135
145
|
private
|
136
146
|
|
137
147
|
attr_reader :dependency, :credentials, :dependency_files,
|
138
|
-
:latest_allowable_version
|
148
|
+
:latest_allowable_version, :repo_contents_path
|
139
149
|
|
140
150
|
def latest_version_finder(dep)
|
141
151
|
@latest_version_finder[dep] ||=
|
@@ -307,30 +317,15 @@ module Dependabot
|
|
307
317
|
# TODO: Add all of the error handling that the FileUpdater does
|
308
318
|
# here (since problematic repos will be resolved here before they're
|
309
319
|
# seen by the FileUpdater)
|
310
|
-
|
320
|
+
base_dir = dependency_files.first.directory
|
321
|
+
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
311
322
|
dependency_files_builder.write_temporary_dependency_files
|
312
323
|
|
313
324
|
filtered_package_files.flat_map do |file|
|
314
325
|
path = Pathname.new(file.name).dirname
|
315
326
|
run_checker(path: path, version: version)
|
316
327
|
rescue SharedHelpers::HelperSubprocessFailed => e
|
317
|
-
|
318
|
-
if e.message.match?(NPM6_PEER_DEP_ERROR_REGEX)
|
319
|
-
e.message.scan(NPM6_PEER_DEP_ERROR_REGEX) do
|
320
|
-
errors << Regexp.last_match.named_captures
|
321
|
-
end
|
322
|
-
elsif e.message.match?(NPM8_PEER_DEP_ERROR_REGEX)
|
323
|
-
e.message.scan(NPM8_PEER_DEP_ERROR_REGEX) do
|
324
|
-
errors << Regexp.last_match.named_captures
|
325
|
-
end
|
326
|
-
elsif e.message.match?(YARN_PEER_DEP_ERROR_REGEX)
|
327
|
-
e.message.scan(YARN_PEER_DEP_ERROR_REGEX) do
|
328
|
-
errors << Regexp.last_match.named_captures
|
329
|
-
end
|
330
|
-
else
|
331
|
-
raise
|
332
|
-
end
|
333
|
-
errors
|
328
|
+
handle_peer_dependency_errors(e)
|
334
329
|
end.compact
|
335
330
|
end
|
336
331
|
rescue SharedHelpers::HelperSubprocessFailed
|
@@ -340,6 +335,30 @@ module Dependabot
|
|
340
335
|
[]
|
341
336
|
end
|
342
337
|
|
338
|
+
def handle_peer_dependency_errors(error)
|
339
|
+
errors = []
|
340
|
+
if error.message.match?(NPM6_PEER_DEP_ERROR_REGEX)
|
341
|
+
error.message.scan(NPM6_PEER_DEP_ERROR_REGEX) do
|
342
|
+
errors << Regexp.last_match.named_captures
|
343
|
+
end
|
344
|
+
elsif error.message.match?(NPM8_PEER_DEP_ERROR_REGEX)
|
345
|
+
error.message.scan(NPM8_PEER_DEP_ERROR_REGEX) do
|
346
|
+
errors << Regexp.last_match.named_captures
|
347
|
+
end
|
348
|
+
elsif error.message.match?(YARN_PEER_DEP_ERROR_REGEX)
|
349
|
+
error.message.scan(YARN_PEER_DEP_ERROR_REGEX) do
|
350
|
+
errors << Regexp.last_match.named_captures
|
351
|
+
end
|
352
|
+
elsif error.message.match?(YARN_BERRY_PEER_DEP_ERROR_REGEX)
|
353
|
+
error.message.scan(YARN_BERRY_PEER_DEP_ERROR_REGEX) do
|
354
|
+
errors << Regexp.last_match.named_captures
|
355
|
+
end
|
356
|
+
else
|
357
|
+
raise
|
358
|
+
end
|
359
|
+
errors
|
360
|
+
end
|
361
|
+
|
343
362
|
def unmet_peer_dependencies
|
344
363
|
peer_dependency_errors.
|
345
364
|
map { |captures| error_details_from_captures(captures) }
|
@@ -351,13 +370,14 @@ module Dependabot
|
|
351
370
|
end
|
352
371
|
|
353
372
|
def error_details_from_captures(captures)
|
373
|
+
required_dep_captures = captures.fetch("required_dep")
|
374
|
+
requiring_dep_captures = captures.fetch("requiring_dep")
|
375
|
+
return {} unless required_dep_captures && requiring_dep_captures
|
376
|
+
|
354
377
|
{
|
355
|
-
requirement_name:
|
356
|
-
|
357
|
-
|
358
|
-
captures.fetch("required_dep").split("@").last.delete('"'),
|
359
|
-
requiring_dep_name:
|
360
|
-
captures.fetch("requiring_dep").sub(/@[^@]+$/, "")
|
378
|
+
requirement_name: required_dep_captures.sub(/@[^@]+$/, ""),
|
379
|
+
requirement_version: required_dep_captures.split("@").last.delete('"'),
|
380
|
+
requiring_dep_name: requiring_dep_captures.sub(/@[^@]+$/, "")
|
361
381
|
}
|
362
382
|
end
|
363
383
|
|
@@ -468,13 +488,37 @@ module Dependabot
|
|
468
488
|
def run_checker(path:, version:)
|
469
489
|
# If there are both yarn lockfiles and npm lockfiles only run the
|
470
490
|
# yarn updater
|
471
|
-
|
491
|
+
lockfiles = lockfiles_for_path(lockfiles: dependency_files_builder.yarn_locks, path: path)
|
492
|
+
if lockfiles.any?
|
493
|
+
return run_yarn_berry_checker(path: path, version: version) if Helpers.yarn_berry?(lockfiles.first)
|
494
|
+
|
472
495
|
return run_yarn_checker(path: path, version: version)
|
473
496
|
end
|
474
497
|
|
475
498
|
run_npm_checker(path: path, version: version)
|
476
499
|
end
|
477
500
|
|
501
|
+
def run_yarn_berry_checker(path:, version:)
|
502
|
+
# This method mimics calling a native helper in order to comply with the caller's expectations
|
503
|
+
# Specifically we add the dependency at the specified updated version
|
504
|
+
# then check the output of the add command for Peer Dependency errors (Denoted by YN0060)
|
505
|
+
# If we find peer dependency issues, we raise HelperSubprocessFailed as
|
506
|
+
# the native helpers do.
|
507
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
508
|
+
Dir.chdir(path) do
|
509
|
+
output = Helpers.run_yarn_command(
|
510
|
+
"yarn add #{dependency.name}@#{version} #{Helpers.yarn_berry_args}".strip
|
511
|
+
)
|
512
|
+
if output.include?("YN0060")
|
513
|
+
raise SharedHelpers::HelperSubprocessFailed.new(
|
514
|
+
message: output,
|
515
|
+
error_context: {}
|
516
|
+
)
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
478
522
|
def run_yarn_checker(path:, version:)
|
479
523
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
480
524
|
Dir.chdir(path) do
|
@@ -21,6 +21,7 @@ module Dependabot
|
|
21
21
|
@allow_removal = allow_removal
|
22
22
|
end
|
23
23
|
|
24
|
+
# rubocop:disable Metrics/MethodLength
|
24
25
|
# Finds any dependencies in the `package-lock.json` or `npm-shrinkwrap.json` that have
|
25
26
|
# a subdependency on the given dependency that is locked to a vuln version range.
|
26
27
|
#
|
@@ -41,7 +42,10 @@ module Dependabot
|
|
41
42
|
# dependency on the blocking dependency
|
42
43
|
# * :top_level_ancestors [Array<String>] the names of all top-level dependencies with a transitive
|
43
44
|
# dependency on the dependency
|
45
|
+
# * :explanation [String] an explanation for why the project failed the vulnerability auditor run
|
44
46
|
def audit(dependency:, security_advisories:)
|
47
|
+
Dependabot.logger.info("VulnerabilityAuditor: starting audit")
|
48
|
+
|
45
49
|
fix_unavailable = {
|
46
50
|
"dependency_name" => dependency.name,
|
47
51
|
"fix_available" => false,
|
@@ -60,7 +64,10 @@ module Dependabot
|
|
60
64
|
# `npm-shrinkwrap.js`, if present, takes precedence over `package-lock.js`.
|
61
65
|
# Both files use the same format. See https://bit.ly/3lDIAJV for more.
|
62
66
|
lockfile = (dependency_files_builder.shrinkwraps + dependency_files_builder.package_locks).first
|
63
|
-
|
67
|
+
unless lockfile
|
68
|
+
Dependabot.logger.info("VulnerabilityAuditor: missing lockfile")
|
69
|
+
return fix_unavailable
|
70
|
+
end
|
64
71
|
|
65
72
|
vuln_versions = security_advisories.map do |a|
|
66
73
|
{
|
@@ -74,25 +81,37 @@ module Dependabot
|
|
74
81
|
function: "npm:vulnerabilityAuditor",
|
75
82
|
args: [Dir.pwd, vuln_versions]
|
76
83
|
)
|
77
|
-
return fix_unavailable unless viable_audit_result?(audit_result, security_advisories)
|
78
84
|
|
85
|
+
validation_result = validate_audit_result(audit_result, security_advisories)
|
86
|
+
if validation_result != :viable
|
87
|
+
Dependabot.logger.info("VulnerabilityAuditor: audit result not viable: #{validation_result}")
|
88
|
+
fix_unavailable["explanation"] = explain_fix_unavailable(validation_result, dependency)
|
89
|
+
return fix_unavailable
|
90
|
+
end
|
91
|
+
|
92
|
+
Dependabot.logger.info("VulnerabilityAuditor: audit result viable")
|
79
93
|
audit_result
|
80
94
|
end
|
81
95
|
rescue SharedHelpers::HelperSubprocessFailed => e
|
82
96
|
log_helper_subprocess_failure(dependency, e)
|
83
97
|
fix_unavailable
|
84
98
|
end
|
99
|
+
# rubocop:enable Metrics/MethodLength
|
85
100
|
|
86
101
|
private
|
87
102
|
|
88
103
|
attr_reader :dependency_files, :credentials
|
89
104
|
|
90
|
-
def
|
91
|
-
validation_result
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
105
|
+
def explain_fix_unavailable(validation_result, dependency)
|
106
|
+
case validation_result
|
107
|
+
when :fix_unavailable, :dependency_still_vulnerable, :downgrades_dependencies
|
108
|
+
"No patched version available for #{dependency.name}"
|
109
|
+
when :fix_incomplete
|
110
|
+
"The lockfile might be out of sync?"
|
111
|
+
when :vulnerable_dependency_removed
|
112
|
+
"#{dependency.name} was removed in the update. Dependabot is not able to " \
|
113
|
+
"deal with this yet, but you can still upgrade manually."
|
114
|
+
end
|
96
115
|
end
|
97
116
|
|
98
117
|
def validate_audit_result(audit_result, security_advisories)
|
@@ -100,6 +119,7 @@ module Dependabot
|
|
100
119
|
return :vulnerable_dependency_removed if !@allow_removal && vulnerable_dependency_removed?(audit_result)
|
101
120
|
return :dependency_still_vulnerable if dependency_still_vulnerable?(audit_result, security_advisories)
|
102
121
|
return :downgrades_dependencies if downgrades_dependencies?(audit_result)
|
122
|
+
return :fix_incomplete if fix_incomplete?(audit_result)
|
103
123
|
|
104
124
|
:viable
|
105
125
|
end
|
@@ -132,6 +152,11 @@ module Dependabot
|
|
132
152
|
current > target
|
133
153
|
end
|
134
154
|
|
155
|
+
def fix_incomplete?(audit_result)
|
156
|
+
audit_result["fix_updates"].any? { |update| !update.key?("target_version") } ||
|
157
|
+
audit_result["fix_updates"].empty?
|
158
|
+
end
|
159
|
+
|
135
160
|
def log_helper_subprocess_failure(dependency, error)
|
136
161
|
# See `Dependabot::SharedHelpers.run_helper_subprocess` for details on error context
|
137
162
|
context = error.error_context || {}
|