Because a checked out repository can otherwise steal your secrets. The pnpm documentation says that since v11.5.3, environment variables are not expanded in the project level .npmrc for registry URLs, URL scoped keys and credential values. A line using a token placeholder is ignored with a warning. Supply the token from a trusted location instead.
The attack this prevents
A project .npmrc is checked out with the repository. If it could expand environment variables in a registry URL, a malicious repository could point the registry at an attacker's host and include your CI token variable in the credential line. The install would send the secret to that host. The pnpm documentation cites advisory GHSA-3qhv-2rgh-x77r and states the reason in nearly these terms: expanding variables there would let a malicious repository exfiltrate secrets such as CI tokens to an attacker controlled registry during installation.
It is a good example of a migration changing a security default that npm users take for granted.
What stops working
Per the documentation, a setting that contains a variable placeholder in these positions is ignored and pnpm prints a warning: registry and proxy URLs, URL scoped keys that start with two slashes, and credential values such as authToken, auth, password, username, tokenHelper, cert and key. A classic line that used an NPM_TOKEN placeholder in the project file falls in that group.
Safe replacements
| Option | Notes |
|---|---|
| Write the token to the user level auth file in a CI step | Use the pnpm config set command with the registry scoped key. It writes to the global location, not the repository |
| Set the credential through an environment variable | The documentation describes URL scoped environment variables, available since v11.6, where the host is encoded in the variable name |
| Keep the placeholder in the user level .npmrc | Move the line out of the repository |
| GitHub Actions setup node with a registry URL | Writes a user level file that pnpm honors, so NODE_AUTH_TOKEN keeps working |
The environment variable route has a security advantage the docs call out: because the registry the credential applies to is encoded in the trusted variable name, a malicious repository cannot redirect it to another host. Shell assignment syntax rejects such names, so the docs show using the env utility to pass it to a single command.
The tempting shortcut
The documentation also describes declaring the project .npmrc trusted through a single environment variable at organization or workspace level. Use that only if you control every repository that runs in that environment, and never in a job that builds untrusted pull requests.
Steps
- Search for token placeholders in every .npmrc in the repository.
- Choose one replacement from the table and apply it in CI.
- Remove the placeholder lines and any committed token.
- Rotate any token that was ever committed. See leaked registry token: what to check.
- Test an install from a fresh runner.
Related: npmrc scoped registry and token scoping.
Safeguard's dependency scanning and policies sit beside this control. A free scan is at gold.safeguard.sh/scan.
Quick check
Is this only a pnpm issue?
The documentation describes this behavior for pnpm. Check your own package manager's documentation for how it treats variables in project config.
Should the token live in the repository .npmrc at all?
No. Keep credentials in user level files or CI secrets, never committed.
Sources
Last reviewed: 19 October 2026.