dependabot-npm_and_yarn 0.212.0 → 0.213.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 +2585 -2386
- data/helpers/package.json +4 -4
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +30 -5
- data/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb +19 -4
- data/lib/dependabot/npm_and_yarn/file_parser.rb +17 -5
- 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 +7 -3
- 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 +83 -24
- data/lib/dependabot/npm_and_yarn/file_updater.rb +54 -0
- data/lib/dependabot/npm_and_yarn/helpers.rb +48 -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/latest_version_finder.rb +6 -1
- data/lib/dependabot/npm_and_yarn/update_checker/library_detector.rb +16 -3
- data/lib/dependabot/npm_and_yarn/update_checker/registry_finder.rb +67 -19
- 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 +23 -1
- data/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb +3 -3
- data/lib/dependabot/npm_and_yarn/update_checker/vulnerability_auditor.rb +33 -8
- data/lib/dependabot/npm_and_yarn/update_checker.rb +72 -19
- data/lib/dependabot/npm_and_yarn/version.rb +1 -1
- metadata +13 -55
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "dependabot/experiments"
|
3
4
|
require "dependabot/file_updaters"
|
4
5
|
require "dependabot/file_updaters/base"
|
6
|
+
require "dependabot/file_updaters/vendor_updater"
|
5
7
|
require "dependabot/npm_and_yarn/dependency_files_filterer"
|
6
8
|
require "dependabot/npm_and_yarn/sub_dependency_files_filterer"
|
7
9
|
|
@@ -53,11 +55,62 @@ module Dependabot
|
|
53
55
|
)
|
54
56
|
end
|
55
57
|
|
58
|
+
base_dir = updated_files.first.directory
|
59
|
+
vendor_updater.updated_vendor_cache_files(base_directory: base_dir).each { |file| updated_files << file }
|
60
|
+
install_state_updater.updated_vendor_cache_files(base_directory: base_dir).each do |file|
|
61
|
+
updated_files << file
|
62
|
+
end
|
63
|
+
pnp_updater.updated_vendor_cache_files(base_directory: base_dir).each do |file|
|
64
|
+
updated_files << file if file.name == ".pnp.cjs" || file.name == ".pnp.data.json"
|
65
|
+
end
|
66
|
+
|
56
67
|
updated_files
|
57
68
|
end
|
58
69
|
|
59
70
|
private
|
60
71
|
|
72
|
+
# Dynamically fetch the vendor cache folder from yarn
|
73
|
+
def vendor_cache_dir
|
74
|
+
return @vendor_cache_dir if defined?(@vendor_cache_dir)
|
75
|
+
|
76
|
+
@vendor_cache_dir = if File.exist?(".yarnrc.yml")
|
77
|
+
YAML.load_file(".yarnrc.yml").fetch("cacheFolder", "./.yarn/cache")
|
78
|
+
else
|
79
|
+
"./.yarn/cache"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def install_state_path
|
84
|
+
return @install_state_path if defined?(@install_state_path)
|
85
|
+
|
86
|
+
@install_state_path = if File.exist?(".yarnrc.yml")
|
87
|
+
YAML.load_file(".yarnrc.yml").fetch("installStatePath", "./.yarn/install-state.gz")
|
88
|
+
else
|
89
|
+
"./.yarn/install-state.gz"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def vendor_updater
|
94
|
+
Dependabot::FileUpdaters::VendorUpdater.new(
|
95
|
+
repo_contents_path: repo_contents_path,
|
96
|
+
vendor_dir: vendor_cache_dir
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def install_state_updater
|
101
|
+
Dependabot::FileUpdaters::VendorUpdater.new(
|
102
|
+
repo_contents_path: repo_contents_path,
|
103
|
+
vendor_dir: install_state_path
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def pnp_updater
|
108
|
+
Dependabot::FileUpdaters::VendorUpdater.new(
|
109
|
+
repo_contents_path: repo_contents_path,
|
110
|
+
vendor_dir: "./"
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
61
114
|
def filtered_dependency_files
|
62
115
|
@filtered_dependency_files ||=
|
63
116
|
if dependencies.select(&:top_level?).any?
|
@@ -175,6 +228,7 @@ module Dependabot
|
|
175
228
|
YarnLockfileUpdater.new(
|
176
229
|
dependencies: dependencies,
|
177
230
|
dependency_files: dependency_files,
|
231
|
+
repo_contents_path: repo_contents_path,
|
178
232
|
credentials: credentials
|
179
233
|
)
|
180
234
|
end
|
@@ -15,6 +15,54 @@ module Dependabot
|
|
15
15
|
rescue JSON::ParserError
|
16
16
|
6
|
17
17
|
end
|
18
|
+
|
19
|
+
# Run any number of yarn commands while ensuring that `enableScripts` is
|
20
|
+
# set to false. Yarn commands should _not_ be ran outside of this helper
|
21
|
+
# to ensure that postinstall scripts are never executed, as they could
|
22
|
+
# contain malicious code.
|
23
|
+
def self.run_yarn_commands(*commands)
|
24
|
+
# We never want to execute postinstall scripts
|
25
|
+
SharedHelpers.run_shell_command("yarn config set enableScripts false")
|
26
|
+
if (http_proxy = ENV.fetch("HTTP_PROXY", false))
|
27
|
+
SharedHelpers.run_shell_command("yarn config set httpProxy #{http_proxy}")
|
28
|
+
end
|
29
|
+
if (https_proxy = ENV.fetch("HTTPS_PROXY", false))
|
30
|
+
SharedHelpers.run_shell_command("yarn config set httpsProxy #{https_proxy}")
|
31
|
+
end
|
32
|
+
if (ca_file_path = ENV.fetch("NODE_EXTRA_CA_CERTS", false))
|
33
|
+
output = SharedHelpers.run_shell_command("yarn --version")
|
34
|
+
major_version = Version.new(output).major
|
35
|
+
if major_version >= 4
|
36
|
+
SharedHelpers.run_shell_command("yarn config set httpsCaFilePath #{ca_file_path}")
|
37
|
+
else
|
38
|
+
SharedHelpers.run_shell_command("yarn config set caFilePath #{ca_file_path}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
commands.each { |cmd| SharedHelpers.run_shell_command(cmd) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.dependencies_with_all_versions_metadata(dependency_set)
|
45
|
+
working_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
46
|
+
dependencies = []
|
47
|
+
|
48
|
+
names = dependency_set.dependencies.map(&:name)
|
49
|
+
names.each do |name|
|
50
|
+
all_versions = dependency_set.all_versions_for_name(name)
|
51
|
+
all_versions.each do |dep|
|
52
|
+
metadata_versions = dep.metadata.fetch(:all_versions, [])
|
53
|
+
if metadata_versions.any?
|
54
|
+
metadata_versions.each { |a| working_set << a }
|
55
|
+
else
|
56
|
+
working_set << dep
|
57
|
+
end
|
58
|
+
end
|
59
|
+
dependency = working_set.dependency_for_name(name)
|
60
|
+
dependency.metadata[:all_versions] = working_set.all_versions_for_name(name)
|
61
|
+
dependencies << dependency
|
62
|
+
end
|
63
|
+
|
64
|
+
dependencies
|
65
|
+
end
|
18
66
|
end
|
19
67
|
end
|
20
68
|
end
|
@@ -18,7 +18,7 @@ module Dependabot
|
|
18
18
|
[a-z0-9\-\_\.\!\~\*\'\(\)]+ # URL-safe characters
|
19
19
|
)
|
20
20
|
\z # end of string
|
21
|
-
}xi
|
21
|
+
}xi # multi-line/case-insensitive
|
22
22
|
|
23
23
|
TYPES_PACKAGE_NAME_REGEX = %r{
|
24
24
|
\A # beginning of string
|
@@ -26,7 +26,7 @@ module Dependabot
|
|
26
26
|
((?<scope>.+)__)? # capture scope
|
27
27
|
(?<name>.+) # capture name
|
28
28
|
\z # end of string
|
29
|
-
}xi
|
29
|
+
}xi # multi-line/case-insensitive
|
30
30
|
|
31
31
|
class InvalidPackageName < StandardError; end
|
32
32
|
|
@@ -6,8 +6,8 @@ require "dependabot/npm_and_yarn/version"
|
|
6
6
|
module Dependabot
|
7
7
|
module NpmAndYarn
|
8
8
|
class Requirement < Gem::Requirement
|
9
|
-
AND_SEPARATOR = /(?<=[a-zA-Z0-9*])\s+(?:&+\s+)?(?!\s*[|-])
|
10
|
-
OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s
|
9
|
+
AND_SEPARATOR = /(?<=[a-zA-Z0-9*])\s+(?:&+\s+)?(?!\s*[|-])/
|
10
|
+
OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/
|
11
11
|
LATEST_REQUIREMENT = "latest"
|
12
12
|
|
13
13
|
# Override the version pattern to allow a 'v' prefix
|
@@ -15,7 +15,7 @@ module Dependabot
|
|
15
15
|
version_pattern = "v?#{NpmAndYarn::Version::VERSION_PATTERN}"
|
16
16
|
|
17
17
|
PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*"
|
18
|
-
PATTERN = /\A#{PATTERN_RAW}\z
|
18
|
+
PATTERN = /\A#{PATTERN_RAW}\z/
|
19
19
|
|
20
20
|
def self.parse(obj)
|
21
21
|
return ["=", nil] if obj.is_a?(String) && obj.strip == LATEST_REQUIREMENT
|
@@ -371,7 +371,8 @@ module Dependabot
|
|
371
371
|
dependency: dependency,
|
372
372
|
credentials: credentials,
|
373
373
|
npmrc_file: npmrc_file,
|
374
|
-
yarnrc_file: yarnrc_file
|
374
|
+
yarnrc_file: yarnrc_file,
|
375
|
+
yarnrc_yml_file: yarnrc_yml_file
|
375
376
|
)
|
376
377
|
end
|
377
378
|
|
@@ -395,6 +396,10 @@ module Dependabot
|
|
395
396
|
dependency_files.find { |f| f.name.end_with?(".yarnrc") }
|
396
397
|
end
|
397
398
|
|
399
|
+
def yarnrc_yml_file
|
400
|
+
dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") }
|
401
|
+
end
|
402
|
+
|
398
403
|
# TODO: Remove need for me
|
399
404
|
def git_dependency?
|
400
405
|
# ignored_version/raise_on_ignored are irrelevant.
|
@@ -8,8 +8,10 @@ module Dependabot
|
|
8
8
|
module NpmAndYarn
|
9
9
|
class UpdateChecker
|
10
10
|
class LibraryDetector
|
11
|
-
def initialize(package_json_file:)
|
11
|
+
def initialize(package_json_file:, credentials:, dependency_files:)
|
12
12
|
@package_json_file = package_json_file
|
13
|
+
@credentials = credentials
|
14
|
+
@dependency_files = dependency_files
|
13
15
|
end
|
14
16
|
|
15
17
|
def library?
|
@@ -20,7 +22,7 @@ module Dependabot
|
|
20
22
|
|
21
23
|
private
|
22
24
|
|
23
|
-
attr_reader :package_json_file
|
25
|
+
attr_reader :package_json_file, :credentials, :dependency_files
|
24
26
|
|
25
27
|
def package_json_may_be_for_library?
|
26
28
|
return false unless project_name
|
@@ -36,7 +38,8 @@ module Dependabot
|
|
36
38
|
return false unless project_description
|
37
39
|
|
38
40
|
# Check if the project is listed on npm. If it is, it's a library
|
39
|
-
|
41
|
+
url = "#{registry.chomp('/')}/#{escaped_project_name}"
|
42
|
+
@project_npm_response ||= Dependabot::RegistryClient.get(url: url)
|
40
43
|
return false unless @project_npm_response.status == 200
|
41
44
|
|
42
45
|
@project_npm_response.body.force_encoding("UTF-8").encode.
|
@@ -56,6 +59,16 @@ module Dependabot
|
|
56
59
|
def parsed_package_json
|
57
60
|
@parsed_package_json ||= JSON.parse(package_json_file.content)
|
58
61
|
end
|
62
|
+
|
63
|
+
def registry
|
64
|
+
NpmAndYarn::UpdateChecker::RegistryFinder.new(
|
65
|
+
dependency: nil,
|
66
|
+
credentials: credentials,
|
67
|
+
npmrc_file: dependency_files.find { |f| f.name.end_with?(".npmrc") },
|
68
|
+
yarnrc_file: dependency_files.find { |f| f.name.end_with?(".yarnrc") },
|
69
|
+
yarnrc_yml_file: dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") }
|
70
|
+
).registry_from_rc(project_name)
|
71
|
+
end
|
59
72
|
end
|
60
73
|
end
|
61
74
|
end
|
@@ -13,19 +13,19 @@ module Dependabot
|
|
13
13
|
http://registry.npmjs.org
|
14
14
|
https://registry.yarnpkg.com
|
15
15
|
).freeze
|
16
|
-
NPM_AUTH_TOKEN_REGEX =
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
/^(?:--)?registry\s+['"](?<registry>.*)['"]/.freeze
|
16
|
+
NPM_AUTH_TOKEN_REGEX = %r{//(?<registry>.*)/:_authToken=(?<token>.*)$}
|
17
|
+
NPM_GLOBAL_REGISTRY_REGEX = /^registry\s*=\s*['"]?(?<registry>.*?)['"]?$/
|
18
|
+
YARN_GLOBAL_REGISTRY_REGEX = /^(?:--)?registry\s+((['"](?<registry>.*)['"])|(?<registry>.*))/
|
19
|
+
NPM_SCOPED_REGISTRY_REGEX = /^(?<scope>@[^:]+)\s*:registry\s*=\s*['"]?(?<registry>.*?)['"]?$/
|
20
|
+
YARN_SCOPED_REGISTRY_REGEX = /['"](?<scope>@[^:]+):registry['"]\s((['"](?<registry>.*)['"])|(?<registry>.*))/
|
22
21
|
|
23
22
|
def initialize(dependency:, credentials:, npmrc_file: nil,
|
24
|
-
yarnrc_file: nil)
|
23
|
+
yarnrc_file: nil, yarnrc_yml_file: nil)
|
25
24
|
@dependency = dependency
|
26
25
|
@credentials = credentials
|
27
26
|
@npmrc_file = npmrc_file
|
28
27
|
@yarnrc_file = yarnrc_file
|
28
|
+
@yarnrc_yml_file = yarnrc_yml_file
|
29
29
|
end
|
30
30
|
|
31
31
|
def registry
|
@@ -46,15 +46,24 @@ module Dependabot
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
def registry_from_rc(dependency_name)
|
50
|
+
return global_registry unless dependency_name.start_with?("@") && dependency_name.include?("/")
|
51
|
+
|
52
|
+
scope = dependency_name.split("/").first
|
53
|
+
scoped_registry(scope)
|
54
|
+
end
|
55
|
+
|
49
56
|
private
|
50
57
|
|
51
|
-
attr_reader :dependency, :credentials, :npmrc_file, :yarnrc_file
|
58
|
+
attr_reader :dependency, :credentials, :npmrc_file, :yarnrc_file, :yarnrc_yml_file
|
52
59
|
|
53
60
|
def first_registry_with_dependency_details
|
54
61
|
@first_registry_with_dependency_details ||=
|
55
62
|
known_registries.find do |details|
|
63
|
+
url = "#{details['registry'].gsub(%r{/+$}, '')}/#{escaped_dependency_name}"
|
64
|
+
url = "https://#{url}" unless url.start_with?("http")
|
56
65
|
response = Dependabot::RegistryClient.get(
|
57
|
-
url:
|
66
|
+
url: url,
|
58
67
|
headers: auth_header_for(details["token"])
|
59
68
|
)
|
60
69
|
response.status < 400 && JSON.parse(response.body)
|
@@ -64,10 +73,12 @@ module Dependabot
|
|
64
73
|
nil
|
65
74
|
end&.fetch("registry")
|
66
75
|
|
67
|
-
@first_registry_with_dependency_details ||= global_registry
|
76
|
+
@first_registry_with_dependency_details ||= global_registry.sub(%r{/+$}, "").sub(%r{^.*?//}, "")
|
68
77
|
end
|
69
78
|
|
70
79
|
def registry_url
|
80
|
+
return registry if registry.start_with?("http")
|
81
|
+
|
71
82
|
protocol =
|
72
83
|
if registry_source_url
|
73
84
|
registry_source_url.split("://").first
|
@@ -190,26 +201,56 @@ module Dependabot
|
|
190
201
|
end
|
191
202
|
end
|
192
203
|
|
204
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
193
205
|
def global_registry
|
206
|
+
return @global_registry if defined? @global_registry
|
207
|
+
|
194
208
|
npmrc_file&.content.to_s.scan(NPM_GLOBAL_REGISTRY_REGEX) do
|
195
209
|
next if Regexp.last_match[:registry].include?("${")
|
196
210
|
|
197
|
-
|
198
|
-
sub(%r{/+$}, "").
|
199
|
-
sub(%r{^.*?//}, "")
|
200
|
-
return registry
|
211
|
+
return @global_registry = Regexp.last_match[:registry].strip
|
201
212
|
end
|
202
213
|
|
203
214
|
yarnrc_file&.content.to_s.scan(YARN_GLOBAL_REGISTRY_REGEX) do
|
204
215
|
next if Regexp.last_match[:registry].include?("${")
|
205
216
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
217
|
+
return @global_registry = Regexp.last_match[:registry].strip
|
218
|
+
end
|
219
|
+
|
220
|
+
if parsed_yarnrc_yml&.key?("npmRegistryServer")
|
221
|
+
return @global_registry = parsed_yarnrc_yml["npmRegistryServer"]
|
222
|
+
end
|
223
|
+
|
224
|
+
replaces_base = credentials.find { |cred| cred["type"] == "npm_registry" && cred["replaces-base"] == true }
|
225
|
+
if replaces_base
|
226
|
+
registry = replaces_base["registry"]
|
227
|
+
registry = "https://#{registry}" unless registry.start_with?("http")
|
228
|
+
return @global_registry = registry
|
229
|
+
end
|
230
|
+
|
231
|
+
"https://registry.npmjs.org"
|
232
|
+
end
|
233
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
234
|
+
|
235
|
+
def scoped_registry(scope)
|
236
|
+
npmrc_file&.content.to_s.scan(NPM_SCOPED_REGISTRY_REGEX) do
|
237
|
+
next if Regexp.last_match[:registry].include?("${") || Regexp.last_match[:scope] != scope
|
238
|
+
|
239
|
+
return Regexp.last_match[:registry].strip
|
240
|
+
end
|
241
|
+
|
242
|
+
yarnrc_file&.content.to_s.scan(YARN_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
|
+
if parsed_yarnrc_yml
|
249
|
+
yarn_berry_registry = parsed_yarnrc_yml.dig("npmScopes", scope.delete_prefix("@"), "npmRegistryServer")
|
250
|
+
return yarn_berry_registry if yarn_berry_registry
|
210
251
|
end
|
211
252
|
|
212
|
-
|
253
|
+
global_registry
|
213
254
|
end
|
214
255
|
|
215
256
|
# npm registries expect slashes to be escaped
|
@@ -224,6 +265,13 @@ module Dependabot
|
|
224
265
|
|
225
266
|
sources.find { |s| s[:type] == "registry" }&.fetch(:url)
|
226
267
|
end
|
268
|
+
|
269
|
+
def parsed_yarnrc_yml
|
270
|
+
return unless yarnrc_yml_file
|
271
|
+
return @parsed_yarnrc_yml if defined? @parsed_yarnrc_yml
|
272
|
+
|
273
|
+
@parsed_yarnrc_yml = YAML.safe_load(yarnrc_yml_file.content)
|
274
|
+
end
|
227
275
|
end
|
228
276
|
end
|
229
277
|
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:)
|
@@ -59,7 +59,9 @@ module Dependabot
|
|
59
59
|
lockfile_name = Pathname.new(lockfile.name).basename.to_s
|
60
60
|
path = Pathname.new(lockfile.name).dirname.to_s
|
61
61
|
|
62
|
-
updated_files = if lockfile.name.end_with?("yarn.lock")
|
62
|
+
updated_files = if lockfile.name.end_with?("yarn.lock") && yarn_berry?(lockfile)
|
63
|
+
run_yarn_berry_updater(path, lockfile_name)
|
64
|
+
elsif lockfile.name.end_with?("yarn.lock")
|
63
65
|
run_yarn_updater(path, lockfile_name)
|
64
66
|
else
|
65
67
|
run_npm_updater(path, lockfile_name, lockfile.content)
|
@@ -68,6 +70,15 @@ module Dependabot
|
|
68
70
|
updated_files.fetch(lockfile_name)
|
69
71
|
end
|
70
72
|
|
73
|
+
def yarn_berry?(yarn_lock)
|
74
|
+
return false unless Experiments.enabled?(:yarn_berry)
|
75
|
+
|
76
|
+
yaml = YAML.safe_load(yarn_lock.content)
|
77
|
+
yaml.key?("__metadata")
|
78
|
+
rescue StandardError
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
71
82
|
def version_from_updated_lockfiles(updated_lockfiles)
|
72
83
|
updated_files = dependency_files -
|
73
84
|
dependency_files_builder.yarn_locks -
|
@@ -109,6 +120,17 @@ module Dependabot
|
|
109
120
|
sleep(rand(3.0..10.0)) && retry
|
110
121
|
end
|
111
122
|
|
123
|
+
def run_yarn_berry_updater(path, lockfile_name)
|
124
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
125
|
+
Dir.chdir(path) do
|
126
|
+
Helpers.run_yarn_commands(
|
127
|
+
"yarn up -R #{dependency.name}"
|
128
|
+
)
|
129
|
+
{ lockfile_name => File.read(lockfile_name) }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
112
134
|
def run_npm_updater(path, lockfile_name, lockfile_content)
|
113
135
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
114
136
|
Dir.chdir(path) do
|
@@ -36,7 +36,7 @@ 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
40
|
|
41
41
|
# Error message from npm install:
|
42
42
|
# react-dom@15.2.0 requires a peer of react@^15.2.0 \
|
@@ -46,7 +46,7 @@ module Dependabot
|
|
46
46
|
(?<requiring_dep>[^\s]+)\s
|
47
47
|
requires\sa\speer\sof\s
|
48
48
|
(?<required_dep>.+?)\sbut\snone\sis\sinstalled.
|
49
|
-
/x
|
49
|
+
/x
|
50
50
|
|
51
51
|
# Error message from npm install:
|
52
52
|
# npm ERR! Could not resolve dependency:
|
@@ -59,7 +59,7 @@ module Dependabot
|
|
59
59
|
/
|
60
60
|
npm\s(?:WARN|ERR!)\sCould\snot\sresolve\sdependency:\n
|
61
61
|
npm\s(?:WARN|ERR!)\speer\s(?<required_dep>\S+@\S+(\s\S+)?)\sfrom\s(?<requiring_dep>\S+@\S+)
|
62
|
-
/x
|
62
|
+
/x
|
63
63
|
|
64
64
|
def initialize(dependency:, credentials:, dependency_files:,
|
65
65
|
latest_allowable_version:, latest_version_finder:)
|
@@ -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 || {}
|