Publish from one CI workflow that the registry trusts through OIDC instead of a stored token. On npm, configure the trusted publisher (npm CLI 11.5.1 or later) and then set publishing access to require two-factor authentication and disallow tokens, because OIDC alone does not revoke old tokens. On PyPI, publish with pypa/gh-action-pypi-publish, which also uploads attestations. Keep publishing in its own job and verify downloads with npm audit signatures or pypi-attestations.
- A trusted publisher binds publishing to a repository, workflow file and optional environment, and the registry issues a short-lived credential per run.
- npm accepts GitHub Actions on GitHub-hosted runners, GitLab CI/CD on GitLab.com and CircleCI cloud; self-hosted runners are not supported.
- npm revoked all classic tokens on 2025-12-09; granular write tokens default to 7 days and cannot exceed 90.
- Configuring OIDC does not disable token publishing; select Require two-factor authentication and disallow tokens.
- Since npm CLI 11.15.0,
npm stage publishqueues a release until a maintainer approves it with two-factor authentication. - Authentic update metadata can still be stale or rolled back: clients must check expiry, version order and target digests.
| Setting | Value | Note | Source |
|---|---|---|---|
| npm CLI for trusted publishing | 11.5.1 or later with Node 22.14.0 or later | Node 24.5.0 and later bundle a compatible npm 11; Node 22 still bundles npm 10. | npm trusted publishing |
| npm package publishing access | Require two-factor authentication and disallow tokens | — | npm trusted publishing |
| npm granular write tokens | 7-day default lifetime, 90-day maximum | — | GitHub changelog: npm authentication and token changes (2025-09-29) |
| npm login session | 2 hours, with two-factor authentication enforced for publishing | — | GitHub changelog: npm classic tokens revoked (2025-12-09) |
| PyPI trusted publishing token | Project-scoped, valid for 15 minutes | — | PyPI trusted publishers |
| GitHub Actions publish job permissions | id-token: write (and contents: read for npm) in the publish job only | — | pypa/gh-action-pypi-publish |
- Run
npm trust list acorn-cliand confirm a single GitHub Actions publisher with the expected repository, workflow file and environment. - In the npm package settings, confirm Publishing access is set to require two-factor authentication and disallow tokens, then revoke unused tokens shown by
npm token list. - Publish a prerelease from the workflow and confirm the npm package page shows provenance linking the source commit and workflow run.
- In a consumer project, run
npm audit signaturesand expect verified registry signatures and attestations for the installed tree. - Run
pypi-attestations verify pypi --repository https://github.com/acorn/cli pypi:<file>and expect OK; a different repository must fail. - Replay the update tests: a lower snapshot version, an expired timestamp and altered target bytes must each be rejected.
- Applies to
- npm CLI 11.5.1 or later for trusted publishing; 11.15.0 or later for npm stage and npm trust
- pypa/gh-action-pypi-publish v1.14.2; attestations are on by default with trusted publishing
Package publication and update channels. Repository, publisher and client-update trust are distinct boundaries.
- Part of
- Workflows: Fix a finding, Respond to a vulnerability
Learning goals and prerequisites
What you will learn
- Explain why trusted publishing does not remove parallel tokens automatically.
- Bind registry authorization to a specific producer and artifact.
- Evaluate freshness, rollback and target integrity separately.
Before you start
Package maintainers who publish to npm or PyPI and engineers who run software update channels
- Understand artifact identity, signing trust and short-lived publication credentials.
Refresh first: Verify artifact signatures against an explicit trust policy
Go to the guided practiceTrusted publishing: from an OIDC token to a verified install
- Register the publisherA maintainer names the repository, workflow file and environment on npmjs.com or with
npm trust github, then sets publishing access to require 2FA and disallow tokens. - Request an identityThe publish job, the only job with
id-token: write, asks GitHub's OIDC issuer for a signed token whose claims name the repository, workflow, ref and environment. - Exchange it
npm publishsends that token to the registry, which accepts it only if the claims match the trusted publisher and returns a short-lived credential for this package. - Publish with provenanceThe CLI uploads the tarball and, for a public package from a public GitHub or GitLab repository, a provenance attestation it generates automatically. A staged version waits until a maintainer approves it with 2FA.
- VerifyConsumers run
npm audit signaturesto check the registry signature and the provenance of what they installed. It proves where the package came from, not that its code is safe.
Trusted publishing exchanges a CI workload identity for short-lived registry authority. It reduces reliance on a stored long-lived publish token, but the identity relationship still needs exact scope: package, repository, workflow and, where supported, protected environment. The registry must recognize the intended producer, and that producer must prevent unreviewed code from gaining its publishing authority. Possessing an OIDC token is not itself permission to publish any package. It is the control that stops account takeovers such as ua-parser-js's in 2021 (/domains/supply-chain/dependencies/case-study-event-stream/), provided the interactive publishing path also requires 2FA.
npm documents trusted publishing for GitHub Actions on GitHub-hosted runners, GitLab CI/CD on GitLab.com shared runners and CircleCI cloud, with npm CLI 11.5.1 or later and Node 22.14.0 or higher; Node 24.5.0 and later bundle a compatible npm, while Node 22 still ships npm 10 and needs an upgrade. Provenance is generated automatically for public packages published from public GitHub or GitLab repositories, but not from CircleCI or private repositories. Token publishing keeps working beside trusted publishing until you select Require two-factor authentication and disallow tokens, so configuring OIDC alone does not revoke an old token; interactive publishing with 2FA and the trusted publisher keep working after that change. PyPI documents trusted publishers for GitHub Actions, GitLab CI/CD on GitLab.com, Google Cloud and ActiveState, and mints a project-scoped token valid for 15 minutes. Supported providers and fields are registry-specific; do not copy a PyPI rule into npm and assume equivalent enforcement.
Register the publisher on npmjs.com or with npm trust github acorn-cli --file npm-publish.yml --repo acorn/cli --env release --allow-publish (npm 11.15.0 or later). The fields are case-sensitive, the workflow is named by its filename including .yml, and repository.url in package.json must match the repository exactly; npm does not check the configuration when you save it, so a mismatch first shows up as an "Unable to authenticate" (ENEEDAUTH) error at publish time. In the workflow below, tests and dependency installation run in a job without id-token: write. The publish job installs nothing, skips lifecycle scripts, and holds the only OIDC permission, so third-party code never runs while a publishing credential can be requested. At TanStack in May 2026, binaries restored from a poisoned cache read the OIDC token from a release job's memory (/domains/supply-chain/dependencies/case-study-shai-hulud/). npm also refuses trusted-publishing tokens from pull_request_target and issue_comment runs. package-manager-cache: false keeps a poisoned cache out of the release job, and no --provenance flag is needed.
npm CLI 11.15.0 also added staged publishing. npm stage publish uploads the tarball to a queue, and a maintainer approves it with npm stage approve or on npmjs.com after a two-factor challenge. A trusted publisher can be limited to staging with --allow-stage-publish. Configurations created since 2026-09-03 always allow staging and make direct publishing opt-in, a package can have up to 10 of them, and since 2026-10-02 one that has not published within 48 hours expires. Since 2026-09-18 granular tokens can be stage-only; npm targets January 2027 for ending direct publishing with tokens that bypass two-factor authentication.
name: Publish to npm
on:
push:
tags: ['v*']
permissions: {}
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read # check out the tagged commit
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
package-manager-cache: false
- run: npm ci
- run: npm test
publish:
needs: test
runs-on: ubuntu-latest
environment: release
permissions:
contents: read # check out the tagged commit
id-token: write # exchange the OIDC token for a short-lived npm credential
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false
- run: npm publish --ignore-scriptsPyPA recommends building in one job and publishing in another so that code injected into the build cannot use the publishing identity. The publish job below has only id-token: write, runs in an environment named pypi that can require reviewers, and uploads with pypa/gh-action-pypi-publish. With trusted publishing the action also signs a PEP 740 attestation for every distribution file, tied to the same workflow identity; set attestations: false only for a documented reason. Trusted publishing cannot be used from inside a reusable workflow, so call the action from the top-level workflow.
name: Publish to PyPI
on:
push:
tags: ['v*']
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read # check out the tagged commit
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.14'
- run: python -m pip install build==1.6.1 && python -m build
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/
publish:
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/acorn-cli
permissions:
id-token: write # mint the 15-minute PyPI token and sign attestations
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2Moving to OIDC removes a secret from CI but leaves every older route open until you close it. Work through these checks for each package and organization.
- npm: set Publishing access to Require two-factor authentication and disallow tokens once the trusted publisher works.
- npm: list tokens with
npm token listand revoke write tokens that no workflow needs. Classic tokens were revoked on 2025-12-09; new granular write tokens expire after 7 days by default and 90 days at most. - npm: prefer stage-only automation tokens and require a two-factor approval for each staged release.
- Both registries: limit who can change trusted publisher settings, package owners and organization membership, and require phishing-resistant two-factor authentication for them.
- GitHub: protect the release environment with required reviewers and deployment branch or tag rules, and protect the workflow file with CODEOWNERS review.
- PyPI: delete API tokens that predate trusted publishing, and use one environment per index so TestPyPI and PyPI cannot share an identity by accident.
A valid workload identity tells the registry which workflow is requesting publication. It does not ensure that the workflow packages the reviewed source or that a release artifact was not substituted. A clean protected build should create the package, compute its digest, produce release evidence and publish that same object. Compare the registry's resulting bytes or authoritative content identity against the approved package where the ecosystem supports it. Protect the branch and tags the publishing workflow builds from as well: /domains/supply-chain/ci-cd/source-repository-protection/.
Record package name/version, archive digest, source commit, workflow revision, run identity, registry destination and publication event. A tag named latest or a distribution channel is a mutable pointer, so consumers who require a particular release need a protected way to bind version and content. Restrict who can change publisher configuration and registry owners; otherwise an attacker may authorize a new producer without touching the original workflow. Keep recovery credentials separately governed, because removing all alternatives without a tested recovery path can strand legitimate releases.
Consumers can check registry evidence without trusting the publisher web page. npm audit signatures verifies the registry signatures of every installed package and the provenance attestations of those that have them; it needs an installed tree, so install with --ignore-scripts first. For PyPI, pypi-attestations verify pypi downloads a file and its attestation and checks that the expected repository published it. Both checks prove where a package came from, not that its code is safe. For GitHub release assets, gh release verify and gh release verify-asset check the attestations of immutable releases.
# npm: verify registry signatures and provenance attestations for the installed tree
npm ci --ignore-scripts
npm audit signatures
# PyPI: verify that a release file was published by the expected repository
python3 -m pip install pypi-attestations==0.0.30
pypi-attestations verify pypi --repository https://github.com/acorn/cli \
pypi:acorn_cli-1.4.0-py3-none-any.whlSoftware updates require more than an authentic old package. An attacker may replay an older signed release, freeze metadata or direct clients to an unauthorized target. The Update Framework separates metadata roles and uses signatures, versions and expiration to address update-system threats. Its verification procedure is broader than a single version comparison; applications should use a maintained implementation, such as python-tuf, go-tuf or tough, and specify their rollback and clock assumptions.
The simplified update policy below requires authenticated metadata, an unexpired timestamp, a non-decreasing snapshot version and a target digest matching authorized metadata. It is an exercise policy, not a replacement for TUF. An expired but correctly signed snapshot fails freshness. A higher package version with the wrong digest fails integrity. A legitimate emergency downgrade needs an explicit recovery/rollback design; disabling all freshness and rollback checks globally would authorize unrelated replay as well. Update metadata is therefore part of the product security boundary, with its own keys, rotation and incident response.
Signing proves where an update came from, not that it is safe to install everywhere at once. Ship updates, including rules and configuration that clients interpret, through rings with automatic halts and a tested rollback: /domains/supply-chain/releases/staged-rollout-rollback/.
Revoke both the compromised publishing route and any parallel credentials. Inspect releases made during the exposure window, invalidate or deprecate affected versions through the registry's supported process, and communicate accurate affected identities through authorized incident channels. Deleting a tag cannot recall copies already installed. Clients need a trusted recovery update path, and organizations need evidence linking each remedial release to clean production.
Test rotation with synthetic identities and a staging registry: the old route must fail while the intended new route succeeds. For update clients, test stale metadata, rollback, altered target bytes and the valid next release. Retain failure reasons so an outage can be distinguished from a real rejection. Publishing and update availability matter, but an undocumented fallback to a stale token or expired metadata undermines the boundary you are trying to restore. The fixture below requires only comparing supplied claims and times, not obtaining credentials or publishing a package.
Guided practice
Acorn has moved to trusted publishing and is reviewing its remaining token plus three update candidates.
Supplied scenario material
Publisher rule PUB3: package acorn-cli; repository acorn/cli; workflow release.yml; environment release. All four fields must match.
P1: all expected fields; registry accepts workload identity.
P2: repository acorn/cli, workflow debug.yml, environment release, package acorn-cli; workload identity authentic.
Legacy token T: still valid; can publish acorn-cli; stored in old CI system.
Update client: trusted metadata keys; highest snapshot version seen 12; current time 2026-10-07T12:00Z.
U1: authenticated, snapshot 13, expires 2026-10-08T12:00Z, target digest H13, downloaded H13.
U2: authenticated, snapshot 11, expires 2026-10-08T12:00Z, target H11, downloaded H11.
U3: authenticated, snapshot 13, expires 2026-10-07T11:59Z, target H13, downloaded H13.
Exercise update rule: reject unauthenticated, expired, lower snapshot, or target mismatch. Fictional owner: registry administrator Kit.1. Apply publisher authorization
Decide P1/P2 and explain what authentic workload identity does not imply.
Expected result and reasoning
P1 matches PUB3. P2 fails the workflow field even with authentic identity; a debug job is not an authorized release producer.
2. Find the incomplete migration
Determine whether PUB3 removes the old publication risk.
Expected result and reasoning
No. Token T remains a separate publishing route. Kit must revoke/remove it after verifying the intended replacement and recovery process; OIDC configuration does not imply token revocation.
3. Evaluate update metadata
Give the exact decision for U1/U2/U3 at the supplied time.
Expected result and reasoning
U1 passes the simplified rule. U2 fails rollback because 11 < 12. U3 fails expiration because 11:59Z is before 12:00Z. Valid signatures do not override either failure.
4. Add a target-substitution case
Change downloaded digest for U1 to H99 and decide it.
Expected result and reasoning
Reject target mismatch: metadata authorizes H13 while downloaded bytes identify H99. A valid snapshot signature does not authenticate arbitrary replacement bytes.
5. Write the recovery tests
Specify two staging tests and expected outcomes.
Expected result and reasoning
After rotation, a publish using T must be rejected and P1 must succeed for the staging package under its own scoped rule. The update client must retain its highest trusted state and reject U2/U3 while allowing valid U1. These are test criteria, not completed publication results.
This is an abstract policy exercise. It is not a deployable registry configuration or full TUF verifier, and no package is published.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- npm trusted publishing
- PyPI trusted publishers
- The Update Framework overview
- Sigstore: verifying signatures and attestations
- GitHub changelog: npm authentication and token changes (2025-09-29)
- GitHub changelog: npm classic tokens revoked (2025-12-09)
- GitHub changelog: staged publishing and install-time controls for npm (2026-05-22)
- GitHub changelog: stage-only npm tokens (2026-09-18)
- GitHub changelog: multiple trusted publishing configurations for npm (2026-09-03)
- GitHub changelog: unvalidated npm trusted publishing configurations now expire (2026-10-02)
- npm Docs: npm trust. npm v11.
- npm Docs: npm audit signatures. npm v11.
- pypa/gh-action-pypi-publish
- PyPI Docs: producing attestations
- actions/setup-node: publishing to npm with trusted publishers
- The Update Framework specification
- CISA, NSA and ODNI (Enduring Security Framework): Securing the Software Supply Chain, Recommended Practices Guide for Developers, section 2.5 Deliver Code. August 2022.
Reference information
- SDLC phase
- Release
- Role
- Developers
- CAPEC attack patterns