dependabot-npm_and_yarn 0.381.0 → 0.382.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/lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb +33 -1
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_workspace_updater.rb +10 -35
- data/lib/dependabot/npm_and_yarn/helpers.rb +5 -0
- data/lib/dependabot/npm_and_yarn/metadata_finder.rb +12 -20
- data/lib/dependabot/npm_and_yarn/package/package_details_fetcher.rb +16 -1
- data/lib/dependabot/npm_and_yarn/package/registry_credential_helpers.rb +64 -0
- data/lib/dependabot/npm_and_yarn/package/registry_finder.rb +33 -17
- data/lib/dependabot/npm_and_yarn/registry_parser.rb +53 -17
- data/lib/dependabot/npm_and_yarn/requirement.rb +1 -1
- data/lib/dependabot/npm_and_yarn/update_checker/vulnerability_auditor.rb +57 -5
- data/lib/dependabot/npm_and_yarn/update_checker.rb +10 -8
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6b62776c4e4f4808215eb847c97e755f49062eda411ee412828ed1d29569378
|
|
4
|
+
data.tar.gz: 96ce53758c58b319deba6b6611cdff40a9a5673c66b6a0b066eb11410fd5a83d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: faa9c39442466c97715c626036b1a711217b8e6c4b5dcd1b9438fc63e4009c20acab40a924c52bccf62d56656b3e7f5d3ed79c83e7ebb6a5053407865bd0d883
|
|
7
|
+
data.tar.gz: 3be31e18189e2e9a541fd884cbc91cf4a83e732a887da56b033785539b4f9e0f332f62774548c62487a1b0ac487060f377c9a9cb3138d232fdb33dfff3f15667
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
5
|
|
|
6
6
|
require "dependabot/npm_and_yarn/file_updater"
|
|
7
|
+
require "dependabot/npm_and_yarn/helpers"
|
|
7
8
|
|
|
8
9
|
module Dependabot
|
|
9
10
|
module NpmAndYarn
|
|
@@ -44,7 +45,7 @@ module Dependabot
|
|
|
44
45
|
if npmrc_file then complete_npmrc_from_credentials
|
|
45
46
|
elsif yarnrc_file then build_npmrc_from_yarnrc
|
|
46
47
|
else
|
|
47
|
-
build_npmrc_content_from_lockfile
|
|
48
|
+
build_npmrc_content_from_lockfile || build_npmrc_content_from_credential_scopes
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
final_content = initial_content || ""
|
|
@@ -98,6 +99,23 @@ module Dependabot
|
|
|
98
99
|
"always-auth = true"
|
|
99
100
|
end
|
|
100
101
|
|
|
102
|
+
sig { returns(T.nilable(String)) }
|
|
103
|
+
def build_npmrc_content_from_credential_scopes
|
|
104
|
+
scoped_credentials = registry_credentials.select { |cred| cred.scope && cred["registry"] }
|
|
105
|
+
return if scoped_credentials.empty?
|
|
106
|
+
|
|
107
|
+
lines = T.let([], T::Array[String])
|
|
108
|
+
scoped_credentials.each do |cred|
|
|
109
|
+
registry = cred.fetch("registry")
|
|
110
|
+
registry_url = registry.start_with?("http") ? registry : "https://#{registry}"
|
|
111
|
+
T.must(cred.scope).each do |s|
|
|
112
|
+
lines << "#{Helpers.normalize_npm_scope(s)}:registry=#{registry_url}"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
lines.join("\n")
|
|
117
|
+
end
|
|
118
|
+
|
|
101
119
|
sig { returns(T.nilable(String)) }
|
|
102
120
|
def build_yarnrc_content_from_lockfile
|
|
103
121
|
return unless yarn_lock || package_lock
|
|
@@ -334,11 +352,25 @@ module Dependabot
|
|
|
334
352
|
)
|
|
335
353
|
end
|
|
336
354
|
|
|
355
|
+
sig { params(registry: String).returns(T.nilable(T::Array[String])) }
|
|
356
|
+
def credential_scopes_for(registry)
|
|
357
|
+
cred = registry_credentials.find { |c| c["registry"] == registry }
|
|
358
|
+
return unless cred&.scope
|
|
359
|
+
|
|
360
|
+
registry_url = registry.start_with?("http") ? registry : "https://#{registry}"
|
|
361
|
+
T.must(cred.scope).map { |s| "#{Helpers.normalize_npm_scope(s)}:registry=#{registry_url}" }
|
|
362
|
+
end
|
|
363
|
+
|
|
337
364
|
# rubocop:disable Metrics/PerceivedComplexity
|
|
338
365
|
sig { params(registry: String).returns(T.nilable(T::Array[String])) }
|
|
339
366
|
def registry_scopes(registry)
|
|
340
367
|
# Central registries don't just apply to scopes
|
|
341
368
|
return if CENTRAL_REGISTRIES.include?(registry)
|
|
369
|
+
|
|
370
|
+
# Use explicit scope from credential if available
|
|
371
|
+
explicit = credential_scopes_for(registry)
|
|
372
|
+
return explicit if explicit
|
|
373
|
+
|
|
342
374
|
return unless dependency_urls
|
|
343
375
|
|
|
344
376
|
other_regs =
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "dependabot/npm_and_yarn/helpers"
|
|
@@ -6,13 +6,6 @@ require "dependabot/npm_and_yarn/package/registry_finder"
|
|
|
6
6
|
require "dependabot/npm_and_yarn/registry_parser"
|
|
7
7
|
require "dependabot/shared_helpers"
|
|
8
8
|
|
|
9
|
-
class DependencyRequirement < T::Struct
|
|
10
|
-
const :file, String
|
|
11
|
-
const :requirement, String
|
|
12
|
-
const :groups, T::Array[String]
|
|
13
|
-
const :source, T.nilable(T::Hash[Symbol, T.untyped])
|
|
14
|
-
end
|
|
15
|
-
|
|
16
9
|
module Dependabot
|
|
17
10
|
module NpmAndYarn
|
|
18
11
|
class FileUpdater
|
|
@@ -73,13 +66,13 @@ module Dependabot
|
|
|
73
66
|
params(
|
|
74
67
|
content: String,
|
|
75
68
|
dependency: Dependabot::Dependency,
|
|
76
|
-
old_requirement: DependencyRequirement,
|
|
77
|
-
new_requirement: DependencyRequirement
|
|
69
|
+
old_requirement: Dependabot::DependencyRequirement,
|
|
70
|
+
new_requirement: Dependabot::DependencyRequirement
|
|
78
71
|
).returns(String)
|
|
79
72
|
end
|
|
80
73
|
def replace_version_in_content(content:, dependency:, old_requirement:, new_requirement:)
|
|
81
|
-
old_version = old_requirement.requirement
|
|
82
|
-
new_version = new_requirement.requirement
|
|
74
|
+
old_version = T.must(old_requirement.requirement)
|
|
75
|
+
new_version = T.must(new_requirement.requirement)
|
|
83
76
|
|
|
84
77
|
pattern = build_replacement_pattern(
|
|
85
78
|
dependency_name: dependency.name,
|
|
@@ -104,37 +97,19 @@ module Dependabot
|
|
|
104
97
|
"\\1#{dependency_name}\\1: \\2#{version}\\2"
|
|
105
98
|
end
|
|
106
99
|
|
|
107
|
-
sig { params(dependency: Dependabot::Dependency).returns(T::Array[DependencyRequirement]) }
|
|
100
|
+
sig { params(dependency: Dependabot::Dependency).returns(T::Array[Dependabot::DependencyRequirement]) }
|
|
108
101
|
def new_requirements(dependency)
|
|
109
|
-
dependency.requirements
|
|
110
|
-
.select { |r| r[:file] == workspace_file.name }
|
|
111
|
-
.map do |r|
|
|
112
|
-
DependencyRequirement.new(
|
|
113
|
-
file: r[:file],
|
|
114
|
-
requirement: r[:requirement],
|
|
115
|
-
groups: r[:groups],
|
|
116
|
-
source: r[:source]
|
|
117
|
-
)
|
|
118
|
-
end
|
|
102
|
+
dependency.requirements.select { |r| r.file == workspace_file.name }
|
|
119
103
|
end
|
|
120
104
|
|
|
121
105
|
sig do
|
|
122
106
|
params(
|
|
123
107
|
dependency: Dependabot::Dependency,
|
|
124
|
-
new_requirement: DependencyRequirement
|
|
125
|
-
).returns(T.nilable(DependencyRequirement))
|
|
108
|
+
new_requirement: Dependabot::DependencyRequirement
|
|
109
|
+
).returns(T.nilable(Dependabot::DependencyRequirement))
|
|
126
110
|
end
|
|
127
111
|
def old_requirement(dependency, new_requirement)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return nil if matching_req.nil?
|
|
131
|
-
|
|
132
|
-
DependencyRequirement.new(
|
|
133
|
-
file: matching_req[:file],
|
|
134
|
-
requirement: matching_req[:requirement],
|
|
135
|
-
groups: matching_req[:groups],
|
|
136
|
-
source: matching_req[:source]
|
|
137
|
-
)
|
|
112
|
+
T.must(dependency.previous_requirements).find { |r| r.groups == new_requirement.groups }
|
|
138
113
|
end
|
|
139
114
|
end
|
|
140
115
|
end
|
|
@@ -634,6 +634,11 @@ module Dependabot
|
|
|
634
634
|
def self.corepack_supported_package_manager?(name)
|
|
635
635
|
SUPPORTED_COREPACK_PACKAGE_MANAGERS.include?(name)
|
|
636
636
|
end
|
|
637
|
+
|
|
638
|
+
sig { params(scope: String).returns(String) }
|
|
639
|
+
def self.normalize_npm_scope(scope)
|
|
640
|
+
scope.start_with?("@") ? scope : "@#{scope}"
|
|
641
|
+
end
|
|
637
642
|
end
|
|
638
643
|
end
|
|
639
644
|
end
|
|
@@ -9,12 +9,14 @@ require "dependabot/metadata_finders"
|
|
|
9
9
|
require "dependabot/metadata_finders/base"
|
|
10
10
|
require "dependabot/registry_client"
|
|
11
11
|
require "dependabot/npm_and_yarn/package/registry_finder"
|
|
12
|
+
require "dependabot/npm_and_yarn/package/registry_credential_helpers"
|
|
12
13
|
require "dependabot/npm_and_yarn/version"
|
|
13
14
|
|
|
14
15
|
module Dependabot
|
|
15
16
|
module NpmAndYarn
|
|
16
17
|
class MetadataFinder < Dependabot::MetadataFinders::Base
|
|
17
18
|
extend T::Sig
|
|
19
|
+
include Package::RegistryCredentialHelpers
|
|
18
20
|
|
|
19
21
|
# Lifecycle scripts that run automatically during package installation.
|
|
20
22
|
# These are security-relevant because they execute with user privileges.
|
|
@@ -310,9 +312,11 @@ module Dependabot
|
|
|
310
312
|
sig { returns(String) }
|
|
311
313
|
def dependency_url
|
|
312
314
|
registry_url =
|
|
313
|
-
if
|
|
314
|
-
#
|
|
315
|
-
|
|
315
|
+
if (configured_registry = configured_registry_from_credentials)
|
|
316
|
+
# Prioritize replaces-base credential over lockfile source
|
|
317
|
+
configured_registry
|
|
318
|
+
elsif new_source.nil?
|
|
319
|
+
"https://registry.npmjs.org"
|
|
316
320
|
else
|
|
317
321
|
new_source&.fetch(:url)
|
|
318
322
|
end
|
|
@@ -332,25 +336,13 @@ module Dependabot
|
|
|
332
336
|
{ "Authorization" => "Bearer #{auth_token}" }
|
|
333
337
|
end
|
|
334
338
|
|
|
335
|
-
sig { returns(T.nilable(String)) }
|
|
336
|
-
def configured_registry_from_credentials
|
|
337
|
-
# Look for a credential that replaces the base registry (global registry replacement)
|
|
338
|
-
replaces_base_cred = credentials.find { |cred| cred["type"] == "npm_registry" && cred.replaces_base? }
|
|
339
|
-
return normalize_registry_url(replaces_base_cred["registry"]) if replaces_base_cred
|
|
340
|
-
|
|
341
|
-
nil
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
sig { params(registry: T.nilable(String)).returns(T.nilable(String)) }
|
|
345
|
-
def normalize_registry_url(registry)
|
|
346
|
-
return nil unless registry
|
|
347
|
-
return registry if registry.start_with?("http")
|
|
348
|
-
|
|
349
|
-
"https://#{registry}"
|
|
350
|
-
end
|
|
351
|
-
|
|
352
339
|
sig { returns(String) }
|
|
353
340
|
def dependency_registry
|
|
341
|
+
# Prioritize replaces-base credential over lockfile source
|
|
342
|
+
if (configured_registry = configured_registry_from_credentials)
|
|
343
|
+
return configured_registry.sub(%r{^https?://}, "")
|
|
344
|
+
end
|
|
345
|
+
|
|
354
346
|
if new_source.nil? then "registry.npmjs.org"
|
|
355
347
|
else
|
|
356
348
|
new_source&.fetch(:url)&.gsub("https://", "")&.gsub("http://", "")
|
|
@@ -7,12 +7,14 @@ require "time"
|
|
|
7
7
|
require "dependabot/package/package_release"
|
|
8
8
|
require "dependabot/package/package_details"
|
|
9
9
|
require "dependabot/npm_and_yarn/package/registry_finder"
|
|
10
|
+
require "dependabot/npm_and_yarn/package/registry_credential_helpers"
|
|
10
11
|
|
|
11
12
|
module Dependabot
|
|
12
13
|
module NpmAndYarn
|
|
13
14
|
module Package
|
|
14
15
|
class PackageDetailsFetcher
|
|
15
16
|
extend T::Sig
|
|
17
|
+
include RegistryCredentialHelpers
|
|
16
18
|
|
|
17
19
|
GLOBAL_REGISTRY = "registry.npmjs.org"
|
|
18
20
|
NPM_OFFICIAL_WEBSITE = "https://www.npmjs.com"
|
|
@@ -63,7 +65,7 @@ module Dependabot
|
|
|
63
65
|
sig { returns(Dependabot::Dependency) }
|
|
64
66
|
attr_reader :dependency
|
|
65
67
|
|
|
66
|
-
sig { returns(T::Array[Dependabot::Credential]) }
|
|
68
|
+
sig { override.returns(T::Array[Dependabot::Credential]) }
|
|
67
69
|
attr_reader :credentials
|
|
68
70
|
|
|
69
71
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
@@ -96,6 +98,11 @@ module Dependabot
|
|
|
96
98
|
|
|
97
99
|
sig { returns(String) }
|
|
98
100
|
def dependency_url
|
|
101
|
+
if (configured_registry = configured_registry_from_credentials)
|
|
102
|
+
escaped_dependency_name = dependency.name.gsub("/", "%2F")
|
|
103
|
+
return "#{configured_registry}/#{escaped_dependency_name}"
|
|
104
|
+
end
|
|
105
|
+
|
|
99
106
|
registry_finder.dependency_url
|
|
100
107
|
end
|
|
101
108
|
|
|
@@ -381,11 +388,19 @@ module Dependabot
|
|
|
381
388
|
|
|
382
389
|
sig { returns(T::Hash[String, String]) }
|
|
383
390
|
def registry_auth_headers
|
|
391
|
+
if (configured_registry = configured_registry_from_credentials)
|
|
392
|
+
return auth_headers_for_registry(configured_registry)
|
|
393
|
+
end
|
|
394
|
+
|
|
384
395
|
registry_finder.auth_headers
|
|
385
396
|
end
|
|
386
397
|
|
|
387
398
|
sig { returns(String) }
|
|
388
399
|
def dependency_registry
|
|
400
|
+
if (configured_registry = configured_registry_from_credentials)
|
|
401
|
+
return configured_registry.sub(%r{^https?://}, "")
|
|
402
|
+
end
|
|
403
|
+
|
|
389
404
|
registry_finder.registry
|
|
390
405
|
end
|
|
391
406
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "base64"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module Dependabot
|
|
8
|
+
module NpmAndYarn
|
|
9
|
+
module Package
|
|
10
|
+
module RegistryCredentialHelpers
|
|
11
|
+
extend T::Sig
|
|
12
|
+
extend T::Helpers
|
|
13
|
+
|
|
14
|
+
abstract!
|
|
15
|
+
|
|
16
|
+
sig { abstract.returns(T::Array[Dependabot::Credential]) }
|
|
17
|
+
def credentials; end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
sig { returns(T.nilable(String)) }
|
|
22
|
+
def configured_registry_from_credentials
|
|
23
|
+
replaces_base_cred = credentials.find { |cred| cred["type"] == "npm_registry" && cred.replaces_base? }
|
|
24
|
+
return unless replaces_base_cred&.fetch("registry", nil)
|
|
25
|
+
|
|
26
|
+
normalize_registry_url(replaces_base_cred["registry"])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
sig { params(registry: T.nilable(String)).returns(T.nilable(String)) }
|
|
30
|
+
def normalize_registry_url(registry)
|
|
31
|
+
return nil unless registry
|
|
32
|
+
|
|
33
|
+
normalized_registry = registry.start_with?("http") ? registry : "https://#{registry}"
|
|
34
|
+
URI::DEFAULT_PARSER.escape(normalized_registry)&.gsub(%r{/+$}, "")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
sig { params(registry: String).returns(T::Hash[String, String]) }
|
|
38
|
+
def auth_headers_for_registry(registry)
|
|
39
|
+
token = credentials
|
|
40
|
+
.select { |cred| cred["type"] == "npm_registry" }
|
|
41
|
+
.find { |cred| normalize_registry_url(cred["registry"]) == registry }
|
|
42
|
+
&.fetch("token", nil)
|
|
43
|
+
|
|
44
|
+
return {} unless token
|
|
45
|
+
|
|
46
|
+
auth_header_for(token)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
sig { params(token: String).returns(T::Hash[String, String]) }
|
|
50
|
+
def auth_header_for(token)
|
|
51
|
+
if token.include?(":")
|
|
52
|
+
encoded_token = Base64.encode64(token).delete("\n")
|
|
53
|
+
{ "Authorization" => "Basic #{encoded_token}" }
|
|
54
|
+
elsif Base64.decode64(token).ascii_only? &&
|
|
55
|
+
Base64.decode64(token).include?(":")
|
|
56
|
+
{ "Authorization" => "Basic #{token.delete("\n")}" }
|
|
57
|
+
else
|
|
58
|
+
{ "Authorization" => "Bearer #{token}" }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "excon"
|
|
5
|
+
require "dependabot/npm_and_yarn/helpers"
|
|
5
6
|
require "dependabot/npm_and_yarn/update_checker"
|
|
6
7
|
require "dependabot/registry_client"
|
|
7
8
|
require "sorbet-runtime"
|
|
@@ -60,7 +61,8 @@ module Dependabot
|
|
|
60
61
|
def registry
|
|
61
62
|
return @registry if @registry
|
|
62
63
|
|
|
63
|
-
@registry = configured_registry || locked_registry ||
|
|
64
|
+
@registry = configured_registry || locked_registry || scoped_credential_registry_for_dependency ||
|
|
65
|
+
first_registry_with_dependency_details
|
|
64
66
|
T.must(@registry)
|
|
65
67
|
end
|
|
66
68
|
|
|
@@ -197,19 +199,12 @@ module Dependabot
|
|
|
197
199
|
def locked_registry
|
|
198
200
|
return unless registry_source_url
|
|
199
201
|
|
|
200
|
-
lockfile_registry =
|
|
201
|
-
|
|
202
|
-
&.gsub("https://", "")
|
|
203
|
-
&.gsub("http://", "")
|
|
202
|
+
lockfile_registry = registry_source_url&.gsub("https://", "")&.gsub("http://", "")
|
|
203
|
+
return unless lockfile_registry
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
.find { |h| h["registry"]&.include?(lockfile_registry) }
|
|
209
|
-
&.fetch("registry")
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
detailed_registry || lockfile_registry
|
|
205
|
+
known_registries
|
|
206
|
+
.find { |h| h["registry"]&.include?(lockfile_registry) }
|
|
207
|
+
&.fetch("registry") || lockfile_registry
|
|
213
208
|
end
|
|
214
209
|
|
|
215
210
|
sig { returns(T.nilable(String)) }
|
|
@@ -288,10 +283,7 @@ module Dependabot
|
|
|
288
283
|
|
|
289
284
|
sig { returns(String) }
|
|
290
285
|
def global_registry
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
@global_registry = configured_global_registry || GLOBAL_NPM_REGISTRY
|
|
294
|
-
@global_registry
|
|
286
|
+
@global_registry ||= configured_global_registry || GLOBAL_NPM_REGISTRY
|
|
295
287
|
end
|
|
296
288
|
|
|
297
289
|
# rubocop:disable Metrics/PerceivedComplexity
|
|
@@ -342,6 +334,30 @@ module Dependabot
|
|
|
342
334
|
nil
|
|
343
335
|
end
|
|
344
336
|
|
|
337
|
+
sig { returns(T.nilable(String)) }
|
|
338
|
+
def scoped_credential_registry_for_dependency
|
|
339
|
+
dep_name = dependency&.name
|
|
340
|
+
return unless dep_name&.start_with?("@") && dep_name.include?("/")
|
|
341
|
+
|
|
342
|
+
scope = T.must(dep_name.split("/").first)
|
|
343
|
+
registry = scoped_credential_registry(scope)
|
|
344
|
+
normalize_configured_registry(registry) if registry
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
sig { params(scope: String).returns(T.nilable(String)) }
|
|
348
|
+
def scoped_credential_registry(scope)
|
|
349
|
+
cred = credentials.find do |c|
|
|
350
|
+
c["type"] == "npm_registry" && c["registry"] && c.scope&.any? do |s|
|
|
351
|
+
Helpers.normalize_npm_scope(s) == scope
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
return unless cred
|
|
355
|
+
|
|
356
|
+
reg = T.must(cred["registry"])
|
|
357
|
+
registry = reg.start_with?("http") ? reg : "https://#{reg}"
|
|
358
|
+
prepare_registry_url(registry)
|
|
359
|
+
end
|
|
360
|
+
|
|
345
361
|
sig do
|
|
346
362
|
params(
|
|
347
363
|
file: T.nilable(Dependabot::DependencyFile),
|
|
@@ -6,6 +6,8 @@ require "sorbet-runtime"
|
|
|
6
6
|
module Dependabot
|
|
7
7
|
module NpmAndYarn
|
|
8
8
|
class RegistryParser
|
|
9
|
+
# NOTE: Bun has an equivalent implementation in bun/registry_parser.rb.
|
|
10
|
+
# Keep credential-matching behavior in sync across both ecosystems.
|
|
9
11
|
extend T::Sig
|
|
10
12
|
|
|
11
13
|
sig { params(resolved_url: String, credentials: T::Array[Dependabot::Credential]).void }
|
|
@@ -14,7 +16,7 @@ module Dependabot
|
|
|
14
16
|
@credentials = credentials
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
sig { params(name: String).returns(T::Hash[Symbol, T.
|
|
19
|
+
sig { params(name: String).returns(T::Hash[Symbol, T.nilable(String)]) }
|
|
18
20
|
def registry_source_for(name)
|
|
19
21
|
url =
|
|
20
22
|
if resolved_url.include?("/~/")
|
|
@@ -60,34 +62,68 @@ module Dependabot
|
|
|
60
62
|
sig { returns(T::Array[Dependabot::Credential]) }
|
|
61
63
|
attr_reader :credentials
|
|
62
64
|
|
|
63
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
64
65
|
sig { returns(T.nilable(String)) }
|
|
65
66
|
def url_for_relevant_cred
|
|
66
|
-
|
|
67
|
+
resolved_uri = URI(resolved_url)
|
|
67
68
|
|
|
68
69
|
credential_matching_url =
|
|
69
70
|
credentials
|
|
70
71
|
.select { |cred| cred["type"] == "npm_registry" && cred["registry"] }
|
|
71
72
|
.sort_by { |cred| cred.fetch("registry").length }
|
|
72
|
-
.find
|
|
73
|
-
next true if resolved_url_host == details["registry"]
|
|
74
|
-
|
|
75
|
-
uri = if details["registry"]&.include?("://")
|
|
76
|
-
URI(details.fetch("registry"))
|
|
77
|
-
else
|
|
78
|
-
URI("https://#{details['registry']}")
|
|
79
|
-
end
|
|
80
|
-
resolved_url_host == uri.host && resolved_url.include?(details.fetch("registry"))
|
|
81
|
-
end
|
|
73
|
+
.find { |details| credential_matches?(details, resolved_uri: resolved_uri) }
|
|
82
74
|
|
|
83
75
|
return unless credential_matching_url
|
|
84
76
|
|
|
85
|
-
# Trim the resolved URL so that it ends at the same point as the
|
|
86
|
-
# credential registry
|
|
87
77
|
reg = credential_matching_url.fetch("registry")
|
|
88
|
-
|
|
78
|
+
# When the credential registry already includes an explicit scheme, return
|
|
79
|
+
# it directly — the gsub pattern would not match and would produce a
|
|
80
|
+
# malformed string if it ran.
|
|
81
|
+
return reg if reg.include?("://")
|
|
82
|
+
|
|
83
|
+
build_registry_url(registry: reg, resolved_uri: resolved_uri)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
sig { params(registry: String, resolved_uri: URI::Generic).returns(String) }
|
|
87
|
+
def build_registry_url(registry:, resolved_uri:)
|
|
88
|
+
credential_uri = URI("https://#{registry}")
|
|
89
|
+
normalized_path = credential_uri.path.to_s.chomp("/")
|
|
90
|
+
|
|
91
|
+
"#{resolved_uri.scheme}://#{resolved_uri.authority}#{normalized_path}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Enforce npm registry credential boundaries by matching on host, optional
|
|
95
|
+
# explicit scheme, and full path segments so sibling paths on the same host
|
|
96
|
+
# cannot inherit credentials configured for a different registry scope.
|
|
97
|
+
sig { params(details: Dependabot::Credential, resolved_uri: URI::Generic).returns(T::Boolean) }
|
|
98
|
+
def credential_matches?(details, resolved_uri:)
|
|
99
|
+
resolved_url_host = resolved_uri.host
|
|
100
|
+
return true if resolved_url_host == details["registry"]
|
|
101
|
+
|
|
102
|
+
registry_has_scheme = details["registry"]&.include?("://")
|
|
103
|
+
uri = if registry_has_scheme
|
|
104
|
+
URI(details.fetch("registry"))
|
|
105
|
+
else
|
|
106
|
+
URI("https://#{details['registry']}")
|
|
107
|
+
end
|
|
108
|
+
return false unless resolved_url_host == uri.host
|
|
109
|
+
# When the credential includes an explicit scheme, require scheme
|
|
110
|
+
# equality so we do not attribute a URL to credentials configured for
|
|
111
|
+
# a different transport protocol.
|
|
112
|
+
return false if registry_has_scheme && resolved_uri.scheme != uri.scheme
|
|
113
|
+
|
|
114
|
+
# Use path-segment-aware matching to prevent credentials configured
|
|
115
|
+
# for one path-scoped registry from being applied to sibling paths
|
|
116
|
+
# on the same host (e.g., /victim-npm should not match /victim-npm-evil).
|
|
117
|
+
credential_path_match?(uri: uri, resolved_url_path: resolved_uri.path.to_s)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
sig { params(uri: URI::Generic, resolved_url_path: String).returns(T::Boolean) }
|
|
121
|
+
def credential_path_match?(uri:, resolved_url_path:)
|
|
122
|
+
registry_path = uri.path.to_s.chomp("/")
|
|
123
|
+
registry_path.empty? ||
|
|
124
|
+
resolved_url_path.start_with?("#{registry_path}/") ||
|
|
125
|
+
resolved_url_path == registry_path
|
|
89
126
|
end
|
|
90
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
|
91
127
|
end
|
|
92
128
|
end
|
|
93
129
|
end
|
|
@@ -68,7 +68,7 @@ module Dependabot
|
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
sig { params(dep_string: String).returns(T.nilable(T::Hash[Symbol, T.
|
|
71
|
+
sig { params(dep_string: String).returns(T.nilable(T::Hash[Symbol, T.nilable(String)])) }
|
|
72
72
|
def self.parse_dep_string(dep_string)
|
|
73
73
|
stripped = dep_string.strip
|
|
74
74
|
return nil if stripped.empty?
|
|
@@ -106,7 +106,7 @@ module Dependabot
|
|
|
106
106
|
validation_result = validate_audit_result(audit_result, security_advisories)
|
|
107
107
|
if validation_result != :viable
|
|
108
108
|
Dependabot.logger.info("VulnerabilityAuditor: audit result not viable: #{validation_result}")
|
|
109
|
-
fix_unavailable["explanation"] = explain_fix_unavailable(validation_result, dependency)
|
|
109
|
+
fix_unavailable["explanation"] = explain_fix_unavailable(validation_result, dependency, audit_result)
|
|
110
110
|
return fix_unavailable
|
|
111
111
|
end
|
|
112
112
|
|
|
@@ -127,11 +127,27 @@ module Dependabot
|
|
|
127
127
|
sig { returns(T::Array[Dependabot::Credential]) }
|
|
128
128
|
attr_reader :credentials
|
|
129
129
|
|
|
130
|
-
sig
|
|
131
|
-
|
|
130
|
+
sig do
|
|
131
|
+
params(
|
|
132
|
+
validation_result: Symbol,
|
|
133
|
+
dependency: Dependabot::Dependency,
|
|
134
|
+
audit_result: T::Hash[String, T.untyped]
|
|
135
|
+
).returns(String)
|
|
136
|
+
end
|
|
137
|
+
def explain_fix_unavailable(validation_result, dependency, audit_result)
|
|
132
138
|
case validation_result
|
|
133
|
-
when :fix_unavailable
|
|
134
|
-
|
|
139
|
+
when :fix_unavailable
|
|
140
|
+
fix_unavailable_message(dependency)
|
|
141
|
+
when :dependency_still_vulnerable
|
|
142
|
+
dependency_still_vulnerable_message(dependency, audit_result)
|
|
143
|
+
when :downgrades_dependencies
|
|
144
|
+
downgraded_dependency = audit_result["fix_updates"].find do |update|
|
|
145
|
+
downgrades_version?(update["current_version"], update["target_version"])
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
return downgrades_dependency_message(dependency) unless downgraded_dependency
|
|
149
|
+
|
|
150
|
+
downgraded_dependency_message(dependency, downgraded_dependency)
|
|
135
151
|
when :fix_incomplete
|
|
136
152
|
"The lockfile might be out of sync?"
|
|
137
153
|
else
|
|
@@ -193,6 +209,42 @@ module Dependabot
|
|
|
193
209
|
current > target
|
|
194
210
|
end
|
|
195
211
|
|
|
212
|
+
sig { params(dependency: Dependabot::Dependency).returns(String) }
|
|
213
|
+
def fix_unavailable_message(dependency)
|
|
214
|
+
"Dependabot could not find a lockfile update that resolves " \
|
|
215
|
+
"#{dependency.name} to a non-vulnerable version."
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
sig do
|
|
219
|
+
params(
|
|
220
|
+
dependency: Dependabot::Dependency,
|
|
221
|
+
audit_result: T::Hash[String, T.untyped]
|
|
222
|
+
).returns(String)
|
|
223
|
+
end
|
|
224
|
+
def dependency_still_vulnerable_message(dependency, audit_result)
|
|
225
|
+
"A patched version exists for #{dependency.name}, but the available " \
|
|
226
|
+
"update path still resolves it to #{audit_result['target_version']}"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
sig { params(dependency: Dependabot::Dependency).returns(String) }
|
|
230
|
+
def downgrades_dependency_message(dependency)
|
|
231
|
+
"A patched version exists for #{dependency.name}, but the available " \
|
|
232
|
+
"update path requires downgrading another dependency"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
sig do
|
|
236
|
+
params(
|
|
237
|
+
dependency: Dependabot::Dependency,
|
|
238
|
+
downgraded_dependency: T::Hash[String, T.untyped]
|
|
239
|
+
).returns(String)
|
|
240
|
+
end
|
|
241
|
+
def downgraded_dependency_message(dependency, downgraded_dependency)
|
|
242
|
+
"A patched version exists for #{dependency.name}, but the available " \
|
|
243
|
+
"update path would downgrade #{downgraded_dependency['dependency_name']} " \
|
|
244
|
+
"from #{downgraded_dependency['current_version']} to " \
|
|
245
|
+
"#{downgraded_dependency['target_version']}"
|
|
246
|
+
end
|
|
247
|
+
|
|
196
248
|
sig { params(audit_result: T::Hash[String, T.untyped]).returns(T::Boolean) }
|
|
197
249
|
def fix_incomplete?(audit_result)
|
|
198
250
|
audit_result["fix_updates"].any? { |update| !update.key?("target_version") } ||
|
|
@@ -51,7 +51,7 @@ module Dependabot
|
|
|
51
51
|
)
|
|
52
52
|
@latest_version = T.let(nil, T.nilable(T.any(String, Gem::Version)))
|
|
53
53
|
@latest_resolvable_version = T.let(nil, T.nilable(T.any(String, Dependabot::Version)))
|
|
54
|
-
@updated_requirements = T.let(nil, T.nilable(T::Array[
|
|
54
|
+
@updated_requirements = T.let(nil, T.nilable(T::Array[Dependabot::DependencyRequirement]))
|
|
55
55
|
@vulnerability_audit = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
|
|
56
56
|
@vulnerable_versions = T.let(nil, T.nilable(T::Array[T.any(String, Gem::Version)]))
|
|
57
57
|
|
|
@@ -162,7 +162,7 @@ module Dependabot
|
|
|
162
162
|
T.unsafe(version_resolver.latest_resolvable_previous_version(updated_version))
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
-
sig { override.returns(T::Array[
|
|
165
|
+
sig { override.returns(T::Array[Dependabot::DependencyRequirement]) }
|
|
166
166
|
def updated_requirements
|
|
167
167
|
resolvable_version =
|
|
168
168
|
if preferred_resolvable_version.is_a?(version_class)
|
|
@@ -177,12 +177,14 @@ module Dependabot
|
|
|
177
177
|
end
|
|
178
178
|
|
|
179
179
|
@updated_requirements ||=
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
wrap_requirements(
|
|
181
|
+
RequirementsUpdater.new(
|
|
182
|
+
requirements: dependency.requirements,
|
|
183
|
+
updated_source: updated_source,
|
|
184
|
+
latest_resolvable_version: resolvable_version,
|
|
185
|
+
update_strategy: T.must(requirements_update_strategy)
|
|
186
|
+
).updated_requirements
|
|
187
|
+
)
|
|
186
188
|
end
|
|
187
189
|
|
|
188
190
|
sig { returns(T::Boolean) }
|
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.382.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.382.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.382.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -349,6 +349,7 @@ files:
|
|
|
349
349
|
- lib/dependabot/npm_and_yarn/native_helpers.rb
|
|
350
350
|
- lib/dependabot/npm_and_yarn/npm_package_manager.rb
|
|
351
351
|
- lib/dependabot/npm_and_yarn/package/package_details_fetcher.rb
|
|
352
|
+
- lib/dependabot/npm_and_yarn/package/registry_credential_helpers.rb
|
|
352
353
|
- lib/dependabot/npm_and_yarn/package/registry_finder.rb
|
|
353
354
|
- lib/dependabot/npm_and_yarn/package_manager.rb
|
|
354
355
|
- lib/dependabot/npm_and_yarn/package_name.rb
|
|
@@ -374,7 +375,7 @@ licenses:
|
|
|
374
375
|
- MIT
|
|
375
376
|
metadata:
|
|
376
377
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
377
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
378
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.382.0
|
|
378
379
|
rdoc_options: []
|
|
379
380
|
require_paths:
|
|
380
381
|
- lib
|