npm Trusted Publishing Adds OIDC Dist-Tag Control

Software package passing through an OIDC identity gate into controlled npm release channels

npm maintainers can now manage distribution tags from trusted publishing workflows without keeping a long-lived registry token. GitHub announced the opt-in capability on September 30, 2026. It closes a practical gap in OpenID Connect (OIDC) based release automation: trusted publishing already supported publishing and staging, but moving tags such as latest, next, or beta still required a granular access token.

The change is small in the interface and significant in the threat model. A distribution tag controls which package version users receive when they install an alias, and latest is the default when no version is specified. Moving that authority from a reusable secret to a short-lived workflow identity reduces the value of stolen CI secrets and makes release access easier to scope and audit.

What changed on September 30

GitHub’s September 30 changelog says each npm trusted publishing configuration can now receive an Allow npm dist-tag permission. The permission is disabled by default for new and existing configurations. No workflow silently gained the power to retag packages.

The permission is separate from direct publishing. A connection limited to staged publishing can also be allowed to manage distribution tags. npm authorizes a dist-tag command when the incoming OIDC token matches a trusted publishing configuration that has the permission enabled. Existing token-based tag workflows continue to operate, which gives maintainers a controlled migration path.

The operational commands are unchanged:

npm dist-tag ls your-package
npm dist-tag add [email protected] latest
npm dist-tag add [email protected] next
npm dist-tag rm your-package beta

What changes is how the command authenticates. Instead of reading an NPM_TOKEN stored in CI, the job requests an OIDC identity token. npm validates the token against the package’s trusted publisher configuration and exchanges it for short-lived authority appropriate to the permitted operation.

Why distribution tags are security-sensitive

Distribution tags are human-friendly aliases for versions. npm’s documentation states that a plain npm install package-name resolves through latest. Projects commonly use next, beta, dev, or canary for prerelease channels. A tag can therefore redirect many future installs without modifying an already published tarball.

That makes tag administration a release control, not housekeeping. If an attacker steals a token with tag-write permission, they may be able to point a trusted channel at an unintended version. A rollback account with excessive rights can have the same blast radius as a publisher even if it cannot upload a new package.

OIDC changes the credential lifecycle. GitHub documents that every eligible job receives a unique signed token containing claims about its repository, workflow, ref, environment, actor, and run. The relying service validates those claims against a configured trust relationship and issues a short-lived credential for that job. There is no static npm publishing secret to copy from a repository secret store and reuse later.

A secure design for tag promotion

Do not add dist-tag authority to every publishing connection simply because the option exists. Treat tag promotion as its own production change and build a narrow identity around it.

Use a dedicated workflow and environment

A dedicated file such as .github/workflows/promote.yml makes the authority visible during review. Attach it to a protected environment such as npm-release, and use environment approval rules when the repository’s risk warrants a human gate. Configure the npm trusted publisher to match the intended repository, workflow, and environment values.

A release workflow can publish a prerelease under next; a separate promotion workflow can move the tested version to latest. This separation limits the number of jobs that can change the default install path and gives the audit log a clearer meaning.

Keep GitHub Actions permissions explicit

Set workflow permissions rather than inheriting broad defaults. The npm trusted publishing example requires id-token: write and contents: read. Add other GitHub permissions only when the workflow demonstrably needs them. An OIDC token is short-lived, but a workflow that can be changed or triggered too broadly can still request one.

name: Promote npm dist-tag

on:
  workflow_dispatch:
    inputs:
      version:
        description: Version already published and approved
        required: true
      channel:
        description: Distribution tag to update
        required: true
        type: choice
        options:
          - latest
          - next

permissions:
  contents: read
  id-token: write

jobs:
  promote:
    environment: npm-release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v6
        with:
          node-version: "24"
          registry-url: https://registry.npmjs.org
          package-manager-cache: false
      - name: Promote approved version
        run: npm dist-tag add "your-package@${{ inputs.version }}" "${{ inputs.channel }}"

Replace your-package and match the Node and npm versions to your supported release tooling. The example has no NODE_AUTH_TOKEN or npm secret. Enabling the package’s Allow npm dist-tag setting remains a separate registry-side step.

Constrain input and approval paths

The example limits channels to latest and next through a choice input. For higher assurance, derive the version from a signed release, validate it against your package name and semantic-version policy, and require an environment reviewer. Avoid running tag promotion on pull requests or arbitrary branch pushes.

If you use reusable workflows, verify the calling workflow identity. npm’s documentation warns that validation may use the caller’s workflow name when workflow_call is involved, and both caller and called workflow need id-token: write. Test the exact identity path before deleting the old token.

