Skip to content

Trusted Publishing

martyy-code edited this page Aug 5, 2026 · 1 revision

Last synced from docs/engineering/plans/release-pipeline-github-ui-setup.md on 2026-08-05. The repo file is the source of truth. If the two diverge, follow the repo.

This page is the one-time setup procedure for npm Trusted Publishing on @deessejs/fp. It is meant to be read once, when the project first adopts Trusted Publishing, and then archived in the maintainer's memory. Day-to-day release operations are documented in the Release Process page, not here.

Outline

  1. What is Trusted Publishing
  2. Why we use it
  3. Prerequisites
  4. npm-side configuration
  5. GitHub-side configuration
  6. Validating the setup
  7. Revoking any existing NPM_TOKEN
  8. What to do when the publisher needs to change
  9. Related documents

1. What is Trusted Publishing

Trusted Publishing is npm's mechanism for authenticating a publish without a long-lived secret. It is a form of OpenID Connect (OIDC) — npm verifies that a publish request came from a specific GitHub Actions workflow in a specific repository, and only then accepts the publish.

The flow:

  1. GitHub Actions runs publish.yml. The workflow has the id-token: write permission, which lets it mint a short-lived OIDC token.
  2. The workflow calls pnpm changeset publish. npm sees that no NPM_TOKEN is present in the environment and that an OIDC token is, instead.
  3. npm validates the OIDC token against the Trusted Publisher configuration registered on the package (see § 4): it checks the repository, workflow filename, and environment.
  4. If validation passes, npm publishes. If not, npm refuses with HTTP 403.

The OIDC token is short-lived (about 15 minutes). It is generated fresh for every workflow run and discarded after the publish. There is no secret to leak because the token is never stored.

2. Why we use it

The previous approach — a long-lived NPM_TOKEN stored as a GitHub secret — has three concrete problems:

  • Secret leakage. A long-lived token in a GitHub secret can leak through workflow logs, fork PRs from external contributors, or compromised maintainer accounts. npm Trusted Publishing eliminates the secret.
  • Operational overhead. Tokens need to be rotated periodically. Anyone joining or leaving the maintainer team requires a token rotation. This is pure toil with no security benefit beyond what OIDC provides.
  • Reduced auditability. A token-based publish attributes the action to whoever last rotated the token. An OIDC publish attributes it to the specific workflow run, including the commit SHA, the runner, and the environment.

For a project that publishes to a public registry, this is the baseline. Any project with a public npm package should use Trusted Publishing.

3. Prerequisites

To configure Trusted Publishing, you need:

  • Maintainer access to npmjs.com for the @deessejs/fp package (the account that owns the package or is added as a maintainer).
  • Admin access to the deessejs/fp GitHub repository (the role required to create or modify GitHub environments).
  • A workflow file at .github/workflows/publish.yml in the repository. This file must exist before Trusted Publishing is configured, because npm validates the workflow filename as part of the configuration.
  • The repository URL in packages/fp/package.json must match the actual GitHub repository URL. Trusted Publishing validates this field case-sensitively.

If any of these are missing, complete the prerequisite first and come back.

4. npm-side configuration

Path: https://www.npmjs.com/package/@deessejs/fp/access → Trusted Publishers → Add Trusted Publisher.

Fill in the form:

