dependabot-nuget 0.271.0 → 0.272.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +19 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +14 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +51 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +83 -0
- data/lib/dependabot/nuget/file_parser.rb +4 -1
- data/lib/dependabot/nuget/file_updater.rb +100 -126
- data/lib/dependabot/nuget/native_discovery/native_dependency_file_discovery.rb +4 -6
- data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +78 -4
- data/lib/dependabot/nuget/native_discovery/native_project_discovery.rb +1 -0
- data/lib/dependabot/nuget/native_helpers.rb +25 -10
- data/lib/dependabot/nuget/native_update_checker/native_requirements_updater.rb +17 -8
- data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +45 -10
- data/lib/dependabot/nuget/update_checker/requirements_updater.rb +4 -4
- data/lib/dependabot/nuget/update_checker.rb +2 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b33f6b0a817def66d738b6d998bedf0248881c225ddf6b97fa0a5405d92b9d23
|
4
|
+
data.tar.gz: 9e736306b9d21e95639627ea2fe585d2e47a8979e1096545b4da2ed49e8dbd5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4dddbe1b9c9cbf4fb9d7876c143cfbd713bf2f6ca501210ae40a7c37bb2076687ea75996042e9ccc6582a6cd535a24eb3b2caa3143b5e8c3ef8ff500455f7827
|
7
|
+
data.tar.gz: f51748be257babb63de285cec8981ca84e580fa48b6d6fec2a89892f485f46ae237af86ab387a584c27c8f207ba1ae16167570bffdc59cf3cd4bb64ac27e9830
|
@@ -108,6 +108,7 @@ public partial class AnalyzeWorker
|
|
108
108
|
discovery,
|
109
109
|
dependenciesToUpdate,
|
110
110
|
updatedVersion,
|
111
|
+
dependencyInfo,
|
111
112
|
nugetContext,
|
112
113
|
_logger,
|
113
114
|
CancellationToken.None);
|
@@ -359,6 +360,7 @@ public partial class AnalyzeWorker
|
|
359
360
|
WorkspaceDiscoveryResult discovery,
|
360
361
|
ImmutableHashSet<string> packageIds,
|
361
362
|
NuGetVersion updatedVersion,
|
363
|
+
DependencyInfo dependencyInfo,
|
362
364
|
NuGetContext nugetContext,
|
363
365
|
Logger logger,
|
364
366
|
CancellationToken cancellationToken)
|
@@ -379,10 +381,23 @@ public partial class AnalyzeWorker
|
|
379
381
|
.Select(NuGetFramework.Parse)
|
380
382
|
.ToImmutableArray();
|
381
383
|
|
382
|
-
// When updating
|
383
|
-
var
|
384
|
-
.
|
385
|
-
|
384
|
+
// When updating dependencies, we only need to consider top-level dependencies _UNLESS_ it's specifically vulnerable
|
385
|
+
var relevantDependencies = projectsWithDependency.SelectMany(p => p.Dependencies)
|
386
|
+
.Where(d =>
|
387
|
+
{
|
388
|
+
if (string.Compare(d.Name, dependencyInfo.Name, StringComparison.OrdinalIgnoreCase) == 0 &&
|
389
|
+
dependencyInfo.IsVulnerable)
|
390
|
+
{
|
391
|
+
// if this dependency is one we're specifically updating _and_ if it's vulnerable, always update it
|
392
|
+
return true;
|
393
|
+
}
|
394
|
+
else
|
395
|
+
{
|
396
|
+
// otherwise only update if it's a top-level dependency
|
397
|
+
return !d.IsTransitive;
|
398
|
+
}
|
399
|
+
});
|
400
|
+
var projectDependencyNames = relevantDependencies
|
386
401
|
.Select(d => d.Name)
|
387
402
|
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);
|
388
403
|
|
@@ -199,13 +199,21 @@ public partial class DiscoveryWorker
|
|
199
199
|
}
|
200
200
|
else
|
201
201
|
{
|
202
|
-
|
203
|
-
// keep this project and check for references
|
204
|
-
expandedProjects.Add(candidateEntryPoint);
|
205
|
-
IEnumerable<string> referencedProjects = ExpandItemGroupFilesFromProject(candidateEntryPoint, "ProjectReference");
|
206
|
-
foreach (string referencedProject in referencedProjects)
|
202
|
+
switch (extension)
|
207
203
|
{
|
208
|
-
|
204
|
+
case ".csproj":
|
205
|
+
case ".fsproj":
|
206
|
+
case ".vbproj":
|
207
|
+
// keep this project and check for references
|
208
|
+
expandedProjects.Add(candidateEntryPoint);
|
209
|
+
IEnumerable<string> referencedProjects = ExpandItemGroupFilesFromProject(candidateEntryPoint, "ProjectReference");
|
210
|
+
foreach (string referencedProject in referencedProjects)
|
211
|
+
{
|
212
|
+
filesToExpand.Push(referencedProject);
|
213
|
+
}
|
214
|
+
break;
|
215
|
+
default:
|
216
|
+
continue;
|
209
217
|
}
|
210
218
|
}
|
211
219
|
}
|
@@ -169,7 +169,7 @@ internal static partial class MSBuildHelper
|
|
169
169
|
string.Equals(property.Condition, $"'$({property.Name})' == ''", StringComparison.OrdinalIgnoreCase);
|
170
170
|
if (hasEmptyCondition || conditionIsCheckingForEmptyString)
|
171
171
|
{
|
172
|
-
properties[property.Name] = new(property.Name, property.Value, buildFile.RelativePath);
|
172
|
+
properties[property.Name] = new(property.Name, property.Value, PathHelper.NormalizePathToUnix(buildFile.RelativePath));
|
173
173
|
}
|
174
174
|
}
|
175
175
|
}
|
@@ -347,6 +347,57 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
|
|
347
347
|
);
|
348
348
|
}
|
349
349
|
|
350
|
+
[Fact]
|
351
|
+
public async Task AnalyzeVulnerableTransitiveDependencies()
|
352
|
+
{
|
353
|
+
await TestAnalyzeAsync(
|
354
|
+
packages:
|
355
|
+
[
|
356
|
+
MockNuGetPackage.CreateSimplePackage("Some.Transitive.Dependency", "1.0.0", "net8.0"),
|
357
|
+
MockNuGetPackage.CreateSimplePackage("Some.Transitive.Dependency", "1.0.1", "net8.0"),
|
358
|
+
],
|
359
|
+
discovery: new()
|
360
|
+
{
|
361
|
+
Path = "/",
|
362
|
+
Projects = [
|
363
|
+
new()
|
364
|
+
{
|
365
|
+
FilePath = "project.csproj",
|
366
|
+
TargetFrameworks = ["net8.0"],
|
367
|
+
Dependencies = [
|
368
|
+
new("Some.Transitive.Dependency", "1.0.0", DependencyType.Unknown, TargetFrameworks: ["net8.0"], IsTransitive: true),
|
369
|
+
]
|
370
|
+
}
|
371
|
+
]
|
372
|
+
},
|
373
|
+
dependencyInfo: new()
|
374
|
+
{
|
375
|
+
Name = "Some.Transitive.Dependency",
|
376
|
+
Version = "1.0.0",
|
377
|
+
IsVulnerable = true,
|
378
|
+
IgnoredVersions = [],
|
379
|
+
Vulnerabilities = [
|
380
|
+
new()
|
381
|
+
{
|
382
|
+
DependencyName = "Some.Transitive.Dependency",
|
383
|
+
PackageManager = "nuget",
|
384
|
+
VulnerableVersions = [Requirement.Parse("<= 1.0.0")],
|
385
|
+
SafeVersions = [Requirement.Parse("= 1.0.1")],
|
386
|
+
}
|
387
|
+
]
|
388
|
+
},
|
389
|
+
expectedResult: new()
|
390
|
+
{
|
391
|
+
UpdatedVersion = "1.0.1",
|
392
|
+
CanUpdate = true,
|
393
|
+
VersionComesFromMultiDependencyProperty = false,
|
394
|
+
UpdatedDependencies = [
|
395
|
+
new("Some.Transitive.Dependency", "1.0.1", DependencyType.Unknown, TargetFrameworks: ["net8.0"]),
|
396
|
+
],
|
397
|
+
}
|
398
|
+
);
|
399
|
+
}
|
400
|
+
|
350
401
|
[Fact]
|
351
402
|
public async Task IgnoredVersionsCanHandleWildcardSpecification()
|
352
403
|
{
|
@@ -375,6 +375,89 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
|
|
375
375
|
);
|
376
376
|
}
|
377
377
|
|
378
|
+
[Fact]
|
379
|
+
public async Task NonSupportedProjectExtensionsAreSkipped()
|
380
|
+
{
|
381
|
+
await TestDiscoveryAsync(
|
382
|
+
packages:
|
383
|
+
[
|
384
|
+
MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
|
385
|
+
],
|
386
|
+
workspacePath: "/",
|
387
|
+
files: new[]
|
388
|
+
{
|
389
|
+
("solution.sln", """
|
390
|
+
Microsoft Visual Studio Solution File, Format Version 12.00
|
391
|
+
# Visual Studio Version 17
|
392
|
+
VisualStudioVersion = 17.10.35027.167
|
393
|
+
MinimumVisualStudioVersion = 10.0.40219.1
|
394
|
+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "supported", "src\supported.csproj", "{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}"
|
395
|
+
EndProject
|
396
|
+
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "unsupported", "src\unsupported.vdproj", "{271E533C-8A44-4572-8C18-CD65A79F8658}"
|
397
|
+
EndProject
|
398
|
+
Global
|
399
|
+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
400
|
+
Debug|Any CPU = Debug|Any CPU
|
401
|
+
Release|Any CPU = Release|Any CPU
|
402
|
+
EndGlobalSection
|
403
|
+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
404
|
+
{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
405
|
+
{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
406
|
+
{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
407
|
+
{4A3B8D8A-A585-4593-8AF3-DED05AE3C40F}.Release|Any CPU.Build.0 = Release|Any CPU
|
408
|
+
{271E533C-8A44-4572-8C18-CD65A79F8658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
409
|
+
{271E533C-8A44-4572-8C18-CD65A79F8658}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
410
|
+
{271E533C-8A44-4572-8C18-CD65A79F8658}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
411
|
+
{271E533C-8A44-4572-8C18-CD65A79F8658}.Release|Any CPU.Build.0 = Release|Any CPU
|
412
|
+
EndGlobalSection
|
413
|
+
GlobalSection(SolutionProperties) = preSolution
|
414
|
+
HideSolutionNode = FALSE
|
415
|
+
EndGlobalSection
|
416
|
+
GlobalSection(ExtensibilityGlobals) = postSolution
|
417
|
+
SolutionGuid = {EE5BDEF7-1D4D-4773-9659-FC4A3846CD6D}
|
418
|
+
EndGlobalSection
|
419
|
+
EndGlobal
|
420
|
+
"""),
|
421
|
+
("src/supported.csproj", """
|
422
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
423
|
+
<PropertyGroup>
|
424
|
+
<TargetFramework>net8.0</TargetFramework>
|
425
|
+
</PropertyGroup>
|
426
|
+
<ItemGroup>
|
427
|
+
<PackageReference Include="Some.Package" Version="1.0.0" />
|
428
|
+
</ItemGroup>
|
429
|
+
</Project>
|
430
|
+
"""),
|
431
|
+
("src/unsupported.vdproj", """
|
432
|
+
"DeployProject"
|
433
|
+
{
|
434
|
+
"SomeKey" = "SomeValue"
|
435
|
+
}
|
436
|
+
"""),
|
437
|
+
},
|
438
|
+
expectedResult: new()
|
439
|
+
{
|
440
|
+
Path = "",
|
441
|
+
Projects = [
|
442
|
+
new()
|
443
|
+
{
|
444
|
+
FilePath = "src/supported.csproj",
|
445
|
+
TargetFrameworks = ["net8.0"],
|
446
|
+
ReferencedProjectPaths = [],
|
447
|
+
ExpectedDependencyCount = 2,
|
448
|
+
Dependencies = [
|
449
|
+
new("Microsoft.NET.Sdk", null, DependencyType.MSBuildSdk),
|
450
|
+
new("Some.Package", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true)
|
451
|
+
],
|
452
|
+
Properties = [
|
453
|
+
new("TargetFramework", "net8.0", @"src/supported.csproj"),
|
454
|
+
]
|
455
|
+
}
|
456
|
+
]
|
457
|
+
}
|
458
|
+
);
|
459
|
+
}
|
460
|
+
|
378
461
|
[Fact]
|
379
462
|
public async Task ResultFileHasCorrectShapeForAuthenticationFailure()
|
380
463
|
{
|
@@ -50,7 +50,10 @@ module Dependabot
|
|
50
50
|
# cache discovery results
|
51
51
|
NativeDiscoveryJsonReader.set_discovery_from_dependency_files(dependency_files: dependency_files,
|
52
52
|
discovery: discovery_json_reader)
|
53
|
-
|
53
|
+
# we only return top-level dependencies and requirements here
|
54
|
+
dependency_set = discovery_json_reader.dependency_set(dependency_files: dependency_files,
|
55
|
+
top_level_only: true)
|
56
|
+
dependency_set.dependencies
|
54
57
|
end
|
55
58
|
|
56
59
|
T.must(self.class.file_dependency_cache[key])
|
@@ -16,35 +16,32 @@ module Dependabot
|
|
16
16
|
class FileUpdater < Dependabot::FileUpdaters::Base
|
17
17
|
extend T::Sig
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
]
|
33
|
-
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
/^Packages\.props$/i
|
46
|
-
]
|
47
|
-
end
|
19
|
+
DependencyDetails = T.type_alias do
|
20
|
+
{
|
21
|
+
file: String,
|
22
|
+
name: String,
|
23
|
+
version: String,
|
24
|
+
previous_version: String,
|
25
|
+
is_transitive: T::Boolean
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { override.returns(T::Array[Regexp]) }
|
30
|
+
def self.updated_files_regex
|
31
|
+
[
|
32
|
+
/.*\.([a-z]{2})?proj$/, # Matches files with any extension like .csproj, .vbproj, etc., in any directory
|
33
|
+
/packages\.config$/i, # Matches packages.config in any directory
|
34
|
+
/app\.config$/i, # Matches app.config in any directory
|
35
|
+
/web\.config$/i, # Matches web.config in any directory
|
36
|
+
/global\.json$/i, # Matches global.json in any directory
|
37
|
+
/dotnet-tools\.json$/i, # Matches dotnet-tools.json in any directory
|
38
|
+
/Directory\.Build\.props$/i, # Matches Directory.Build.props in any directory
|
39
|
+
/Directory\.Build\.targets$/i, # Matches Directory.Build.targets in any directory
|
40
|
+
/Directory\.targets$/i, # Matches Directory.targets in any directory or root directory
|
41
|
+
/Packages\.props$/i, # Matches Packages.props in any directory
|
42
|
+
/.*\.nuspec$/, # Matches any .nuspec files in any directory
|
43
|
+
%r{^\.config/dotnet-tools\.json$} # Matches .config/dotnet-tools.json in only root directory
|
44
|
+
]
|
48
45
|
end
|
49
46
|
|
50
47
|
sig { params(original_content: T.nilable(String), updated_content: String).returns(T::Boolean) }
|
@@ -65,9 +62,21 @@ module Dependabot
|
|
65
62
|
def updated_dependency_files
|
66
63
|
base_dir = "/"
|
67
64
|
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
68
|
-
|
69
|
-
|
65
|
+
expanded_dependency_details.each do |dep_details|
|
66
|
+
file = T.let(dep_details.fetch(:file), String)
|
67
|
+
name = T.let(dep_details.fetch(:name), String)
|
68
|
+
version = T.let(dep_details.fetch(:version), String)
|
69
|
+
previous_version = T.let(dep_details.fetch(:previous_version), String)
|
70
|
+
is_transitive = T.let(dep_details.fetch(:is_transitive), T::Boolean)
|
71
|
+
NativeHelpers.run_nuget_updater_tool(repo_root: T.must(repo_contents_path),
|
72
|
+
proj_path: file,
|
73
|
+
dependency_name: name,
|
74
|
+
version: version,
|
75
|
+
previous_version: previous_version,
|
76
|
+
is_transitive: is_transitive,
|
77
|
+
credentials: credentials)
|
70
78
|
end
|
79
|
+
|
71
80
|
updated_files = dependency_files.filter_map do |f|
|
72
81
|
updated_content = File.read(dependency_file_path(f))
|
73
82
|
next if updated_content == f.content
|
@@ -87,104 +96,69 @@ module Dependabot
|
|
87
96
|
|
88
97
|
private
|
89
98
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
checked_key = "#{project_file.name}-#{dependency.name}#{dependency.version}"
|
105
|
-
call_nuget_updater_tool(dependency, proj_path) unless checked_files.include?(checked_key)
|
106
|
-
|
107
|
-
checked_files.add(checked_key)
|
108
|
-
# We need to check the downstream references even though we're already evaluated the file
|
109
|
-
downstream_files = referenced_project_paths(project_file)
|
110
|
-
downstream_files.each do |downstream_file|
|
111
|
-
checked_files.add("#{downstream_file}-#{dependency.name}#{dependency.version}")
|
99
|
+
# rubocop:disable Metrics/AbcSize
|
100
|
+
sig { returns(T::Array[DependencyDetails]) }
|
101
|
+
def expanded_dependency_details
|
102
|
+
discovery_json_reader = NativeDiscoveryJsonReader.get_discovery_from_dependency_files(dependency_files)
|
103
|
+
dependency_set = discovery_json_reader.dependency_set(dependency_files: dependency_files, top_level_only: false)
|
104
|
+
all_dependencies = dependency_set.dependencies
|
105
|
+
dependencies.map do |dep|
|
106
|
+
# if vulnerable metadata is set, re-fetch all requirements from discovery
|
107
|
+
is_vulnerable = T.let(dep.metadata.fetch(:is_vulnerable, false), T::Boolean)
|
108
|
+
relevant_dependencies = all_dependencies.filter { |d| d.name.casecmp?(dep.name) }
|
109
|
+
candidate_vulnerable_dependency = T.must(relevant_dependencies.first)
|
110
|
+
relevant_dependency = is_vulnerable ? candidate_vulnerable_dependency : dep
|
111
|
+
relevant_details = relevant_dependency.requirements.filter_map do |req|
|
112
|
+
dependency_details_from_requirement(dep.name, req, is_vulnerable: is_vulnerable)
|
112
113
|
end
|
113
|
-
update_ran = true
|
114
|
-
end
|
115
|
-
update_ran
|
116
|
-
end
|
117
|
-
|
118
|
-
sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
|
119
|
-
def try_update_json(dependency)
|
120
|
-
if dotnet_tools_json_dependencies.any? { |dep| dep.name.casecmp?(dependency.name) } ||
|
121
|
-
global_json_dependencies.any? { |dep| dep.name.casecmp?(dependency.name) }
|
122
|
-
|
123
|
-
# We just need to feed the updater a project file, grab the first
|
124
|
-
project_file = T.must(project_files.first)
|
125
|
-
proj_path = dependency_file_path(project_file)
|
126
|
-
|
127
|
-
return false unless repo_contents_path
|
128
|
-
|
129
|
-
call_nuget_updater_tool(dependency, proj_path)
|
130
|
-
return true
|
131
|
-
end
|
132
114
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
T.must(@update_tooling_calls[key]) + 1
|
150
|
-
else
|
151
|
-
1
|
115
|
+
next relevant_details if relevant_details.any?
|
116
|
+
|
117
|
+
# If we didn't find anything to update, we're in a very specific corner case: we were explicitly asked to
|
118
|
+
# (1) update a certain dependency, (2) it wasn't listed as a security update, but (3) it only exists as a
|
119
|
+
# transitive dependency. In this case, we need to rebuild the dependency requirements as if this were a
|
120
|
+
# security update so that we can perform the appropriate update.
|
121
|
+
candidate_vulnerable_dependency.requirements.filter_map do |req|
|
122
|
+
rebuilt_req = {
|
123
|
+
file: req[:file], # simple copy
|
124
|
+
requirement: relevant_dependency.version, # the newly available version
|
125
|
+
metadata: {
|
126
|
+
is_transitive: T.let(req[:metadata], T::Hash[Symbol, T.untyped])[:is_transitive], # simple copy
|
127
|
+
previous_requirement: req[:requirement] # the old requirement's "current" version is now the "previous"
|
128
|
+
}
|
129
|
+
}
|
130
|
+
dependency_details_from_requirement(dep.name, rebuilt_req, is_vulnerable: true)
|
152
131
|
end
|
153
|
-
|
154
|
-
|
155
|
-
#
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
end
|
184
|
-
|
185
|
-
sig { returns(T::Array[NativeDependencyDetails]) }
|
186
|
-
def dotnet_tools_json_dependencies
|
187
|
-
workspace&.dotnet_tools_json&.dependencies || []
|
132
|
+
end.flatten
|
133
|
+
end
|
134
|
+
# rubocop:enable Metrics/AbcSize
|
135
|
+
|
136
|
+
sig do
|
137
|
+
params(
|
138
|
+
name: String,
|
139
|
+
requirement: T::Hash[Symbol, T.untyped],
|
140
|
+
is_vulnerable: T::Boolean
|
141
|
+
).returns(T.nilable(DependencyDetails))
|
142
|
+
end
|
143
|
+
def dependency_details_from_requirement(name, requirement, is_vulnerable:)
|
144
|
+
metadata = T.let(requirement.fetch(:metadata), T::Hash[Symbol, T.untyped])
|
145
|
+
current_file = T.let(requirement.fetch(:file), String)
|
146
|
+
return nil unless current_file.match?(/\.(cs|vb|fs)proj$/)
|
147
|
+
|
148
|
+
is_transitive = T.let(metadata.fetch(:is_transitive), T::Boolean)
|
149
|
+
return nil if !is_vulnerable && is_transitive
|
150
|
+
|
151
|
+
version = T.let(requirement.fetch(:requirement), String)
|
152
|
+
previous_version = T.let(metadata[:previous_requirement], String)
|
153
|
+
return nil if version == previous_version
|
154
|
+
|
155
|
+
{
|
156
|
+
file: T.let(requirement.fetch(:file), String),
|
157
|
+
name: name,
|
158
|
+
version: version,
|
159
|
+
previous_version: previous_version,
|
160
|
+
is_transitive: is_transitive
|
161
|
+
}
|
188
162
|
end
|
189
163
|
|
190
164
|
# rubocop:disable Metrics/PerceivedComplexity
|
@@ -106,22 +106,20 @@ module Dependabot
|
|
106
106
|
.returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
107
107
|
end
|
108
108
|
def build_requirement(file_name, dependency_details)
|
109
|
-
return if dependency_details.is_transitive
|
110
|
-
|
111
109
|
version = dependency_details.version
|
112
110
|
version = nil if version&.empty?
|
111
|
+
metadata = { is_transitive: dependency_details.is_transitive }
|
113
112
|
|
114
113
|
requirement = {
|
115
114
|
requirement: version,
|
116
115
|
file: file_name,
|
117
116
|
groups: [dependency_details.is_dev_dependency ? "devDependencies" : "dependencies"],
|
118
|
-
source: nil
|
117
|
+
source: nil,
|
118
|
+
metadata: metadata
|
119
119
|
}
|
120
120
|
|
121
121
|
property_name = dependency_details.evaluation&.root_property_name
|
122
|
-
|
123
|
-
|
124
|
-
requirement[:metadata] = { property_name: property_name }
|
122
|
+
metadata[:property_name] = property_name if property_name
|
125
123
|
requirement
|
126
124
|
end
|
127
125
|
end
|
@@ -125,16 +125,90 @@ module Dependabot
|
|
125
125
|
sig { returns(T.nilable(NativeWorkspaceDiscovery)) }
|
126
126
|
attr_reader :workspace_discovery
|
127
127
|
|
128
|
-
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
129
|
-
attr_reader :dependency_set
|
130
|
-
|
131
128
|
sig { params(discovery_json: DependencyFile).void }
|
132
129
|
def initialize(discovery_json:)
|
133
130
|
@discovery_json = discovery_json
|
134
131
|
@workspace_discovery = T.let(read_workspace_discovery, T.nilable(Dependabot::Nuget::NativeWorkspaceDiscovery))
|
135
|
-
@dependency_set = T.let(read_dependency_set, Dependabot::FileParsers::Base::DependencySet)
|
136
132
|
end
|
137
133
|
|
134
|
+
# rubocop:disable Metrics/AbcSize
|
135
|
+
# rubocop:disable Metrics/MethodLength
|
136
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
137
|
+
sig do
|
138
|
+
params(
|
139
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
140
|
+
top_level_only: T::Boolean
|
141
|
+
).returns(Dependabot::FileParsers::Base::DependencySet)
|
142
|
+
end
|
143
|
+
def dependency_set(dependency_files:, top_level_only:)
|
144
|
+
# dependencies must be recalculated so that we:
|
145
|
+
# 1. only return dependencies that are in the file set we reported earlier
|
146
|
+
# see https://github.com/dependabot/dependabot-core/issues/10303
|
147
|
+
# 2. the reported version is the minimum across all requirements; this ensures that we get the opportunity
|
148
|
+
# to update everything later
|
149
|
+
dependency_file_set = T.let(Set.new(dependency_files.map do |df|
|
150
|
+
Pathname.new(File.join(df.directory, df.name)).cleanpath.to_path
|
151
|
+
end), T::Set[String])
|
152
|
+
|
153
|
+
rebuilt_dependencies = read_dependency_set.dependencies.filter_map do |dep|
|
154
|
+
# only report requirements in files we know about
|
155
|
+
matching_requirements = dep.requirements.filter do |req|
|
156
|
+
file = T.let(req.fetch(:file), String)
|
157
|
+
dependency_file_set.include?(file)
|
158
|
+
end
|
159
|
+
|
160
|
+
# find the minimum version across all requirements
|
161
|
+
min_version = matching_requirements.filter_map do |req|
|
162
|
+
v = T.let(req.fetch(:requirement), T.nilable(String))
|
163
|
+
next unless v
|
164
|
+
|
165
|
+
Dependabot::Nuget::Version.new(v)
|
166
|
+
end.min
|
167
|
+
next unless min_version
|
168
|
+
|
169
|
+
# only return dependency requirements that are top-level
|
170
|
+
if top_level_only
|
171
|
+
matching_requirements.reject! do |req|
|
172
|
+
metadata = T.let(req.fetch(:metadata), T::Hash[Symbol, T.untyped])
|
173
|
+
T.let(metadata.fetch(:is_transitive), T::Boolean)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# we might need to return a dependency like this
|
178
|
+
dep_without_reqs = Dependabot::Dependency.new(
|
179
|
+
name: dep.name,
|
180
|
+
version: min_version.to_s,
|
181
|
+
package_manager: "nuget",
|
182
|
+
requirements: []
|
183
|
+
)
|
184
|
+
|
185
|
+
dep_with_reqs = matching_requirements.filter_map do |req|
|
186
|
+
version = T.let(req.fetch(:requirement, nil), T.nilable(String))
|
187
|
+
next unless version
|
188
|
+
|
189
|
+
Dependabot::Dependency.new(
|
190
|
+
name: dep.name,
|
191
|
+
version: min_version.to_s,
|
192
|
+
package_manager: "nuget",
|
193
|
+
requirements: [req]
|
194
|
+
)
|
195
|
+
end
|
196
|
+
|
197
|
+
# if only returning top-level dependencies and we had no non-transitive requirements, return an empty
|
198
|
+
# dependency so it can be tracked for security updates
|
199
|
+
matching_requirements.empty? && top_level_only ? [dep_without_reqs] : dep_with_reqs
|
200
|
+
end.flatten
|
201
|
+
|
202
|
+
final_dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
203
|
+
rebuilt_dependencies.each do |dep|
|
204
|
+
final_dependency_set << dep
|
205
|
+
end
|
206
|
+
final_dependency_set
|
207
|
+
end
|
208
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
209
|
+
# rubocop:enable Metrics/MethodLength
|
210
|
+
# rubocop:enable Metrics/AbcSize
|
211
|
+
|
138
212
|
private
|
139
213
|
|
140
214
|
sig { returns(DependencyFile) }
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# typed: strong
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "dependabot/file_parsers/base/dependency_set"
|
4
5
|
require "dependabot/nuget/native_discovery/native_dependency_details"
|
5
6
|
require "dependabot/nuget/native_discovery/native_property_details"
|
6
7
|
require "sorbet-runtime"
|
@@ -171,10 +171,18 @@ module Dependabot
|
|
171
171
|
|
172
172
|
# rubocop:disable Metrics/MethodLength
|
173
173
|
sig do
|
174
|
-
params(
|
175
|
-
|
174
|
+
params(
|
175
|
+
repo_root: String,
|
176
|
+
proj_path: String,
|
177
|
+
dependency_name: String,
|
178
|
+
version: String,
|
179
|
+
previous_version: String,
|
180
|
+
is_transitive: T::Boolean,
|
181
|
+
result_output_path: String
|
182
|
+
).returns([String, String])
|
176
183
|
end
|
177
|
-
def self.get_nuget_updater_tool_command(repo_root:, proj_path:,
|
184
|
+
def self.get_nuget_updater_tool_command(repo_root:, proj_path:, dependency_name:, version:, previous_version:,
|
185
|
+
is_transitive:, result_output_path:)
|
178
186
|
exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli")
|
179
187
|
command_parts = [
|
180
188
|
exe_path,
|
@@ -184,11 +192,11 @@ module Dependabot
|
|
184
192
|
"--solution-or-project",
|
185
193
|
proj_path,
|
186
194
|
"--dependency",
|
187
|
-
|
195
|
+
dependency_name,
|
188
196
|
"--new-version",
|
189
|
-
|
197
|
+
version,
|
190
198
|
"--previous-version",
|
191
|
-
|
199
|
+
previous_version,
|
192
200
|
is_transitive ? "--transitive" : nil,
|
193
201
|
"--result-output-path",
|
194
202
|
result_output_path,
|
@@ -229,14 +237,21 @@ module Dependabot
|
|
229
237
|
params(
|
230
238
|
repo_root: String,
|
231
239
|
proj_path: String,
|
232
|
-
|
240
|
+
dependency_name: String,
|
241
|
+
version: String,
|
242
|
+
previous_version: String,
|
233
243
|
is_transitive: T::Boolean,
|
234
244
|
credentials: T::Array[Dependabot::Credential]
|
235
245
|
).void
|
236
246
|
end
|
237
|
-
def self.run_nuget_updater_tool(repo_root:, proj_path:,
|
238
|
-
|
239
|
-
|
247
|
+
def self.run_nuget_updater_tool(repo_root:, proj_path:, dependency_name:, version:, previous_version:,
|
248
|
+
is_transitive:, credentials:)
|
249
|
+
(command, fingerprint) = get_nuget_updater_tool_command(repo_root: repo_root,
|
250
|
+
proj_path: proj_path,
|
251
|
+
dependency_name: dependency_name,
|
252
|
+
version: version,
|
253
|
+
previous_version: previous_version,
|
254
|
+
is_transitive: is_transitive,
|
240
255
|
result_output_path: update_result_file_path)
|
241
256
|
|
242
257
|
puts "running NuGet updater:\n" + command
|
@@ -21,15 +21,18 @@ module Dependabot
|
|
21
21
|
sig do
|
22
22
|
params(
|
23
23
|
requirements: T::Array[T::Hash[Symbol, T.untyped]],
|
24
|
-
dependency_details: T.nilable(Dependabot::Nuget::NativeDependencyDetails)
|
24
|
+
dependency_details: T.nilable(Dependabot::Nuget::NativeDependencyDetails),
|
25
|
+
vulnerable: T::Boolean
|
25
26
|
)
|
26
27
|
.void
|
27
28
|
end
|
28
|
-
def initialize(requirements:, dependency_details:)
|
29
|
+
def initialize(requirements:, dependency_details:, vulnerable:)
|
29
30
|
@requirements = requirements
|
30
31
|
@dependency_details = dependency_details
|
32
|
+
@vulnerable = vulnerable
|
31
33
|
end
|
32
34
|
|
35
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
33
36
|
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
34
37
|
def updated_requirements
|
35
38
|
return requirements unless clean_version
|
@@ -37,13 +40,18 @@ module Dependabot
|
|
37
40
|
# NOTE: Order is important here. The FileUpdater needs the updated
|
38
41
|
# requirement at index `i` to correspond to the previous requirement
|
39
42
|
# at the same index.
|
40
|
-
requirements.
|
41
|
-
next
|
42
|
-
|
43
|
+
requirements.filter_map do |req|
|
44
|
+
next if !@vulnerable && req[:metadata][:is_transitive]
|
45
|
+
|
46
|
+
previous_requirement = req.fetch(:requirement)
|
47
|
+
req[:metadata][:previous_requirement] = previous_requirement
|
48
|
+
|
49
|
+
next req if previous_requirement.nil?
|
50
|
+
next req if previous_requirement.include?(",")
|
43
51
|
|
44
52
|
new_req =
|
45
|
-
if
|
46
|
-
update_wildcard_requirement(
|
53
|
+
if previous_requirement.include?("*")
|
54
|
+
update_wildcard_requirement(previous_requirement)
|
47
55
|
else
|
48
56
|
# Since range requirements are excluded by the line above we can
|
49
57
|
# replace anything that looks like a version with the new
|
@@ -54,7 +62,7 @@ module Dependabot
|
|
54
62
|
)
|
55
63
|
end
|
56
64
|
|
57
|
-
next req if new_req ==
|
65
|
+
next req if new_req == previous_requirement
|
58
66
|
|
59
67
|
new_source = req[:source]&.dup
|
60
68
|
unless @dependency_details.nil?
|
@@ -67,6 +75,7 @@ module Dependabot
|
|
67
75
|
req.merge({ requirement: new_req, source: new_source })
|
68
76
|
end
|
69
77
|
end
|
78
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
70
79
|
|
71
80
|
private
|
72
81
|
|
@@ -56,7 +56,8 @@ module Dependabot
|
|
56
56
|
dep_details = updated_dependency_details.find { |d| d.name.casecmp?(dependency.name) }
|
57
57
|
NativeRequirementsUpdater.new(
|
58
58
|
requirements: dependency.requirements,
|
59
|
-
dependency_details: dep_details
|
59
|
+
dependency_details: dep_details,
|
60
|
+
vulnerable: vulnerable?
|
60
61
|
).updated_requirements
|
61
62
|
end
|
62
63
|
|
@@ -112,9 +113,10 @@ module Dependabot
|
|
112
113
|
|
113
114
|
sig { void }
|
114
115
|
def write_dependency_info
|
116
|
+
dependency_version = T.let(dependency.requirements.first&.fetch(:requirement, nil), T.nilable(String))
|
115
117
|
dependency_info = {
|
116
118
|
Name: dependency.name,
|
117
|
-
Version: dependency.version.to_s,
|
119
|
+
Version: dependency_version || dependency.version.to_s,
|
118
120
|
IsVulnerable: vulnerable?,
|
119
121
|
IgnoredVersions: ignored_versions,
|
120
122
|
Vulnerabilities: security_advisories.map do |vulnerability|
|
@@ -141,7 +143,7 @@ module Dependabot
|
|
141
143
|
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
142
144
|
def discovered_dependencies
|
143
145
|
discovery_json_reader = NativeDiscoveryJsonReader.get_discovery_from_dependency_files(dependency_files)
|
144
|
-
discovery_json_reader.dependency_set
|
146
|
+
discovery_json_reader.dependency_set(dependency_files: dependency_files, top_level_only: false)
|
145
147
|
end
|
146
148
|
|
147
149
|
sig { override.returns(T::Boolean) }
|
@@ -150,6 +152,10 @@ module Dependabot
|
|
150
152
|
true
|
151
153
|
end
|
152
154
|
|
155
|
+
# rubocop:disable Metrics/AbcSize
|
156
|
+
# rubocop:disable Metrics/BlockLength
|
157
|
+
# rubocop:disable Metrics/MethodLength
|
158
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
153
159
|
sig { override.returns(T::Array[Dependabot::Dependency]) }
|
154
160
|
def updated_dependencies_after_full_unlock
|
155
161
|
dependencies = discovered_dependencies.dependencies
|
@@ -157,14 +163,16 @@ module Dependabot
|
|
157
163
|
dep = dependencies.find { |d| d.name.casecmp(dependency_details.name)&.zero? }
|
158
164
|
next unless dep
|
159
165
|
|
160
|
-
|
166
|
+
dep_metadata = T.let({}, T::Hash[Symbol, T.untyped])
|
161
167
|
# For peer dependencies, instruct updater to not directly update this dependency
|
162
|
-
|
168
|
+
dep_metadata[:information_only] = true unless dependency.name.casecmp(dependency_details.name)&.zero?
|
169
|
+
dep_metadata[:is_vulnerable] = vulnerable?
|
163
170
|
|
164
171
|
# rebuild the new requirements with the updated dependency details
|
165
172
|
updated_reqs = dep.requirements.map do |r|
|
166
173
|
r = r.clone
|
167
|
-
r[:
|
174
|
+
T.let(r[:metadata], T::Hash[Symbol, T.untyped])[:previous_requirement] = r[:requirement] # keep old version
|
175
|
+
r[:requirement] = dependency_details.version # set new version
|
168
176
|
r[:source] = {
|
169
177
|
type: "nuget_repo",
|
170
178
|
source_url: dependency_details.info_url
|
@@ -172,17 +180,44 @@ module Dependabot
|
|
172
180
|
r
|
173
181
|
end
|
174
182
|
|
183
|
+
reqs = dep.requirements
|
184
|
+
unless vulnerable?
|
185
|
+
updated_reqs = updated_reqs.filter do |r|
|
186
|
+
req_metadata = T.let(r.fetch(:metadata, {}), T::Hash[Symbol, T.untyped])
|
187
|
+
!T.let(req_metadata[:is_transitive], T::Boolean)
|
188
|
+
end
|
189
|
+
reqs = reqs.filter do |r|
|
190
|
+
req_metadata = T.let(r.fetch(:metadata, {}), T::Hash[Symbol, T.untyped])
|
191
|
+
!T.let(req_metadata[:is_transitive], T::Boolean)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# report back the highest version that all of these dependencies can be updated to
|
196
|
+
# this will ensure that we get a chance to update all relevant dependencies
|
197
|
+
max_updatable_version = updated_reqs.filter_map do |r|
|
198
|
+
v = T.let(r.fetch(:requirement, nil), T.nilable(String))
|
199
|
+
next unless v
|
200
|
+
|
201
|
+
Dependabot::Nuget::Version.new(v)
|
202
|
+
end.max
|
203
|
+
next unless max_updatable_version
|
204
|
+
|
205
|
+
previous_version = T.let(dep.requirements.first&.fetch(:requirement, nil), T.nilable(String))
|
175
206
|
Dependency.new(
|
176
207
|
name: dep.name,
|
177
|
-
version:
|
208
|
+
version: max_updatable_version.to_s,
|
178
209
|
requirements: updated_reqs,
|
179
|
-
previous_version:
|
180
|
-
previous_requirements:
|
210
|
+
previous_version: previous_version,
|
211
|
+
previous_requirements: reqs,
|
181
212
|
package_manager: dep.package_manager,
|
182
|
-
metadata:
|
213
|
+
metadata: dep_metadata
|
183
214
|
)
|
184
215
|
end
|
185
216
|
end
|
217
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
218
|
+
# rubocop:enable Metrics/MethodLength
|
219
|
+
# rubocop:enable Metrics/BlockLength
|
220
|
+
# rubocop:enable Metrics/AbcSize
|
186
221
|
|
187
222
|
sig { returns(T::Array[Dependabot::Nuget::NativeDependencyDetails]) }
|
188
223
|
def updated_dependency_details
|
@@ -37,10 +37,11 @@ module Dependabot
|
|
37
37
|
def updated_requirements
|
38
38
|
return requirements unless latest_version
|
39
39
|
|
40
|
-
# NOTE: Order is important here. The FileUpdater needs the updated
|
41
|
-
# requirement at index `i` to correspond to the previous requirement
|
42
|
-
# at the same index.
|
43
40
|
requirements.map do |req|
|
41
|
+
req[:metadata] ||= {}
|
42
|
+
req[:metadata][:is_transitive] = false
|
43
|
+
req[:metadata][:previous_requirement] = req[:requirement]
|
44
|
+
|
44
45
|
next req if req.fetch(:requirement).nil?
|
45
46
|
next req if req.fetch(:requirement).include?(",")
|
46
47
|
|
@@ -56,7 +57,6 @@ module Dependabot
|
|
56
57
|
latest_version.to_s
|
57
58
|
)
|
58
59
|
end
|
59
|
-
|
60
60
|
next req if new_req == req.fetch(:requirement)
|
61
61
|
|
62
62
|
req.merge(requirement: new_req, source: updated_source)
|
@@ -166,7 +166,8 @@ module Dependabot
|
|
166
166
|
requirements: updated_requirements,
|
167
167
|
previous_version: dependency.version,
|
168
168
|
previous_requirements: dependency.requirements,
|
169
|
-
package_manager: dependency.package_manager
|
169
|
+
package_manager: dependency.package_manager,
|
170
|
+
metadata: { is_vulnerable: vulnerable? }
|
170
171
|
)
|
171
172
|
updated_dependencies = [updated_dependency]
|
172
173
|
updated_dependencies += DependencyFinder.new(
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dependabot-nuget
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.272.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dependabot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-22 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.272.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.272.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rubyzip
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -463,7 +463,7 @@ licenses:
|
|
463
463
|
- MIT
|
464
464
|
metadata:
|
465
465
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
466
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
466
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.272.0
|
467
467
|
post_install_message:
|
468
468
|
rdoc_options: []
|
469
469
|
require_paths:
|