Description
Is there an existing issue for this?
- I have searched the existing issues
This issue exists in the latest npm version
- I am using the latest npm
Current Behavior
With min-release-age (or its flattened equivalent before) set in .npmrc or via CLI, npm audit signatures fails with ETARGET for any package whose lockfile-pinned version was published after the cutoff — even though that exact version has already been installed by npm ci in the same pipeline run, moments earlier.
Observed in a CI pipeline with min-release-age=7:
> npm audit signatures
npm warn Fetching verification keys using TUF failed. Fetching directly from https://<private-registry>/.
npm error code ETARGET
npm error notarget No matching version found for caniuse-lite@1.0.30001790 with a date before 4/16/2026, 7:54:30 PM.
npm error notarget In most cases you or one of your dependencies are requesting a package version that doesn't exist.
##[error]Bash exited with code '1'.
Timeline of that same pipeline run:
npm ci— succeeds, installscaniuse-lite@1.0.30001790(pinned inpackage-lock.json) intonode_modules.npm audit signatures— fails withETARGETon the exact same version that was just installed.
The version exists, it's in the lockfile, it's on disk. It's rejected only because audit signatures re-resolves through the registry with the before filter applied, and the packument's time[version] is newer than the cutoff.
Expected Behavior
npm audit signatures should verify the signatures of the packages that are actually installed, using the exact versions recorded in node_modules / package-lock.json. min-release-age is an install-time policy; by the time we reach audit signatures, the policy has already been evaluated (or bypassed) during npm ci. Re-applying it during signature verification can only produce false negatives on already-admitted code — it cannot protect anything that isn't already protected, because the code is already on disk.
Concretely: the pacote.manifest() call inside audit signatures is asked for an exact name@version spec (not a range, not a tag), so the --before filter contributes nothing beyond turning valid lookups into ETARGETs.
Steps To Reproduce
- Create a project with a single dep:
// package.json { "name": "repro", "version": "1.0.0", "dependencies": { "kms-demo": "1.0.0" } }
npm installto producenode_modulesandpackage-lock.json.- Run:
(or
npm audit signatures --before=2020-01-01
--min-release-age=99999, or setmin-release-age=99999in.npmrc) - Observe:
npm error code ETARGET npm error notarget No matching version found for kms-demo@1.0.0 with a date before 1/1/2020, 12:00:00 AM.
The package is installed. The version is pinned. audit signatures still refuses to verify it.
Environment
- npm: 11.x (bug is in code still present on
latest) - Relevant code path:
lib/utils/verify-signatures.js— thepacote.manifest()call spreadsthis.npm.flatOptions, which carriesbeforederived frommin-release-ageviaworkspaces/config/lib/definitions/definitions.js#L1362-L1384.
Root cause
workspaces/config/lib/definitions/definitions.js:1377-1383 flattens min-release-age into flatOptions.before = Date.now() - days * 86400000. Every consumer of flatOptions inherits that cutoff.
lib/utils/verify-signatures.js:294-306:
async verifySignatures (name, version, registry) {
const {
_integrity: integrity,
_signatures,
_attestations,
_attestationBundles,
_resolved: resolved,
} = await pacote.manifest(`${name}@${version}`, {
verifySignatures: true,
verifyAttestations: true,
...this.buildRegistryConfig(registry),
...this.npm.flatOptions, // ← leaks `before` into manifest lookup
})
...
}name and version come from arb.loadActual() a few frames up — they are the exact versions present in node_modules. The pacote spec is exact. The before filter has no legitimate work to do here — its only possible effect is to reject a version that is already installed.
Proposed fix
One line, in lib/utils/verify-signatures.js:
} = await pacote.manifest(`${name}@${version}`, {
verifySignatures: true,
verifyAttestations: true,
...this.buildRegistryConfig(registry),
...this.npm.flatOptions,
+ before: null, // audit signatures verifies exact installed versions; install-time policy does not apply here
})Why safe:
audit signaturesonly operates onloadActual()trees — the code is already installed. Strippingbeforecannot bypass the policy because the policy decision happened during install and can't be un-done at audit time.- The spec is exact (
name@version), not a range/tag —beforehas no legitimate filtering work to do. - No breaking change: the fix only removes false-negative ETARGETs; verification results themselves are unchanged.
Relation to prior issues / RFCs
- Distinguishes from #9212 (closed, not-planned): that issue was about
npm audit fixdeclining to install a fix younger than the cutoff. That is policy working as intended —audit fixmust respect install-time policy because it installs new code. This issue is aboutnpm audit signatureserroring on code that was already installed bynpm ciseconds earlier in the same pipeline. That is not policy working — it is a pipeline-internal self-contradiction:npm ciadmitted the version,audit signaturespretends it does not exist. - Aligned with RRFC #856 (open): "Ability to override
--min-release-ageon CLI or in the lockfile" proposes that lockfile-pinned versions be implicit exceptions to the policy. This fix is a narrower, immediately-actionable subset: onlyaudit signatures(which cannot install anything) is exempted, and only for exact-version lookups. No new config surface. - Related: #9005 (closed):
min-release-ageleaking intogit dep preparation— same family of "the flattenedbeforefilter is reaching internal invocations where it shouldn't".
Additional context — what the fix does not change
npm install/npm cistill applymin-release-ageexactly as today.npm audit fixstill appliesmin-release-ageexactly as today.npm audit signatureson a package that is not installed (e.g., after a partial install) behaves as it does today — it returns what pacote returns; the exact-version lookup still fails if the tarball is genuinely gone.- No change to TUF/keys fetch; no change to signature/attestation logic.
Happy to send a PR with the 1-line fix + regression test if this looks good.
Activity
Bafff commentedon May 8, 2026
Hey, any thoughts on this?
JamieMagee commentedon May 28, 2026
PR up: #9430. Adds the
before: nullfix and a regression test. Thanks for the thorough issue.