Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
With npm 12, a root preinstall script runs before npm fetches project dependencies. However, npm loads the user and project .npmrc files before running that script and does not use credential changes made by the script for the active installation.
npm/cli#2660 was a long-running request to execute root preinstall before dependency installation. A major use case raised repeatedly in that discussion was automatic authentication for private registries, including registries that issue short-lived tokens. npm 12 now provides the requested lifecycle ordering, but that use case remains impossible within a single npm install: the earlier configuration load means credentials created or refreshed by preinstall are not used by the dependency requests that follow it.
This can be reproduced when the project depends on a package in a private registry and preinstall creates or refreshes the registry credentials:
- npm starts with a missing, expired, revoked, or deliberately invalid credential.
- The root
preinstall script successfully obtains a valid credential and writes it to the user .npmrc.
- The same
npm install still requests the private package using the configuration loaded before preinstall and fails with E401.
- Running
npm install again succeeds because the new npm process loads the credential written by the first invocation.
The first invocation therefore fails even though authentication completed successfully before the private-registry request. This particularly affects private registries that use short-lived tokens and authentication helpers intended to refresh them automatically.
Expected Behavior
After the root preinstall hook completes, npm should reload the configuration or credentials used for subsequent dependency requests. A valid registry credential written by preinstall should be used by the active npm install, allowing the first invocation to succeed.
If reloading configuration after preinstall is intentionally unsupported, the npm 12 lifecycle documentation should make that limitation explicit. Running root preinstall before dependency installation otherwise appears to support authentication and configuration preparation, while the active installation silently retains its earlier state.
Steps To Reproduce
The following generic reproduction works with any private npm registry and avoids committing a real token. Back up any credentials before adapting it.
-
Create a project containing a dependency from a private registry:
{
"name": "npm-preinstall-auth-repro",
"version": "1.0.0",
"private": true,
"scripts": {
"preinstall": "node refresh-auth.cjs"
},
"dependencies": {
"@private/example": "1.0.0"
}
}
-
Add the registry to the project .npmrc, replacing the example URL and scope:
@private:registry=https://private-registry.example.test/npm/
-
Save working credentials in an untracked file named valid-user.npmrc:
//private-registry.example.test/npm/:_authToken=VALID_TOKEN
-
Create refresh-auth.cjs. It replaces the user configuration during root preinstall:
const { copyFileSync } = require("node:fs");
const { join } = require("node:path");
const { homedir } = require("node:os");
const target =
process.env.npm_config_userconfig ||
process.env.NPM_CONFIG_USERCONFIG ||
join(homedir(), ".npmrc");
copyFileSync("valid-user.npmrc", target);
console.log(`Wrote valid registry credentials to ${target}`);
-
Point npm at a disposable user configuration containing an invalid token. In PowerShell:
$env:NPM_CONFIG_USERCONFIG = "$PWD/repro-user.npmrc"
Set-Content -LiteralPath $env:NPM_CONFIG_USERCONFIG -Value '//private-registry.example.test/npm/:_authToken=INVALID_TOKEN'
-
Run the installation with lifecycle and request output visible:
npm install --foreground-scripts --loglevel verbose
-
Observe that refresh-auth.cjs reports that it wrote the valid credential, but the subsequent private-registry request receives 401 Unauthorized and npm exits with E401.
-
Run the same command again without changing any files:
npm install --foreground-scripts --loglevel verbose
-
Observe that the second invocation succeeds because it loads repro-user.npmrc after the first invocation replaced its invalid credential.
The missing-credential variant behaves the same way: begin with a user configuration containing no registry credential, let preinstall add it, and observe that only the second npm invocation uses it.
Environment
The private registry hostname, username, and filesystem paths below are sanitized. No token value is included.
-
npm: 12.0.2
-
Node.js: v24.18.1
-
OS Name: Microsoft Windows 11 Enterprise 10.0.26200, 64-bit
-
System Model Name: Dell Pro Max 16 MC16250
-
npm config:
; "user" config from C:\Users\<user>\.npmrc
//private-registry.example.test/npm/:_password = (protected)
//private-registry.example.test/npm/:email = (protected)
//private-registry.example.test/npm/:username = (protected)
; "env" config from environment
cache = "Z:\\packages\\npm"
; node bin location = C:\nvm4w\nodejs\node.exe
; node version = v24.18.1
; npm local prefix = <project-path>
; npm version = 12.0.2
; cwd = <project-path>
; HOME = C:\Users\<user>
; Run `npm config ls -l` to show all defaults.
Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
With npm 12, a root
preinstallscript runs before npm fetches project dependencies. However, npm loads the user and project.npmrcfiles before running that script and does not use credential changes made by the script for the active installation.npm/cli#2660 was a long-running request to execute root
preinstallbefore dependency installation. A major use case raised repeatedly in that discussion was automatic authentication for private registries, including registries that issue short-lived tokens. npm 12 now provides the requested lifecycle ordering, but that use case remains impossible within a singlenpm install: the earlier configuration load means credentials created or refreshed bypreinstallare not used by the dependency requests that follow it.This can be reproduced when the project depends on a package in a private registry and
preinstallcreates or refreshes the registry credentials:preinstallscript successfully obtains a valid credential and writes it to the user.npmrc.npm installstill requests the private package using the configuration loaded beforepreinstalland fails withE401.npm installagain succeeds because the new npm process loads the credential written by the first invocation.The first invocation therefore fails even though authentication completed successfully before the private-registry request. This particularly affects private registries that use short-lived tokens and authentication helpers intended to refresh them automatically.
Expected Behavior
After the root
preinstallhook completes, npm should reload the configuration or credentials used for subsequent dependency requests. A valid registry credential written bypreinstallshould be used by the activenpm install, allowing the first invocation to succeed.If reloading configuration after
preinstallis intentionally unsupported, the npm 12 lifecycle documentation should make that limitation explicit. Running rootpreinstallbefore dependency installation otherwise appears to support authentication and configuration preparation, while the active installation silently retains its earlier state.Steps To Reproduce
The following generic reproduction works with any private npm registry and avoids committing a real token. Back up any credentials before adapting it.
Create a project containing a dependency from a private registry:
{ "name": "npm-preinstall-auth-repro", "version": "1.0.0", "private": true, "scripts": { "preinstall": "node refresh-auth.cjs" }, "dependencies": { "@private/example": "1.0.0" } }Add the registry to the project
.npmrc, replacing the example URL and scope:@private:registry=https://private-registry.example.test/npm/Save working credentials in an untracked file named
valid-user.npmrc://private-registry.example.test/npm/:_authToken=VALID_TOKENCreate
refresh-auth.cjs. It replaces the user configuration during rootpreinstall:Point npm at a disposable user configuration containing an invalid token. In PowerShell:
Run the installation with lifecycle and request output visible:
Observe that
refresh-auth.cjsreports that it wrote the valid credential, but the subsequent private-registry request receives401 Unauthorizedand npm exits withE401.Run the same command again without changing any files:
Observe that the second invocation succeeds because it loads
repro-user.npmrcafter the first invocation replaced its invalid credential.The missing-credential variant behaves the same way: begin with a user configuration containing no registry credential, let
preinstalladd it, and observe that only the second npm invocation uses it.Environment
The private registry hostname, username, and filesystem paths below are sanitized. No token value is included.
npm:
12.0.2Node.js:
v24.18.1OS Name:
Microsoft Windows 11 Enterprise 10.0.26200, 64-bitSystem Model Name:
Dell Pro Max 16 MC16250npm config: