npm is turning publication into a two-phase operation
On September 18, 2026, GitHub announced a stage-only permission for npm granular access tokens. A workflow holding that credential may run npm stage publish, but npm rejects a direct npm publish. A maintainer later reviews the staged version and approves it with two-factor authentication. This is not just another token scope. It lets teams split the authority to prepare a release from the authority to make it public.
The timing is operationally important. npm says it is targeting January 2027 to remove direct publishing through granular access tokens configured to bypass 2FA. Maintainers that depend on those long-lived credentials need a replacement. The preferred path is trusted publishing, where npm accepts a short-lived OpenID Connect credential from an authorized GitHub Actions, GitLab CI/CD, or CircleCI workflow. Where that path is not available, a narrowly scoped stage-only token keeps automation running without giving the stored credential direct publication authority.
Staged publishing itself is broader than the new token. npm has supported the stage, review, and approve sequence since May 2026, and on October 2 it extended staging to create new packages. The current documentation requires npm CLI 11.15.0 or later, Node.js 22.14.0 or later, publish access, and 2FA on the maintainer account. A new package gets a public 0.0.0-stage placeholder, while the staged version and its contents remain unavailable until approval.
The security claim needs precision. A stolen stage-only token cannot directly publish a new version, but it remains a package write credential. GitHub notes that it may retain other write permissions, including moving dist-tags and deprecating versions. Staging also does not prove that the tarball came from the intended commit, that tests were meaningful, that lifecycle scripts are safe, or that the reviewer inspected the artifact. The gate lowers one class of credential-compromise impact. It does not turn release engineering into a solved problem.
The release gate is not “someone typed a one-time code.” It is “a named reviewer approved this exact artifact, from this build identity, with this evidence, for this version.”
Understand the staged object before designing the gate
source commit + locked dependencies
-> isolated build and tests
-> npm pack + hashes + manifest
-> npm stage publish
-> immutable stage ID reserves package@version
-> reviewer: list / view / download / verify
-> npm stage approve <stage-id> + 2FA
-> public registry + dist-tag decision
-> install, signature, provenance, and smoke verification
npm stage publish packs the selected package and places that version in a registry staging state. The operation does not prompt for 2FA. Staged versions share the same package-and-semver uniqueness constraint as published versions, so a staged 2.4.0 reserves that coordinate. Teams may stage multiple versions, and normal direct publishing can continue while staged versions are pending unless package policy removes that path.
Reviewers have explicit operations. npm stage list finds pending stages; npm stage view <stage-id> shows metadata; npm stage download <stage-id> retrieves the tarball; and npm stage approve or npm stage reject resolves it. Approval publishes the version and demands 2FA. These review and decision subcommands require interactive authentication; OIDC identities cannot approve or reject a stage.
That last constraint is the proof-of-presence boundary. It should not be confused with proof of artifact. The registry knows which staged object the command names and which maintainer authenticated. Your release process must supply the evidence that connects the staged object to the intended source, build, policy, and release plan.
A useful review starts by downloading the registry-held tarball rather than inspecting the workspace left by CI. Run npm pack --dry-run before staging to catch packaging mistakes, but treat the staged download as the approval object. Hash it, list its files, inspect package.json, compare its version and repository metadata, scan lifecycle scripts, and execute installation tests in a disposable environment.
Choose among three authority models deliberately
| Model | Credential and authority | Best fit | Main residual risk |
| Direct token publish | Long-lived token can make a version public | Legacy workflows during migration | Token theft or workflow compromise becomes immediate release authority |
| Stage-only token | Long-lived token can upload stages but not directly publish | Unsupported CI providers or workflows that cannot use OIDC | Stored write credential remains; human gate can become a rubber stamp |
| OIDC trusted publisher, stage-only | Short-lived workflow identity may stage; human approves with 2FA | Supported cloud CI with stable repository/workflow identity | Workflow and source compromise can prepare a malicious stage; reviewer must detect it |
Trusted publishing eliminates a persistent npm write secret from the release job. npm binds the publisher configuration to provider claims such as repository and workflow. The current supported set is GitHub-hosted Actions, GitLab.com shared runners, and CircleCI cloud. Self-hosted runners remain unsupported. A newly created configuration must complete its first successful publish within two days or it expires.
Permissions are independent. A trusted publisher can always be allowed to stage, and maintainers decide whether it may also publish directly or manage dist-tags. Dist-tag access is disabled by default and requires newer npm releases than the base staging feature. Do not grant tag movement merely because a workflow stages versions; changing latest can redirect consumers even when no new tarball is created.
Trusted publishing can automatically generate provenance for qualifying public packages built from public repositories. Provenance identifies the build environment, source commit, workflow, and transparency-log record. It is valuable evidence, but it does not prove that the source was safe or the reviewer intended the change. Private repositories do not receive npm provenance for public packages under the current limitation, and CircleCI trusted publishing does not currently generate it.
Bind approval to a release manifest
The manifest is the handoff between automation and reviewer. It should be generated before staging, stored with tamper-evident build artifacts, and reconciled against the downloaded staged tarball. A release system that asks a maintainer to approve “stage 7f91” without this context has separated duties but not created informed review.
schema: npm.release-gate/v1
package: "@example/parser"
version: "4.8.0"
source:
repository: "https://github.com/example/parser"
commit: "4df2...91ac"
tag_candidate: "v4.8.0"
build:
workflow: ".github/workflows/release.yml"
run_id: "38155048656"
node: "24.10.0"
npm: "12.2.0"
artifact:
filename: "example-parser-4.8.0.tgz"
sha256: "8a6e...c122"
stage_id: "STAGE_ID_AFTER_UPLOAD"
file_count: 127
policy:
tests: "pass"
dependency_diff: "reviewed"
lifecycle_scripts: ["prepare"]
provenance_expected: true
direct_publish_allowed: false
dist_tag_change: "none"
review:
required_role: "release-maintainer"
separation_from_committer: true
expires_at: "2026-10-17T12:00:00Z"
post_release:
install_fixture: "consumer-smoke@3"
rollback_owner: "package-oncall"
Record absence as evidence too. If provenance is not expected because the source repository is private, say so. If a package contains an install script, list it and link the review. If an emergency release weakens separation of duties, require a named exception, narrow duration, and follow-up review. Silent exceptions are how temporary shortcuts become permanent authority.
For monorepos, create one manifest per package plus a batch manifest that names the dependency graph and intended approval unit. npm's public roadmap acknowledges the need for publishing plans and batch approval. Until the available workflow matches your scale, do not turn a 200-package release into 200 context-free approval clicks. Group packages only when they share the same source commit, build run, reviewer context, dependency plan, and rollback decision.
Make CI prepare evidence, not make the decision
A GitHub Actions workflow using OIDC should request id-token: write, pin its actions, use a release environment, build from locked dependencies, and call npm stage publish. The trusted publisher configuration on npm must name the exact repository and workflow file, with stage-only permission and no direct publish permission.
name: stage-npm-release
on:
workflow_dispatch:
inputs:
package_dir: { required: true, type: string }
permissions:
contents: read
id-token: write
jobs:
stage:
environment: npm-release-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
package-manager-cache: false
- run: npm ci --ignore-scripts
- run: npm test
- run: npm pack --dry-run
working-directory: ${{ inputs.package_dir }}
- run: ./scripts/build-release-manifest.sh
working-directory: ${{ inputs.package_dir }}
- run: npm stage publish
working-directory: ${{ inputs.package_dir }}
This example is a skeleton, not paste-and-trust policy. A package whose build legitimately needs lifecycle scripts must enable them in a controlled step, pin the toolchain, and record the behavior. A native module needs platform fixtures. A package with generated files needs a clean-tree rebuild comparison. A package with private dependencies still needs a read-only installation credential because trusted publishing authenticates publication, not private dependency reads.
With a stage-only granular token instead of OIDC, keep the same workflow but scope the token to the minimum packages, store it in the protected release environment, rotate it, monitor use, and use npm stage publish. Test that an accidental npm publish fails with E_STAGE_REQUIRED. A guardrail that has never been exercised is an assumption.
The reviewer must verify the registry-held artifact
- Resolve the stage. List pending stages, select the expected package and version, and confirm the stage was created by the approved workflow window.
- Download and hash. Retrieve the staged tarball with
npm stage download. Compare its digest, size, and file list with the signed manifest.
- Inspect package identity. Verify name, version, repository, license, exports, files, engines, publish configuration, dependencies, and lifecycle scripts.
- Trace source and build. Match commit, workflow, run, environment, toolchain, and provenance expectation. Confirm the source change passed protected-branch and review policy.
- Reperform critical checks. Install the tarball into a clean consumer fixture. Exercise imports, CLI entry points, types, native artifacts, and one negative case.
- Review authority changes. Confirm there is no unintended dist-tag change, maintainer change, token-policy change, or new-package namespace surprise.
- Approve or reject. Use the exact stage ID. Capture reviewer identity, decision, timestamp, rationale, and 2FA-protected action.
- Verify public state. Install by exact version, inspect metadata, run
npm audit signatures where applicable, check provenance, then apply the intended dist-tag through its separately authorized path.
Separate version publication from tag promotion when blast radius warrants it. Publishing 4.8.0 makes the coordinate available; moving latest changes the default consumers receive. A canary package or next tag can provide a bounded observation period, but only if the release plan states who may promote and what evidence qualifies.
Consumer verification matters because a successful registry response is not the business outcome. Verify that the intended tarball is retrievable from the registry, that signatures and provenance are present when expected, that installation does not execute surprising behavior, and that downstream smoke tests pass. Record the result in an execution receipt rather than ending the run at approval.
Failure modes that staging does not remove
| Failure | Why the gate misses it | Control |
| Reviewer approves from release notes only | The staged tarball may differ from source or intent | Download, hash, inventory, and install the staged artifact |
| Compromised workflow stages malware | Stage-only blocks publication, not preparation | Protected workflow, pinned dependencies, manifest, independent review |
| Stolen stage-only token moves a tag | Other package write permissions may remain | Prefer OIDC; test and minimize dist-tag authority |
| Monorepo approval fatigue | Dozens of nearly identical prompts become rubber stamps | Evidence-backed batch plan with bounded package graph |
| Wrong source identity in OIDC config | Configuration is not fully validated when saved | Exercise first publish within two days and verify exact claims |
| Private repo lacks provenance | Current npm provenance has public-source limits | Record limitation; retain signed internal build evidence |
| New package creates namespace surprise | 0.0.0-stage placeholder is public | Pre-approve package name, ownership, visibility, and legal checks |
| Approval succeeds, install fails | Registry transition is not consumer usability | Exact-version install, signature, provenance, and smoke verification |
Current community evidence is directional, not a product adoption study. The focused scan found a current Tensorlake package/repository compromise report and active developer concern about npm and PyPI supply-chain delays. The most visible Reddit reactions were blunt: “It's not all npm but it is always npm” and concern that the ecosystem will later question why these attack vectors were accepted. Those comments establish pain and distrust. They do not prove staged publishing would have prevented a specific incident.
Run a migration before the deadline becomes an outage
- Inventory every package, maintainer, publish workflow, token, runner type, CI provider, tag operation, private dependency, and emergency path.
- Classify each package into OIDC supported now, stage-only token required, or blocked by provider/runner/workflow constraints.
- Build the release manifest and consumer smoke fixture before changing credentials.
- Configure trusted publishing with stage-only permission where supported. Validate repository, workflow filename, environment, and
id-token: write.
- For remaining packages, create narrowly scoped stage-only tokens and prove direct publication fails.
- Run a rehearsal on a low-risk version: stage, download, verify, reject. Then repeat and approve.
- Exercise new-package creation, prereleases, dist-tags, monorepo batches, private dependencies, provenance expectations, and rollback separately.
- Remove direct-publish authority, revoke unused tokens, and monitor all publisher and package-maintainer changes.
- Document an emergency release path with the same artifact identity and a time-bounded exception review.
The target state is not “all releases need a person.” It is “public release authority is explicit and evidence-bound.” Some low-risk internal packages may choose direct OIDC publication with protected environments and automated policy. Critical public packages may require staging and independent review. The right unit of control is the package's blast radius, maintainer capacity, recovery model, and consumer trust, not one universal click count.
FAQ
What is an npm stage-only token?
It is a granular access token that may upload a version to npm's staging area but cannot publish that version directly. A maintainer must approve the stage with 2FA.
Should every project use staged publishing?
No. Use it where separation of preparation and publication reduces meaningful risk and reviewers can perform informed review. Direct OIDC publication may be reasonable for lower-risk packages with strong workflow controls. A ceremonial gate can add latency without adding safety.
Does staged publishing replace provenance?
No. Staging controls when a version becomes public. Provenance records where and how a qualifying artifact was built. Strong release systems use both and still inspect the artifact.
Can CI approve its own staged package?
Not through OIDC. npm's approve and reject operations require interactive authentication and proof of presence. If another bot holds a human-equivalent session, the separation has been defeated.
What should teams do first?
Inventory current bypass-2FA publishing tokens and workflows. Pick one low-risk package, generate an artifact manifest, configure stage-only authority, and rehearse both rejection and approval before removing the legacy path.
Sources and further reading
- GitHub Changelog: Stage-only npm tokens for safer automation - checked October 16, 2026.
- GitHub Changelog: npm staged publishing now supports creating new packages - checked October 16, 2026.
- npm Docs: Staged publishing for npm packages - checked October 16, 2026.
- npm CLI Docs: npm stage - checked October 16, 2026.
- npm Docs: About access tokens - checked October 16, 2026.
- npm Docs: Trusted publishing for npm packages - checked October 16, 2026.
- npm Docs: Viewing and verifying package provenance - checked October 16, 2026.
- npm Community: Safer publishing roadmap and January 2027 migration - checked October 16, 2026.
- npm Community: Upcoming changes to 2FA-bypass granular access tokens - checked October 16, 2026.
- npm CLI repository - checked October 16, 2026.
- Tensorlake: npm package and repository compromise report - checked October 16, 2026.
- OpenSSF Trusted Publishers guidance - checked October 16, 2026.
- r/programming: Cooldown or compromise discussion - checked October 16, 2026.
Continue with the SDK generation supply-chain guide, the AI contribution policy, or the agentic workflow approval guide.