Skip to content

[BUG] Root preinstall cannot update registry credentials for the active npm install #9853

Description

@edumserrano

Is there an existing issue for this?

  • I have searched the existing issues

This issue exists in the latest npm version

  • I am using the latest npm

This is not just a request to bump a dependency for a CVE

  • This is not solely 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:

  1. npm starts with a missing, expired, revoked, or deliberately invalid credential.
  2. The root preinstall script successfully obtains a valid credential and writes it to the user .npmrc.
  3. The same npm install still requests the private package using the configuration loaded before preinstall and fails with E401.
  4. 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.

  1. 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"
      }
    }
  2. Add the registry to the project .npmrc, replacing the example URL and scope:

    @private:registry=https://private-registry.example.test/npm/
  3. Save working credentials in an untracked file named valid-user.npmrc:

    //private-registry.example.test/npm/:_authToken=VALID_TOKEN
  4. 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}`);
  5. 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'
  6. Run the installation with lifecycle and request output visible:

    npm install --foreground-scripts --loglevel verbose
  7. 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.

  8. Run the same command again without changing any files:

    npm install --foreground-scripts --loglevel verbose
  9. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Bugthing that needs fixingNeeds Triageneeds review for next steps

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions