Automated NPM secret rotation in GitHub Actions

16 Nov 2025 in Tech

NPM recently announced that all long-lived tokens are being revoked, and that going forwards any new tokens may be valid for a maximum of 90 days.

This presents a challenge for me, as I've built a system where when I tag a release on GitHub in my JavaScript projects, it builds and publishes to NPM using an access token. Rotating those tokens regularly would be a lot of toil.

Upgrading to OIDC

The correct way to solve this is to adopt trusted publishing (OIDC). However, I don't have time to update all of my projects right now. I'd like to keep using an access token until I have time to update each project.

Rotating GitHub Actions user secrets at scale

Fortunately, back in 2020 I was feeling the pain of rotating GitHub secrets for a user account as I couldn't use organization level secrets and built github-update-secret.

github-update-secret iterates over all of your repositories and checks if the provided secret name is set. If so, it updates the value to the new value provided.

How it works

  • Provide GitHub authentication by populating the GITHUB_TOKEN environment variable, or passing the --pat flag to the CLI
  • Fetch a list of all repos to which the authenticated user has admin access for the provided user/org
  • Fetch the list of secrets on each repository
  • For each repository that has a secret named the same as the provided SECRET_NAME, update the value of that secret
  • Check if the provided target is an organisation.
  • If so:
    • Check if there is an org secret named SECRET_NAME
    • If there is, update the value

Example

I just rotated all of my NPM_TOKEN secrets with a secret that's valid for another 90 days using the following command:

bash
GITHUB_TOKEN=ghp_... DEBUG=github-update-secret npx github-update-secret <user/org> <SECRET_NAME> <new_value>

Running the tool with DEBUG=github-update-secret allows you to see all of the repositories that are being updated:

bash
❯ DEBUG=github-update-secret npx github-update-secret mheap NPM_TOKEN npm_...
github-update-secret Fetching repo list +0ms
github-update-secret Processed repo list +2s
github-update-secret Building list of repos using [npm_token] +0ms
github-update-secret Found [27] repos with the secret [npm_token] +2s
github-update-secret Updating action-guard +1ms
github-update-secret Updated action-guard +347ms
github-update-secret Updating action-router +1ms
github-update-secret Updated action-router +381ms
github-update-secret Updating action-run +1ms
github-update-secret Updated action-run +338ms
github-update-secret Updating actions-output-wrapper +0ms
...snip...
github-update-secret Check if provided user is an org +0ms
github-update-secret User is not an org. Skipping org secret update +142ms

NPM’s move to 90-day tokens is good for security but rough on existing workflows. OIDC is the long-term fix, but until I can update each project, github-update-secret gives me a quick way to rotate tokens across all my repos and keep releases moving. It’s not perfect, but it provides enough breathing room until I can do the real migration.