npm Staged Publishing Turns Package Release Security Into a Human Approval Workflow
npm staged publishing is a small change to a command line and a large change to the trust model behind JavaScript releases. For years, maintainers had an awkward choice: keep publishing manual and friction-heavy, or give CI enough authority to make a package live while everyone hopes the workflow, token, runner, dependencies, and maintainer accounts remain clean. That was never a comfortable bargain. It was just the one the ecosystem normalized.
GitHub’s new staged publishing flow, generally available with npm CLI 11.15.0, draws the boundary in a better place. CI can build and upload the package tarball into a non-public staging queue. A human maintainer then approves the package with 2FA before it becomes installable. Automation produces the artifact; a person performs the release act. That is the right split.
This is not release-process theater. It is the supply-chain equivalent of requiring a human to turn the key before launch.
CI can stage; humans should publish
The new workflow changes the critical command from direct npm publish to npm stage publish. Staging uploads the package into a holding area where it is not publicly installable. Maintainers can then inspect, list, view, download, approve, or reject staged versions through the CLI or npmjs.com. The npm stage command set includes publish, list, view, approve, reject, and download.
The important security detail is where 2FA lands. Staging itself does not require 2FA, which makes it suitable for automated workflows. Approval and rejection do require 2FA. npm’s docs describe this as “proof-of-presence”: a human has to authenticate at the moment the package becomes public. That does not solve every supply-chain problem, but it removes one of the worst defaults: compromised CI equals instant public release.
Staged packages also share the same semver uniqueness index as published versions. You cannot publish a version that already exists as staged for that package. Maintainers can stage multiple versions, and normal publishing can continue while staged versions are pending. The feature requires the package to already exist on npm, write access to the package, and 2FA enabled on the maintainer account.
GitHub’s recommended pairing is the key: use staged publishing with trusted publishing/OIDC configured as stage-only. Trusted publishing already helps remove long-lived npm tokens from CI by letting supported providers authenticate via short-lived identity. Stage-only trusted publishing goes further: CI can prepare the release artifact, but cannot make it live. That is the workflow most serious packages should be moving toward.
The tarball deserves review, not just the repo
The best argument for staged publishing is that the thing users install is not your GitHub repository. It is the package tarball. Build steps, generated files, ignored files, bundlers, lifecycle scripts, provenance metadata, and packaging mistakes all sit between source and install. Reviewing a pull request is necessary. It is not sufficient if nobody checks the artifact that actually goes to the registry.
npm stage download should become part of the release checklist for high-impact packages. Download the staged tarball. Inspect its file list. Check package metadata, lifecycle scripts, bundled dependencies, generated output, and any unexpected binary or minified payloads. Compare it with the previous release. Make sure the version, changelog, provenance, and package contents match the intent. Then approve.
That may sound heavy for a tiny package. It is not heavy for packages with meaningful downstream users. JavaScript’s supply chain has repeatedly learned that small packages can become large blast radiuses. If your package is widely installed, part of your job is making release compromise harder than pushing a bad workflow or stealing a token.
The feature also creates a cleaner separation of duties without requiring every project to adopt heavyweight enterprise process. One maintainer can stage through CI; another can approve. Or a maintainer can approve after CI produces an artifact from a protected tag. The point is not bureaucracy. The point is forcing the release moment to be explicit, authenticated, and inspectable.
Install-source controls close another old door
npm 11.15.0 also adds --allow-file, --allow-remote, and --allow-directory, extending the source allowlist model beyond the existing --allow-git flag. Each can be set to all, none, or root. The current default remains all, while --allow-git is scheduled to move from all to none in npm CLI v12.
This matters because dependency specifications do not have to come from the npm registry. Git URLs, local paths, directories, and remote tarballs can be legitimate. They are also a neat way to route around the controls companies put around registry packages. A dependency graph that silently pulls a tarball from a remote URL or a Git repo is harder to audit, mirror, scan, and reproduce.
The root mode is the practical compromise. There is a meaningful difference between a top-level application intentionally depending on a local package during development and a transitive dependency introducing a remote tarball surprise. Allowing root-level exceptions while blocking transitive ones gives platform teams a rollout path. Start permissive, observe usage, move CI toward stricter settings, and then ratchet toward none where possible.
For companies, these flags belong in CI before they belong on every developer laptop. CI is where reproducibility and policy enforcement matter most. Run installs with explicit allow-* settings, log violations, and figure out which exceptions are intentional. If a build breaks because a transitive dependency depended on a Git URL nobody knew about, congratulations: that is exactly the kind of thing you wanted to learn in CI instead of during an incident.
Agents make this more urgent, not less
This change also belongs in the coding-agent governance conversation. Agents increasingly edit package.json, update dependencies, generate release workflows, and “helpfully” modify CI. That means agents can also weaken supply-chain boundaries: adding a remote tarball, introducing a Git dependency, creating a publish token, skipping 2FA, or turning a trusted publisher into a direct publish path because it made the workflow pass.
If your team uses Codex, Copilot, Claude Code, Cursor, or any other coding agent on JavaScript projects, release workflow files should be treated as high-risk edit surfaces. Require human review for changes to .npmrc, package.json, package-lock.json, publishing workflows, trusted publisher configuration, and provenance settings. Ask reviewers to check whether the agent preserved stage-only publishing and explicit install-source controls. “The tests passed” is not enough if the bot quietly handed CI publish authority.
The public reaction was minimal during the research window: Hacker News showed one matching story with four points and no comments. That is normal. Supply-chain controls do not trend until after someone needs them. The right time to adopt them is before your package name appears in a postmortem.
The migration checklist is concrete. Upgrade release workflows to npm CLI 11.15.0 or newer in a test package. Configure trusted publishing to allow npm stage publish only where possible. Remove long-lived granular access tokens, especially anything with bypass-2FA power. Enable 2FA on maintainer accounts. Add staged-tarball inspection to the release process. Pilot stricter allow-file, allow-remote, allow-directory, and allow-git settings in CI. Document the exceptions instead of letting them hide in dependency graphs.
The broader industry point is encouraging. JavaScript package security is slowly moving from token trust toward workflow design: OIDC, trusted publishers, provenance, explicit source controls, staged artifacts, and human release approval. None of these is sufficient alone. Together, they make the easy path safer than the dangerous shortcut. That is how ecosystems actually improve.
Staged publishing is not friction for its own sake. It is a human checkpoint placed exactly where automated compromise hurts most: the moment a package becomes installable by the world. If you maintain anything with real users, this should move from “interesting changelog item” to “planned migration.”
Sources: GitHub Changelog, npm staged publishing docs, npm stage CLI reference, npm trusted publishers docs