Migration checklist: remove the long-lived token safely

  1. Inventory the current path. Find NPM_TOKEN, NODE_AUTH_TOKEN, registry entries in .npmrc, organization secrets, environment secrets, and external release bots.
  2. List every operation. Separate package installation, direct publishing, staged publishing, provenance, and dist-tag changes. Trusted publishing addresses publishing operations; private dependency installation may still need a read-only token.
  3. Create or review the trusted publisher. Match the package, repository, workflow, and environment. npm currently allows multiple trusted publisher connections, so use different identities for different release duties where useful.
  4. Enable the smallest registry permission. Turn on Allow npm dist-tag only for connections that must change tags. Do not grant direct publishing solely because a promotion job needs tag access.
  5. Add explicit workflow permissions. Use contents: read and id-token: write, then add only documented necessities.
  6. Test with a non-default channel. Where project policy permits, validate the workflow against a prerelease package and a non-production tag before touching latest.
  7. Remove fallback secrets. After successful OIDC runs, delete the write token from repository, organization, environment, deployment platform, and maintainer documentation. A dormant fallback token remains a credential to steal.
  8. Review evidence. Confirm the selected package version, tag result, workflow run, environment approval, and npm provenance or audit information.

npm recommends trusted publishing for CI/CD release jobs because it avoids long-lived publishing tokens. For workflows that only install private dependencies, npm advises a read-only granular token. Keep those uses distinct so a dependency-read credential cannot become a release credential.

Hands-on lab: audit a release workflow offline

This lab creates a sample promotion workflow and checks it locally for the core controls discussed above. It never contacts npm or GitHub, requests no OIDC token, and cannot publish or retag a package.

Prerequisites

  • Python 3.9 or newer
  • A disposable directory
  • No registry credentials
mkdir -p oidc-workflow-lab/.github/workflows
cd oidc-workflow-lab

Save the earlier YAML example as .github/workflows/promote.yml, then save this checker as audit_workflow.py:

from pathlib import Path
import re
import sys

path = Path(".github/workflows/promote.yml")
text = path.read_text(encoding="utf-8")

checks = {
    "OIDC permission is enabled":
        re.search(r"(?m)^s*id-token:s*writes*$", text),
    "repository permission is read-only":
        re.search(r"(?m)^s*contents:s*reads*$", text),
    "protected release environment is named":
        re.search(r"(?m)^s*environment:s*npm-releases*$", text),
    "GitHub-hosted runner is used":
        re.search(r"(?m)^s*runs-on:s*ubuntu-latests*$", text),
    "dist-tag command is present":
        "npm dist-tag add" in text,
    "release channel is constrained":
        all(tag in text for tag in ("latest", "next")),
    "no reusable npm secret is referenced":
        not re.search(r"NPM_TOKEN|NODE_AUTH_TOKEN|secrets.", text),
}

failed = []
for label, passed in checks.items():
    state = "PASS" if passed else "FAIL"
    print(f"{state}: {label}")
    if not passed:
        failed.append(label)

if failed:
    print(f"nReview required: {len(failed)} control(s) failed.")
    sys.exit(1)

print("nAll offline workflow checks passed.")
python3 audit_workflow.py

Expected result:

PASS: OIDC permission is enabled
PASS: repository permission is read-only
PASS: protected release environment is named
PASS: GitHub-hosted runner is used
PASS: dist-tag command is present
PASS: release channel is constrained
PASS: no reusable npm secret is referenced

All offline workflow checks passed.

For troubleshooting, confirm the workflow path and indentation first. If the secret check fails, remove the write token from this workflow rather than hiding it under another variable name. This small checker is a teaching aid, not a full YAML parser or GitHub policy engine. Review the effective repository settings, environment rules, npm trusted publisher configuration, and workflow permissions before production use.

Cleanup

cd ..
rm -rf oidc-workflow-lab

Provenance helps, but it is not a malware verdict

npm says trusted publishing automatically generates provenance attestations for supported CI providers. Provenance links a package to its source repository and build instructions, which is useful for investigation and consumer verification. npm also states that provenance does not prove a package contains no malicious code. Reviewers still need source control, protected release workflows, dependency policy, and artifact inspection.

This change fits a broader workload-identity model. Our AWS security best-practices guide covers least privilege and identity controls for cloud environments, while the security automation guide provides patterns for making operational scripts auditable and predictable.

Conclusion

Opt-in dist-tag permission removes a stubborn reason to keep a long-lived npm write token in CI. The best migration is deliberately narrow: one trusted workflow, one protected environment, explicit OIDC permissions, constrained channels, and no fallback publishing secret. Because distribution tags steer package installation, the workflow that moves latest deserves the same review discipline as the workflow that publishes the package.

Primary sources

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *