You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is not just a request to bump a dependency for a CVE
This is not solely a request to bump a dependency for a CVE
Current Behavior
When publishing with an externally generated provenance bundle under OIDC trusted
publishing (npm publish <tarball> --provenance-file=<bundle> from GitHub Actions,
public repository, public package), npm silently discards the supplied bundle and
publishes its own auto-generated provenance instead.
lib/commands/publish.js calls oidc() unconditionally before reading credentials
(L145-L155). The OIDC flow auto-enables provenance when the config is at its default
(lib/utils/oidc.js L144-L168: config.isDefault('provenance') → opts.provenance = true).
In libnpmpublish/lib/publish.jsbuildMetadata() (L145-L179), the inner if (provenance === true) branch runs generateProvenance(), so the inner else
(verifyProvenance(subject, provenanceFile), the only code path that reads the
supplied file) never executes.
The registry therefore receives npm's generated statement (builder.id = https://github.com/actions/runner/github-hosted, internalParameters.github,
sha512-only subject) in place of the supplied one. No warning or error is emitted;
the publish "succeeds".
The same structure exists in v11.5.1 (commit da1d4d2), so the whole trusted-publishing
release line is affected.
Additionally, every documented way to disable automatic provenance is unusable in
combination with --provenance-file:
NPM_CONFIG_PROVENANCE=false: on 12.0.2 the env value is silently skipped when --provenance-file is set via CLI (the where === 'env' carve-out in @npmcli/config#loadObject); on 11.5.1 the same combination throws --provenance can not be provided when using --provenance-file.
provenance=false in user/global npmrc: throws the same exclusive-config error.
publishConfig.provenance=false: the manifest flatten happens afteroidc()
runs (commands/publish.js L248-L280 vs L145-L155), so it cannot prevent the
auto-enable.
Expected: an explicitly supplied --provenance-file takes precedence over the implicit
OIDC auto-provenance (the supplied bundle is verified via verifyProvenance() and
published), or, if the combination is considered invalid, npm rejects it explicitly
with a clear error. npm must not silently discard a caller-supplied, cryptographically
meaningful artifact. The trusted-publishing documentation should also state how
automatic generation interacts with --provenance-file.
Steps To Reproduce
Environment: GitHub Actions workflow on a public repository, with permissions: id-token: write, and an npm trusted publisher configured for the
repository + workflow filename. Target package is public.
Config: no provenance settings anywhere (defaults); generate or obtain a valid
Sigstore provenance bundle for the tarball by external means (the documented purpose
of --provenance-file, e.g. PR feat: add --provenance-file flag for publish command #6490).
Run:npm publish <pkg>.tgz --provenance-file=<bundle>.json --registry=https://registry.npmjs.org
(npm 11.5.1+; verified through 12.0.2).
After publish, fetch https://registry.npmjs.org/-/npm/v1/attestations/<url-encoded-pkg>@<version>,
select the https://slsa.dev/provenance/v1 entry, and base64-decode .bundle.dsseEnvelope.payload.
Observe: the statement is npm's generated shape
(externalParameters.workflow, internalParameters.github, builder.id = https://github.com/actions/runner/github-hosted, sha512-only subject),
not the supplied statement. The supplied bundle was never read.
Note: npm publish --dry-run does not invoke libnpmpublish's provenance path, so this
is only observable on a real publish — a disposable scoped test package is recommended.
Real-world evidence: observed during a live publish of @windlass/vers-js@0.1.2
(workflow run: https://github.com/windlasstech/vers-js/actions/runs/31840088262); the
registry's SLSA provenance payload differs byte-for-byte from the bundle that was
passed via --provenance-file (preserved build artifact available on request).
Environment
npm: 11.17.0 (observed live); code path verified identical in 11.5.1 and 12.0.2 sources
Node.js: v24.19.0
OS Name: Ubuntu 24.04.4 LTS (GitHub-hosted runner)
System Model Name: GitHub Actions ubuntu-24.04
npm config:
; node bin location = /opt/hostedtoolcache/node/24.19.0/x64/bin/node; node version = v24.19.0; npm local prefix = /home/runner/work/vers-js/vers-js; npm version = 11.17.0; cwd = /home/runner/work/vers-js/vers-js; HOME = /home/runner; Run `npm config ls -l` to show all defaults.
Note: the original failure occurred inside a reusable workflow that invokes npm
with stronger isolation (stripped NPM_CONFIG_* env, empty user/global
config files, isolated temp cwd) — so ambient configuration cannot explain the
substitution. The reusable-workflow indirection does not affect the bug: npm still
runs on a GitHub-hosted runner with GITHUB_ACTIONS=true and OIDC request variables
present, which is all the auto-enable path requires.
Full downstream root-cause analysis, including a byte-level comparison of the
supplied bundle versus the statement actually published by the registry: windlasstech/slsa-builder#97
Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
When publishing with an externally generated provenance bundle under OIDC trusted
publishing (
npm publish <tarball> --provenance-file=<bundle>from GitHub Actions,public repository, public package), npm silently discards the supplied bundle and
publishes its own auto-generated provenance instead.
Mechanism (npm 12.0.2, commit b888cc9):
lib/commands/publish.jscallsoidc()unconditionally before reading credentials(L145-L155). The OIDC flow auto-enables provenance when the config is at its default
(
lib/utils/oidc.jsL144-L168:config.isDefault('provenance')→opts.provenance = true).libnpmpublish/lib/publish.jsbuildMetadata()(L145-L179), the innerif (provenance === true)branch runsgenerateProvenance(), so the innerelse(
verifyProvenance(subject, provenanceFile), the only code path that reads thesupplied file) never executes.
https://github.com/actions/runner/github-hosted,internalParameters.github,sha512-only subject) in place of the supplied one. No warning or error is emitted;
the publish "succeeds".
The same structure exists in v11.5.1 (commit da1d4d2), so the whole trusted-publishing
release line is affected.
Additionally, every documented way to disable automatic provenance is unusable in
combination with
--provenance-file:NPM_CONFIG_PROVENANCE=false: on 12.0.2 the env value is silently skipped when--provenance-fileis set via CLI (thewhere === 'env'carve-out in@npmcli/config#loadObject); on 11.5.1 the same combination throws--provenance can not be provided when using --provenance-file.provenance=falsein user/global npmrc: throws the same exclusive-config error.publishConfig.provenance=false: the manifest flatten happens afteroidc()runs (
commands/publish.jsL248-L280 vs L145-L155), so it cannot prevent theauto-enable.
Expected Behavior
The
--provenance-filedocumentation states: "When publishing, the provenance bundleat the given path will be used."
Expected: an explicitly supplied
--provenance-filetakes precedence over the implicitOIDC auto-provenance (the supplied bundle is verified via
verifyProvenance()andpublished), or, if the combination is considered invalid, npm rejects it explicitly
with a clear error. npm must not silently discard a caller-supplied, cryptographically
meaningful artifact. The trusted-publishing documentation should also state how
automatic generation interacts with
--provenance-file.Steps To Reproduce
permissions: id-token: write, and an npm trusted publisher configured for therepository + workflow filename. Target package is public.
Sigstore provenance bundle for the tarball by external means (the documented purpose
of
--provenance-file, e.g. PR feat: add --provenance-file flag for publish command #6490).npm publish <pkg>.tgz --provenance-file=<bundle>.json --registry=https://registry.npmjs.org(npm 11.5.1+; verified through 12.0.2).
https://registry.npmjs.org/-/npm/v1/attestations/<url-encoded-pkg>@<version>,select the
https://slsa.dev/provenance/v1entry, and base64-decode.bundle.dsseEnvelope.payload.(
externalParameters.workflow,internalParameters.github,builder.id = https://github.com/actions/runner/github-hosted, sha512-only subject),not the supplied statement. The supplied bundle was never read.
Note:
npm publish --dry-rundoes not invokelibnpmpublish's provenance path, so thisis only observable on a real publish — a disposable scoped test package is recommended.
Real-world evidence: observed during a live publish of
@windlass/vers-js@0.1.2(workflow run: https://github.com/windlasstech/vers-js/actions/runs/31840088262); the
registry's SLSA provenance payload differs byte-for-byte from the bundle that was
passed via
--provenance-file(preserved build artifact available on request).Environment
ubuntu-24.04with stronger isolation (stripped NPM_CONFIG_* env, empty user/global
config files, isolated temp cwd) — so ambient configuration cannot explain the
substitution. The reusable-workflow indirection does not affect the bug: npm still
runs on a GitHub-hosted runner with GITHUB_ACTIONS=true and OIDC request variables
present, which is all the auto-enable path requires.
Full downstream root-cause analysis, including a byte-level comparison of the
supplied bundle versus the statement actually published by the registry:
windlasstech/slsa-builder#97