GHSA-cjhr-43r9-cfmw pnpm binds unscoped user-level npm auth credentials to a repository-selected registry
Summarypnpm can send user-level unscoped npm authentication credentials to a registry chosen by a repository-local `.npmrc` file.In the reproduced case, the user's npm config contains a default registry and an unscoped `_authToken`. The repository does not provide a token-bearing auth line. It only sets `registry=` to a different registry URL. During normal pnpm metadata/install workflows, pnpm binds the user-origin unscoped credential to the repository-selected registry and sends it as an `Authorization` header.This was reproduced with fake credentials and loopback registries only. No third-party registry or real token was used.Affected Behavior ObservedObserved affected:pnpm `10.33.2`: `pnpm install --ignore-scripts` sends the user-level unscoped `_authToken` to the repository-selected registry.pnpm `11.1.3`: `pnpm install --ignore-scripts` sends the user-level unscoped `_authToken` to the repository-selected registry.pnpm `11.2.1` (`next-11` dist tag at testing time): `pnpm install --ignore-scripts` sends the user-level unscoped `_authToken` to the repository-selected registry.pnpm `11.1.3`: `pnpm view` also sends user-level unscoped `_authToken`, `_auth`, and `username` / `_password` credentials to the repository-selected registry in the local loopback replay.Control:npm `10.9.7` rejects the same unscoped user `_authToken` configuration with `ERR_INVALID_AUTH` and does not send an `Authorization` header to the  repository-selected registry.URL-scoped registry token controls held in the local loopback replay: tokens scoped to the trusted registry URL were not sent to the attacker registry.Threat ModelVictim:developer or CI job with user-level npm registry credentials configured;runs `pnpm install`, `pnpm view`, or an equivalent pnpm metadata/restore command in a repository.Attacker:controls repository-local package manager configuration, such as `.npmrc`;can set `registry=` to a registry endpoint they control;does not need to provide a token-bearing auth line for the strong case.Boundary:Credentials from a higher-trust user configuration should not be rebound to a lower-trust repository-selected registry unless the credential is explicitly scoped to that registry.Minimal ReproductionThe reproducer below starts two loopback HTTP registries:a trusted registry URL used in the isolated user `.npmrc`;an attacker registry URL used in the repository-local `.npmrc`.The isolated user `.npmrc` contains:The repository-local `.npmrc` contains:The repository `package.json` depends on a toy package served by the loopback registry. The script then runs:Expected Safe Behaviorpnpm should not send the user-level unscoped `_authToken` to the repository-selected registry. A safe behavior would be to reject or ignore the unscoped credential in this lower-trust registry-rebinding situation and require the credential to be URL-scoped to the selected registry.Observed Behaviorpnpm `10.33.2`, pnpm `11.1.3`, and pnpm `11.2.1` send:to the attacker loopback registry during install. npm `10.9.7` rejects the same config and sends no `Authorization` header.Security ImpactThis can disclose npm registry credentials from user-level configuration to a registry endpoint selected by an untrusted repository. The leak occurs before package lifecycle scripts run and does not depend on package code execution.Non-ClaimsThis report does not claim:remote code execution;registry account compromise by itself;leakage of URL-scoped tokens for a different registry;npm CLI impact;impact from a repository explicitly committing its own token-bearing auth
line.Source-Level NotesIn pnpm's config/auth-header flow, unscoped/default credentials are parsed from the merged auth config and stored as default credentials. The auth-header logic then maps those default credentials to the effective default registry. Because repository-local `.npmrc` can change the effective default registry, higher-trust default credentials can be applied to a lower-trust registry choice.Suggested Fix DirectionThe conservative fix direction is to reject or contain unscoped/default auth credentials when a lower-trust workspace/repository config changes the default registry. A compatibility-preserving fix could track the source layer of both the default registry and the default credentials, then only bind default credentials to a registry selected by the same or higher-trust source. A stricter npm-compatible fix would reject unscoped auth and require URL-scoped
credentials.This needs maintainer semantic review and compatibility control because some legacy workflows may intentionally rely on default/unscoped auth.Runnable ReproducerSave the following as `repro.py` and run it with Python 3 in an environment with pnpm and npm available. To force a specific pnpm version through Corepack, set `PR166_PNPM_SPEC`, for example `PR166_PNPM_SPEC=11.2.1`.Abbreviated Expected OutputReporter: JUNYI LIU
CVSS v4.0 score: 6.9