Field Value
Provider GitHub Actions
Organization or user deessejs
Repository deessejs/fp (must match package.json#repository.url exactly)
Workflow filename publish.yml
Environment name release

Save. The new Trusted Publisher appears in the list. Repeat the same configuration for any future workflow (canary, hotfix, pre-release) that needs its own publish slot, with a different Workflow filename and Environment name.

After saving:

  • Publishing access: require two-factor authentication and disallow tokens. This is the recommended setting.
  • Two-factor authentication must already be enabled on the npm account.

5. GitHub-side configuration

5.1 Create the release environment

Path: https://github.com/deessejs/fp/settings/environments → New environment.

  • Name: release (must match the npm-side configuration).
  • Deployment branches and tags:
    • Selected branches: main.
    • Selected tags: leave empty. The pipeline does not deploy from tag pushes.
  • Required reviewers: add 1-2 named engineering maintainers. Do not add a team; keep it named individuals so accountability is explicit.
  • Wait timer: leave at 0 minutes. The publish should not be artificially delayed.
  • Allow administrators to bypass: leave disabled. Maintainers still go through review.

5.2 Enable Actions permissions

Path: https://github.com/deessejs/fp/settings/actions → General → Workflow permissions.

  • Allow GitHub Actions to create and approve pull requests: enable. This is required if you want any future automation (like the changesets/action@v2 Version Packages PR) to open PRs from the bot identity.
  • Read repository contents and packages permissions: leave as default.

5.3 Verify the workflow

The .github/workflows/publish.yml file must declare the right permissions and environment:

jobs:
  release:
    environment: release
    permissions:
      id-token: write   # for npm OIDC
      contents: write   # to push the version bump and the tag
      pull-requests: read  # to inspect the merge

If any of these are missing, GitHub will not mint the OIDC token, and the publish will fail with HTTP 403.

6. Validating the setup

Do not assume the configuration works. Verify it explicitly before trusting it.

6.1 First test publish

Open a test PR with a --empty Changeset (which produces a version bump with no changelog body). Merge it to main. The first time the workflow runs:

  1. publish.yml triggers on pull_request: closed (merged).
  2. The workflow detects the Changeset.
  3. The workflow calls pnpm changeset publish --tag latest.
  4. npm receives the publish request, validates the OIDC token, accepts it.
  5. The version is published.

To verify the publish succeeded, run:

npm view @deessejs/fp version

This should print the version that was just published. If the publish failed, npm will return an error and the workflow's ::error:: annotation will explain why.

6.2 Inspect the publish provenance

After a successful publish, npm provides provenance attestation. Verify it:

  • Go to the npmjs.com page for the new version.
  • Look for the "Provenance" badge or section.
  • It should link to the GitHub Actions workflow run that produced the publish, including the commit SHA and the environment name.

If the provenance is missing, the publish used a different mechanism (probably a fallback to a personal access token). Fix the workflow before the next publish.

7. Revoking any existing NPM_TOKEN

If the project previously used NPM_TOKEN:

  1. Confirm OIDC works first. Run the test publish (§ 6.1) and verify it succeeded without NPM_TOKEN in the GitHub environment. If OIDC works, NPM_TOKEN is no longer used.
  2. Rotate the npm token. Go to https://www.npmjs.com/settings/tokens and delete the token that was used as NPM_TOKEN. This revokes it from npm's side.
  3. Delete the GitHub secret. Go to https://github.com/deessejs/fp/settings/secrets/actions and delete the NPM_TOKEN secret. This removes it from the GitHub side.
  4. Update any documentation. Remove references to NPM_TOKEN from internal docs and READMEs.

If the OIDC setup fails after revoking NPM_TOKEN, the next publish will fail. Keep the rollback plan: if you need to revert to NPM_TOKEN, regenerate it in npm, re-add the GitHub secret, and use the token in the workflow as a fallback while you debug OIDC.

8. What to do when the publisher needs to change

Three situations:

8.1 The workflow filename changes

If publish.yml is renamed (for example, split into publish-release.yml and publish-hotfix.yml), update the Trusted Publisher entry on npm with the new filename. Until then, the next publish fails with HTTP 403.

8.2 The environment name changes

If the release environment is renamed, update both sides atomically: npm Trusted Publisher first, then GitHub environment. Until both are updated, the publish fails.

8.3 The repository moves

If the repository is transferred to a new owner (for example, from nesalia-inc to deessejs), update package.json#repository.url first, then update the Trusted Publisher entry on npm with the new organization name. The npm entry validates the repository URL case-sensitively.

9. Related documents

Clone this wiki locally