Files
pnpm/pnpm11/network/auth-header/test
kyungseopk1mandZoltan Kochan cbecd4afc0 fix(config): allow tokenHelper to be set in the global auth.ini (#12833)
### What

Allow a `tokenHelper` configured in the global pnpm `auth.ini` to be used, instead of rejecting it as project-level configuration.

Closes #12759.

### Why

The guard that blocks `tokenHelper` from a project `.npmrc` (so a checked-in `.npmrc` can't run an arbitrary command) validated the helper against `userConfig`, which only contains `~/.npmrc`. But `pnpm config set` writes a `tokenHelper` to the global `auth.ini`, which flows into `trustedConfig`, not `userConfig`. As a result a legitimately configured `tokenHelper` was rejected on every command with `ERR_PNPM_TOKEN_HELPER_IN_PROJECT_CONFIG`, and `pnpm config delete` failed as well. The guard's own comment already says the helper may come from "`~/.npmrc` or global auth.ini".

### How

Validate against `trustedConfig` instead of `userConfig`. `trustedConfig` merges the trusted sources (including `~/.npmrc` and the global `auth.ini`) but excludes workspace and project `.npmrc` files, so the global helper is accepted while a project-level one is still rejected.

Tests in `config/reader` cover both paths: a `tokenHelper` in the global `auth.ini` is honored, and a `tokenHelper` in a project `.npmrc` still throws `ERR_PNPM_TOKEN_HELPER_IN_PROJECT_CONFIG`.


### Pacquet parity

Pacquet had no `tokenHelper` support at all, so this PR also ports the whole feature to the Rust stack (matching the cardinal rule that user-visible changes land in both stacks):

- **Config layer** (`pnpm/crates/config`): parse `//host/:tokenHelper` (scoped and default forms too) from trusted `.npmrc` / `auth.ini` sources only; reject a project/workspace helper via the same full-vs-trusted comparison used on the TypeScript side; and reject reserved characters — surfacing the same `ERR_PNPM_TOKEN_HELPER_IN_PROJECT_CONFIG` / `ERR_PNPM_TOKEN_HELPER_UNSUPPORTED_CHARACTER` codes.
- **Network layer** (`pnpm/crates/network`): `AuthHeaders` carries the un-executed command per registry and runs it lazily on lookup, memoized (runs at most once, and never for a command that makes no matching request). Baked headers keep their eager, lock-free hot path; a header is emitted only on success, so a failing helper sends no credential.
- **Known divergence**: because `AuthHeaders::for_url` is infallible and there is no single network seam, a helper failure surfaces as a loud error plus an unauthenticated request rather than pnpm's hard whole-command abort.
- **Known divergence — execution timing**: pnpm runs every configured registry's `tokenHelper` when it builds the auth-header lookup (so a helper for a registry never contacted still runs); pacquet runs each helper lazily on the first request to that registry (per-registry, memoized, and never on a command that makes no matching request). Both bound execution at 60s.

Covered by new Rust unit + integration tests (parse, trust guard, reserved-char, env-drop, lazy execution / memoization, and an end-to-end `/bin/echo` run).

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:05:48 +02:00
..