Gate npm_and_yarn audit-fix fallbacks to security updates only#15051
Open
robaiken wants to merge 3 commits into
Open
Gate npm_and_yarn audit-fix fallbacks to security updates only#15051robaiken wants to merge 3 commits into
robaiken wants to merge 3 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR gates npm/yarn/pnpm audit-fix lockfile fallbacks so they only run for security updates, using metadata stamped by the npm_and_yarn update checker and forwarded through the file updater boundary.
Changes:
- Removes audit-fix fallback logic from
SubdependencyVersionResolver. - Adds
security_updatepropagation fromUpdateChecker→FileUpdater→ lockfile updaters. - Updates specs to cover security vs non-security audit-fix behavior.
Show a summary per file
| File | Description |
|---|---|
npm_and_yarn/lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb |
Removes audit-fix fallback handling from subdependency resolution. |
npm_and_yarn/lib/dependabot/npm_and_yarn/update_checker.rb |
Stamps updated dependencies with metadata[:security_update]. |
npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb |
Detects security-update metadata and forwards it to lockfile updaters. |
npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb |
Gates Yarn audit-fix fallback on security-update status. |
npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb |
Gates npm audit-fix fallback on security-update status. |
npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb |
Gates pnpm audit-fix fallback while retaining deep-update fallback. |
npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver_spec.rb |
Removes audit-fix resolver expectations and updates pnpm fallback expectations. |
npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker_spec.rb |
Adds security-update metadata stamping coverage. |
npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater_spec.rb |
Adds Yarn security/non-security audit-fix gating coverage. |
npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb |
Adds npm security/non-security audit-fix gating coverage. |
npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater_spec.rb |
Adds pnpm security/non-security audit-fix gating coverage. |
Copilot's findings
- Files reviewed: 11/11 changed files
- Comments generated: 5
d1641cb to
05b0ac4
Compare
kbukum1
approved these changes
May 20, 2026
This restricts the npm/yarn/pnpm 'audit fix' lockfile fallbacks so they
only run for security updates, where adding registry-level overrides to
package.json is justified by a known vulnerability. Regular version
bumps no longer trigger audit-fix.
Changes:
* Remove all audit-fix code from SubdependencyVersionResolver. The
resolver now reports the version directly from the updated lockfile;
audit-based version inference and the audit_fix_used metadata path
are gone.
* In FileUpdater, expose security_update? based on dependency metadata
(dependencies.any? { |d| d.metadata[:security_update] }) and forward
it as a constructor arg to YarnLockfileUpdater, NpmLockfileUpdater,
and PnpmLockfileUpdater.
* In each lockfile updater, gate the audit-fix fallback on both
Experiments.enabled?(:enable_audit_fix_fallback) AND the new
security_update? flag. pnpm's deep_update fallback (pnpm update
--depth Infinity) remains gated on the experiment only since it
doesn't modify package.json.
* In UpdateChecker, stamp metadata[:security_update] = true on every
Dependency returned from #updated_dependencies. This works across all
three unlock paths (:none, :own, :all) via overrides of
updated_dependency_without_unlock and
updated_dependency_with_own_req_unlock, plus the existing
build_updated_dependency hook.
Tests:
* New 'security_update metadata stamping' group in
update_checker_spec.rb verifies the flag is set across all unlock
paths when security_advisories are present, and absent otherwise.
* New gating contexts in the three lockfile updater specs assert that
audit-fix runs only when security_update: true AND the experiment is
enabled, and is skipped otherwise.
* Existing pnpm 'regular package dependency' test split into security
and non-security contexts to reflect the new gating.
* Audit-fix specs removed from subdependency_version_resolver_spec.rb.
- Make base UpdateChecker#updated_dependency_(without|with_own_req)_unlock overridable so npm_and_yarn can declare 'sig { override }' validly under Sorbet
- Restore speculative-version inference in SubdependencyVersionResolver, gated to security advisories + the enable_audit_fix_fallback experiment, so transitive security updates whose normal npm/yarn update is a no-op still flow through to the FileUpdater's audit-fix fallback
- Thread security_advisories through to SubdependencyVersionResolver and update its constructor matchers in update_checker_spec.rb
- Rewrite the two :none unlock metadata stamping tests to use the public updated_dependencies(requirements_to_unlock: :none) API instead of send(:updated_dependency_without_unlock)
- Add a security_update metadata bridge describe block in file_updater_spec.rb that verifies YarnLockfileUpdater, PnpmLockfileUpdater, and NpmLockfileUpdater receive security_update: true/false reflecting dependency metadata
…etadata stamp These tests target security advisories (locked transitive dep, duplicated dep, no-op fix_update scenarios) where the npm_and_yarn UpdateChecker now stamps :security_update => true on each returned dependency. Bring expected metadata in line with the new stamping contract introduced earlier in this PR.
947566b to
3bb1026
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What are you trying to accomplish?
Restrict the npm/yarn/pnpm
audit fixlockfile fallbacks so they only run for security updates, where adding registry-level overrides topackage.jsonis justified by a known vulnerability. Regular version bumps no longer triggeraudit fix.audit fixfor npm/yarn/pnpm has user-facing side-effects (it addsoverridestopackage.json, or rewrites lockfile entries based on advisory data) that are only appropriate for security updates. Running it as a generic fallback for every transitive-dependency update was producing surprising changes for routine version bumps.Production code changes:
SubdependencyVersionResolver(npm_and_yarn): removed allaudit fixcode. The resolver now reports the version straight from the updated lockfile. Theaudit_fix_usedmetadata path that flowed through here is gone.FileUpdater(npm_and_yarn): added asecurity_update?predicate that reads from dependency metadata (dependencies.any? { |d| d.metadata[:security_update] }) and forwards it as a boolean constructor arg toYarnLockfileUpdater,NpmLockfileUpdater, andPnpmLockfileUpdater.YarnLockfileUpdater/NpmLockfileUpdater/PnpmLockfileUpdater: each now takes asecurity_update:keyword arg. Theaudit fixfallback is gated on bothExperiments.enabled?(:enable_audit_fix_fallback)andsecurity_update?.UpdateChecker(npm_and_yarn): stampsmetadata[:security_update] = trueon everyDependencyreturned from#updated_dependencies, across all three unlock paths (:none,:own,:all), via overrides ofupdated_dependency_without_unlock,updated_dependency_with_own_req_unlock, and the existingbuild_updated_dependencyhook — all routed through awith_security_update_metadatahelper so the signal survives theUpdateChecker → FileUpdaterboundary.Anything you want to highlight for special attention from reviewers?
Dependency#metadata[:security_update]) fromUpdateCheckertoFileUpdater, and then as an explicit keyword arg (security_update:) fromFileUpdaterinto each lockfile updater. The metadata-only approach was considered but rejected for the lockfile updaters because they sometimes receive a singledependenciesarray containing a mix of stamped and unstamped deps; an explicit arg keeps the gate unambiguous.deep_updatefallback (pnpm update --depth Infinity) is intentionally not gated bysecurity_update?— it does not modifypackage.json, so it remains a safe general fallback gated only on the:enable_audit_fix_fallbackexperiment. Onlypnpm audit --fix(which writesoverrides) is gated onsecurity_update?.Dependabot::UpdateCheckers::Base(:none/:own/:all) needed three different stamping touch-points. The two single-dependency paths buildDependencyobjects in the base class, so we override those methods to apply the stamp aftersuper; the:allpath is built bybuild_updated_dependencyin the subclass, where the stamp is folded into the metadata hash before constructing theDependency.How will you know you've accomplished your goal?
Tests assert both directions of the gate:
update_checker_spec.rb(newsecurity_update metadata stampinggroup)security_update: truefor all unlock paths when advisories present; no stamp otherwise.yarn_lockfile_updater_spec.rbrun_yarn_audit_fix_commandruns only whensecurity_update: trueAND experiment enabled; skipped when either is off.npm_lockfile_updater_spec.rbrun_npm_audit_fix_command.pnpm_lockfile_updater_spec.rbaudit --fixonly with it.subdependency_version_resolver_spec.rbLocal test results (run via
bin/test npm_and_yarn):update_checker_spec.rb(new group only): 6 examples, 0 failures.subdependency_version_resolver_spec.rb: 11 examples, 0 failures.file_updater_spec.rb: 165 examples, 0 failures, 2 pending (pre-existing).{yarn,npm,pnpm}_lockfile_updater_spec.rbcombined: 119 examples, 1 pending, 1 failure (pre-existing:pnpm/github_dependency_privatetimes out making a real network call to GitHub — unrelated to this change).Checklist