Skip to content

Commit 88f4dff

Browse files
thavaahariharangitkbukum1
authored andcommitted
Review comments updated
1 parent 0e45c76 commit 88f4dff

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

npm_and_yarn/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ class VersionResolver
3434
T::Hash[String, T::Array[String]]
3535
)
3636

37+
# Maximum number of older versions to check when the latest version
38+
# triggers a pnpm trust downgrade. Prevents excessive subprocess spawning
39+
# for packages with many published versions.
40+
MAX_TRUST_DOWNGRADE_FALLBACK_ATTEMPTS = 5
41+
3742
# Error message returned by `yarn add` (for Yarn classic):
3843
# " > @reach/router@1.2.1" has incorrect peer dependency "react@15.x || 16.x || 16.4.0-alpha.0911da3"
3944
# "workspace-aggregator-<random-string> > test > react-dom@15.6.2" has incorrect peer dependency "react@^15.6.2"
@@ -251,6 +256,11 @@ def dependency_updates_from_full_unlock
251256
sig { returns(T::Boolean) }
252257
attr_reader :raise_on_ignored
253258

259+
sig { returns(T::Boolean) }
260+
def trust_downgrade_detected?
261+
@trust_downgrade_detected
262+
end
263+
254264
sig { params(dep: Dependabot::Dependency).returns(PackageLatestVersionFinder) }
255265
def latest_version_finder(dep)
256266
@latest_version_finder[dep] ||=
@@ -277,6 +287,7 @@ def find_version_without_trust_downgrade
277287
.select { |v| current_version.nil? || v > current_version }
278288
.sort
279289
.reverse
290+
.first(MAX_TRUST_DOWNGRADE_FALLBACK_ATTEMPTS)
280291

281292
candidate_versions.each do |candidate|
282293
next if version_has_trust_downgrade?(candidate)
@@ -288,29 +299,37 @@ def find_version_without_trust_downgrade
288299
end
289300

290301
Dependabot.logger.info(
291-
"No version of #{dependency.name} found without pnpm trust downgrade"
302+
"No version of #{dependency.name} found without pnpm trust downgrade " \
303+
"(checked #{candidate_versions.length} versions)"
292304
)
293305
nil
294306
end
295307

296308
# Checks whether a specific version triggers pnpm trust downgrade by running
297309
# the peer dependency check and inspecting the trust_downgrade_detected flag.
310+
# Saves and restores instance state to avoid polluting the caller's context.
298311
sig { params(version: Gem::Version).returns(T::Boolean) }
299312
def version_has_trust_downgrade?(version)
313+
saved_trust_downgrade = @trust_downgrade_detected
314+
saved_peer_errors = @peer_dependency_errors
315+
300316
@trust_downgrade_detected = false
301317
@peer_dependency_errors = nil
302318

303319
fetch_peer_dependency_errors(version: version)
304320

305-
# fetch_peer_dependency_errors sets @trust_downgrade_detected as a side effect
306-
# when pnpm returns ERR_PNPM_TRUST_DOWNGRADE
307-
result = T.unsafe(@trust_downgrade_detected)
321+
result = trust_downgrade_detected?
308322
if result
309323
Dependabot.logger.info(
310324
"pnpm trust downgrade also detected for #{dependency.name}@#{version}, skipping"
311325
)
312326
end
313327
result
328+
ensure
329+
# T.must needed because Sorbet considers locals potentially nil in ensure blocks;
330+
# @peer_dependency_errors doesn't need it since its type is already nilable.
331+
@trust_downgrade_detected = T.must(saved_trust_downgrade)
332+
@peer_dependency_errors = saved_peer_errors
314333
end
315334

316335
# rubocop:disable Metrics/PerceivedComplexity

npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/version_resolver_spec.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,67 @@
13011301
expect(result).to eq(Gem::Version.new("1.1.3"))
13021302
end
13031303
end
1304+
1305+
context "when fallback limit is exceeded" do
1306+
before do
1307+
# Stub the constant to 2 so we only check 2 candidates (1.2.0, 1.1.3)
1308+
stub_const(
1309+
"Dependabot::NpmAndYarn::UpdateChecker::VersionResolver::MAX_TRUST_DOWNGRADE_FALLBACK_ATTEMPTS", 2
1310+
)
1311+
1312+
# 1.2.0 and 1.1.3 are checked (within limit) but both fail;
1313+
# versions below are truncated by the limit.
1314+
allow(Dependabot::NpmAndYarn::Helpers).to receive(:run_pnpm_command) do |command, **_kwargs|
1315+
if command.include?("@1.3.0") || command.include?("@1.2.0") || command.include?("@1.1.3")
1316+
raise Dependabot::SharedHelpers::HelperSubprocessFailed.new(
1317+
message: "ERR_PNPM_TRUST_DOWNGRADE High-risk trust downgrade for " \
1318+
"\"left-pad\" (possible package takeover)",
1319+
error_context: {}
1320+
)
1321+
end
1322+
"" # Would pass, but not reached due to the limit
1323+
end
1324+
end
1325+
1326+
it "returns nil after checking only the limited number of versions" do
1327+
expect(resolver.latest_resolvable_version).to be_nil
1328+
end
1329+
end
1330+
1331+
context "when the only passing version equals the current version" do
1332+
let(:dependency) do
1333+
Dependabot::Dependency.new(
1334+
name: "left-pad",
1335+
version: "1.2.0",
1336+
requirements: [{
1337+
file: "package.json",
1338+
requirement: "^1.0.1",
1339+
groups: ["dependencies"],
1340+
source: { type: "registry", url: "https://registry.npmjs.org" }
1341+
}],
1342+
package_manager: "npm_and_yarn"
1343+
)
1344+
end
1345+
1346+
before do
1347+
# All versions above 1.2.0 (only 1.3.0 in this fixture) fail trust downgrade,
1348+
# and candidates below current version are filtered out.
1349+
allow(Dependabot::NpmAndYarn::Helpers).to receive(:run_pnpm_command) do |command, **_kwargs|
1350+
if command.include?("@1.3.0")
1351+
raise Dependabot::SharedHelpers::HelperSubprocessFailed.new(
1352+
message: "ERR_PNPM_TRUST_DOWNGRADE High-risk trust downgrade for " \
1353+
"\"left-pad@1.3.0\" (possible package takeover)",
1354+
error_context: {}
1355+
)
1356+
end
1357+
""
1358+
end
1359+
end
1360+
1361+
it "returns nil to avoid a no-op update" do
1362+
expect(resolver.latest_resolvable_version).to be_nil
1363+
end
1364+
end
13041365
end
13051366
end
13061367
end

0 commit comments

Comments
 (0)