Skip to content

feat(config): support an _auth setting for registry authentication#12559

Merged
zkochan merged 3 commits into
pnpm:mainfrom
aqeelat:feat/json-env-auth
Jun 25, 2026
Merged

feat(config): support an _auth setting for registry authentication#12559
zkochan merged 3 commits into
pnpm:mainfrom
aqeelat:feat/json-env-auth

Conversation

@aqeelat

@aqeelat aqeelat commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an _auth setting that configures registry authentication as a single structured (URL-keyed) value, instead of many //host/:_authToken=… keys. It can be set in two trusted places:

  • the global pnpm config (config.yaml), as a native YAML map, and
  • the pnpm_config__auth environment variable, as a JSON string — the CI-friendly form, since GitHub Actions / bash / zsh silently drop env vars whose names contain /, :, or . (which broke the old pnpm_config_//host/:_authToken=… form on CI).

_auth is never read from a project .npmrc / pnpm-workspace.yaml, so repository-controlled config can never supply registry credentials. (npm's deprecated .npmrc _auth scalar keeps its legacy meaning there — no collision, because pnpm only reads the structured _auth from its own yaml and the env projection.)

Example env form:

export pnpm_config__auth='{"https://registry.npmjs.org":{"@":{"authToken":"npm-token"},"@org":{"authToken":"org-token"}}}'

Equivalent in the global config.yaml:

_auth:
  https://registry.npmjs.org:
    '@':
      authToken: npm-token
    '@org':
      authToken: org-token
  • The value is keyed by registry URL, so each secret is explicitly bound to the host that may receive it. URL keys must use http or https and must not include credentials, query strings, or fragments.
  • Within each registry URL, @ means registry-wide/default credentials and package scopes like @org bind credentials to that scope on the same host.
  • The only supported credential field is authToken (maps to //host/:_authToken / bearer auth). The deprecated basicAuth / username + password forms are intentionally not accepted in this new setting and are dropped with a warning; tokenHelper is likewise unsupported (executable paths must not come from this setting).
  • Each accepted entry also infers a trusted registry route: @ routes the default registry and @org routes that scope. Because the credential and its destination host arrive in one trusted value, repo-controlled config cannot redirect the token to a different host.
  • Precedence: CLI flags (--registry, --@scope:registry) > pnpm_config__auth > global config.yaml _auth > pnpm-workspace.yaml. The env var wins over the global config.yaml on a conflicting key.
  • Both pnpm_config__auth (lowercase, documented) and PNPM_CONFIG__AUTH (all-caps shell convention) are honored; lowercase wins unless empty.
  • Reusing the _auth name means the value is already covered by the existing protected-settings redaction, so it prints as (protected) in pnpm config list.
  • Lands on both stacks (TS CLI + pacquet) per the cardinal rule.

Closes #12314. Related to pnpm/pnpm.io#827 (docs PR will follow in the pnpm.io repo).

Squash Commit Body

Add an `_auth` setting that carries registry authentication as one
structured, URL-keyed value instead of many `//host/:_authToken=…` keys.

GitHub Actions silently drops env vars whose names contain `/`, `:`, or `.`
(and bash/zsh reject them outright), which broke the
`pnpm_config_//host/:_authToken=…` form on CI (pnpm/pnpm#12314). The
`pnpm_config__auth` env var — the env projection of the `_auth` setting —
sidesteps the runner-level filter while preserving host scoping. The same
value can be set in the global pnpm config.yaml as a native YAML map.

Design:
- `_auth` is honored only from the env var and the global config.yaml —
  never a project pnpm-workspace.yaml / .npmrc — so repo-controlled config
  can never supply registry auth. The env var wins over the global config
  on a conflicting key.
- The value is host-keyed: `{ "https://registry.example": { "@": {...},
  "@org": {...} } }`. The host and credential arrive in one trusted value,
  so the same entry is a safe source of registry routing — repo config
  cannot redirect an env token to a different host. Routes inferred from
  `_auth` are applied after workspace yaml (env/global wins over repo
  config) and before PNPM_CONFIG_* so an explicit registry override still
  wins — matching pnpm's "CLI > _auth > yaml" precedence.
- Reuses npm's deprecated `_auth` key name on purpose: pnpm reads the
  structured form only from its own yaml (and the env projection), so it
  never collides with npm's legacy `.npmrc` `_auth` scalar, and it is
  already covered by the existing protected-settings redaction (prints as
  `(protected)`).
- Only `authToken` is accepted (maps byte-for-byte to the existing
  `//host/:_authToken` .npmrc key), so the parser is a thin adapter onto
  the existing auth pipeline. The deprecated `basicAuth` / `username` +
  `password` forms and `tokenHelper` are excluded — dropped with a warning.
- Malformed values, non-object shapes, non-URL keys, non-http(s) schemes,
  URLs carrying credentials/query/fragment, invalid scopes, and unsupported
  auth fields each push a distinct warning and are skipped without aborting
  the load. Warnings never leak raw URL credentials.

Parity: implemented on both sides (TS CLI + pacquet). Both support the
single credential field `authToken`.

Closes pnpm/pnpm#12314.

Checklist

  • The change is implemented in both the TypeScript CLI and the Rust
    pacquet/ port. (Both sides implemented.)
  • Added a changeset (.changeset/json-env-auth.md — minor bump for pnpm
    and @pnpm/config.reader).
  • Added or updated tests. (TS: env-rename + new global-yaml-sourcing,
    env-over-yaml precedence, and project-yaml-ignored tests in
    pnpm11/config/reader/test/index.ts. Pacquet: unit + end-to-end tests in
    npmrc_auth/tests.rs and tests.rs for the env rename, global config.yaml
    _auth, env-wins precedence, and the project-yaml trust boundary.)
  • Updated the documentation if needed. (pnpm.io docs live in a separate
    repo — a follow-up PR there will document the _auth setting (Recommendation to set environment variable with special characters in name is misleading pnpm.io#827).)

Written by an agent (Claude Code, claude-opus-4-8).

Summary by CodeRabbit

  • New Features

    • Added support for structured registry authentication from environment settings and global config, including host-based and scope-based routing.
    • Registry and package-manager bootstrap settings now stay in sync across command-line, environment, and config sources.
  • Bug Fixes

    • Strengthened precedence so trusted auth settings win over workspace/project settings when they conflict.
    • Invalid auth values now fail fast instead of being silently ignored.
    • Improved registry URL normalization, including consistent trailing-slash handling.

@aqeelat aqeelat requested a review from zkochan as a code owner June 21, 2026 16:53
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Adds trusted JSON auth support for registry credentials and scope routing, plus precedence updates, bootstrap propagation, and tests in pnpm11 and pacquet.

Changes

JSON env auth feature

Layer / File(s) Summary
Changeset and conventions
.changeset/json-env-auth.md, AGENTS.md
Adds the changeset entry, documents _auth usage and precedence, and adds a trust-boundary convention note.
TypeScript parsing and env auth result
pnpm11/config/reader/src/loadNpmrcFiles.ts
Defines the JSON env auth result shape, parses pnpm_config__auth / PNPM_CONFIG_AUTH, threads it through loadNpmrcConfig, and records JSON-derived registry routing.
TypeScript registry precedence and tests
pnpm11/config/reader/src/index.ts, pnpm11/config/reader/test/index.ts
Rebuilds registry precedence to include env JSON and CLI scoped overrides, and adds tests for host tokens, scope routing, malformed JSON, warnings, global auth, and bootstrap propagation.
Rust auth parsing and helpers
pacquet/crates/config/src/npmrc_auth.rs, pacquet/crates/config/src/npmrc_auth/tests.rs
Adds JSON env auth parsing, trusted registry inference, helper validation functions, merge behavior for inferred registries, and unit tests for supported fields, warnings, and URL sanitization.
Rust config integration and end-to-end tests
pacquet/crates/config/Cargo.toml, pacquet/crates/config/src/lib.rs, pacquet/crates/config/src/tests.rs
Wires JSON env auth into Config::current and bootstrap config, adds tracing capture support, and covers config loading, registry routing, bootstrap propagation, and warning capture in end-to-end tests.
CLI override bootstrap propagation
pacquet/crates/cli/src/config_overrides.rs, pacquet/crates/cli/src/config_overrides/tests.rs
Copies CLI registry overrides into package_manager_bootstrap and updates tests to assert the bootstrap registry fields.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Provide a trusted env/global auth path for registry credentials and routing [#12314]

Out-of-scope changes

Code Change Explanation
Trust-boundary guidance added to AGENTS.md (AGENTS.md:254) Repository guidance was added, but it does not implement the auth behavior requested by the issue.
CLI package_manager_bootstrap registry propagation (pacquet/crates/cli/src/config_overrides.rs:63-72) This extends CLI override plumbing beyond the env/global auth recovery path in the issue.

Possibly related PRs

  • pnpm/pnpm#11806: Both PRs adjust registry override plumbing and bootstrap propagation in config handling.
  • pnpm/pnpm#12392: Both PRs modify scope-aware registry auth routing and credential application in NpmrcAuth.
  • pnpm/pnpm#12047: Both PRs touch auth merging and precedence inside pacquet/crates/config/src/npmrc_auth.rs.

Suggested reviewers

  • zkochan
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 21, 2026

Copy link
Copy Markdown

PR Summary by Qodo

feat(config): add structured _auth for safe, CI-friendly registry auth
✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Description

• Add URL-keyed _auth registry auth via global config.yaml or pnpm_config__auth JSON env var.
• Enforce trust boundary: repo config cannot supply or redirect registry credentials.
• Apply consistent precedence and routing across TS CLI and Rust pacquet, with tests.
Diagram

graph TD
  E{{"Env: pnpm_config__auth"}} --> P["JsonAuth parser"] --> TS["TS config reader"] --> O["Effective registries + tokens"]
  G["Global config.yaml (_auth)"] --> P --> RS["Rust config loader"] --> O --> B["Bootstrap registries"]
  subgraph Legend
    direction LR
    _ext{{"External input"}} ~~~ _file["Config file"] ~~~ _mod["Module"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep URL-scoped env vars only (//host/:_authToken)
  • ➕ No new schema to validate or document
  • ➕ Reuses existing auth pipeline without adding a new input channel
  • ➖ Breaks on CI/shells that drop or reject env var names with '/', ':', or '.'
  • ➖ Harder to safely bind scope routing to token source without many env keys
2. Introduce separate safe-named env vars per host/scope (e.g., PNPM_AUTH_)
  • ➕ Avoids JSON parsing failures and quoting issues
  • ➕ Could reduce strict schema complexity
  • ➖ Requires a new mapping layer (hashing/escaping) and discoverability story
  • ➖ More moving parts for precedence/override behavior; harder to audit in CI
3. Support token helpers / executable-based auth in `_auth`
  • ➕ Matches some legacy npmrc auth patterns and enterprise setups
  • ➖ High security risk: executable paths must not be supplied via env/global structured auth
  • ➖ Significantly expands threat model and review surface

Recommendation: Proceed with the PR’s approach: a single structured _auth object from trusted sources (global config.yaml + env JSON) with strict validation and host-pinned routing. It directly solves the CI env-var-name limitation while preserving a strong trust boundary (project config cannot inject or redirect credentials) and keeps parity across TS and pacquet. The strict fail-fast behavior is appropriate here because both sources are user-controlled and silent drops would be difficult to debug and could lead to surprising auth/routing gaps.

Files changed (12) +1852 / -22

Enhancement (6) +468 / -18
json-env-auth.mdDocument and version-bump for new structured '_auth' setting +31/-0

Document and version-bump for new structured '_auth' setting

• Adds a changeset describing the new '_auth' setting, its trusted sources (global config.yaml and pnpm_config__auth), precedence, and security properties. Notes CI motivation and pacquet parity.

.changeset/json-env-auth.md

lib.rsLoad structured '_auth' from trusted sources and apply routing precedence +42/-10

Load structured '_auth' from trusted sources and apply routing precedence

• Integrates JSON '_auth' parsing into the config load, merges it ahead of URL-scoped env auth so env JSON wins on conflicting keys, and applies inferred registry routes after workspace yaml but before PNPM_CONFIG_* overrides. Also normalizes and stores env registry overrides consistently into 'registries.default' and bootstrap registries.

pacquet/crates/config/src/lib.rs

npmrc_auth.rsImplement strict JSON '_auth' parsing + inferred registry routes +173/-1

Implement strict JSON '_auth' parsing + inferred registry routes

• Adds 'NpmrcAuth::from_json_sources' to parse '_auth' from global config value plus 'pnpm_config__auth'/'PNPM_CONFIG__AUTH' (env wins). Validates http(s) URL keys without credentials/query/fragment, validates scopes ('@' or '@org'), accepts only 'authToken', and derives scope→registry routing that can be applied after workspace yaml.

pacquet/crates/config/src/npmrc_auth.rs

workspace_yaml.rsPlumb global '_auth' through settings and define InvalidJsonAuth error +13/-0

Plumb global '_auth' through settings and define InvalidJsonAuth error

• Adds a raw '_auth' field to 'WorkspaceSettings' for the global config.yaml path only (not applied from project yaml) and introduces 'LoadWorkspaceYamlError::InvalidJsonAuth' to surface strict parse failures.

pacquet/crates/config/src/workspace_yaml.rs

index.tsApply '_auth'-inferred registry routes into registries and bootstrap registries +34/-3

Apply '_auth'-inferred registry routes into registries and bootstrap registries

• Passes global yaml '_auth' into 'loadNpmrcConfig', deletes '_auth' afterward to avoid unknown-setting warnings, and merges '_auth'-derived routes into both 'pnpmConfig.registries' and 'packageManagerRegistries'. Ensures CLI scoped registries are re-applied last to preserve 'CLI > _auth > yaml' precedence.

pnpm11/config/reader/src/index.ts

loadNpmrcFiles.tsParse structured '_auth' from env/global yaml into auth keys + trusted routes +175/-4

Parse structured '_auth' from env/global yaml into auth keys + trusted routes

• Introduces strict parsing for 'pnpm_config__auth' JSON and global yaml '_auth', producing '.npmrc'-shaped auth keys and inferred scope→registry routes. Merges JSON auth into both merged and trusted config cascades with env-over-yaml precedence, and validates URL keys/scopes while avoiding secret leakage in error messages.

pnpm11/config/reader/src/loadNpmrcFiles.ts

Bug fix (1) +7 / -0
config_overrides.rsPropagate CLI registry overrides into bootstrap registries +7/-0

Propagate CLI registry overrides into bootstrap registries

• Ensures '--registry' and scoped registry overrides update both the main config and 'package_manager_bootstrap' (including 'registries.default'). This keeps bootstrap resolution consistent with the main cascade.

pacquet/crates/cli/src/config_overrides.rs

Tests (4) +1373 / -2
tests.rsAdd assertions for bootstrap registry override behavior +19/-0

Add assertions for bootstrap registry override behavior

• Extends tests to verify that CLI registry and scoped registry overrides apply to 'package_manager_bootstrap' and default registries, including last-write-wins semantics.

pacquet/crates/cli/src/config_overrides/tests.rs

tests.rsUnit tests for JSON '_auth' parsing, validation, and redaction +288/-0

Unit tests for JSON '_auth' parsing, validation, and redaction

• Adds comprehensive unit tests covering default/scoped tokens, env var casing rules, global-vs-env precedence, URL normalization, strict schema errors, and ensuring errors do not leak URL-embedded secrets.

pacquet/crates/config/src/npmrc_auth/tests.rs

tests.rsEnd-to-end tests for '_auth' trust boundary, routing, and precedence +416/-2

End-to-end tests for '_auth' trust boundary, routing, and precedence

• Adds e2e tests verifying env/global '_auth' configures auth + routing, project workspace '_auth' is ignored, repo registries cannot redirect tokens, and explicit env/CLI registry overrides still win for routing while tokens remain pinned to their declared hosts. Also validates bootstrap propagation and trailing-slash normalization for registry overrides.

pacquet/crates/config/src/tests.rs

index.tsTS tests for '_auth' env/global parsing, precedence, and trust boundary +650/-0

TS tests for '_auth' env/global parsing, precedence, and trust boundary

• Adds extensive tests covering parsing/normalization, default + scoped routing, env casing behavior, strict error handling (including non-leakage of URL secrets), precedence vs workspace/user config and CLI overrides, and ignoring '_auth' in project pnpm-workspace.yaml.

pnpm11/config/reader/test/index.ts

Other (1) +4 / -2
Cargo.tomlAdd 'url' dependency and test tooling for JSON '_auth' parsing +4/-2

Add 'url' dependency and test tooling for JSON '_auth' parsing

• Adds the 'url' crate for strict registry URL validation/normalization and expands dev-dependencies to support the new test suite.

pacquet/crates/config/Cargo.toml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 21, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (14) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used

Grey Divider


Action required

1. Warnings leak URL secrets 🐞 Bug ⛨ Security
Description
NpmrcAuth::from_json_env includes the raw registry URL key in warning strings, and those warnings
are later emitted via tracing::warn!; if the URL contains userinfo (user:pass@) or token-like
query parameters, a malformed entry can leak secrets into CI logs/telemetry.
Code

pacquet/crates/config/src/npmrc_auth.rs[R263-292]

+        for (url, scopes) in root {
+            let serde_json::Value::Object(scopes) = scopes else {
+                auth.warnings.push(format!(
+                    "pnpm_config_auth[{url:?}] ignored: value must be an object keyed by scope.",
+                ));
+                continue;
+            };
+            let normalized = if is_valid_json_auth_registry_url(&url) {
+                normalize_registry_url(&url)
+            } else {
+                String::new()
+            };
+            let nerfed = if normalized.is_empty() { String::new() } else { nerf_dart(&normalized) };
+            if nerfed.is_empty() {
+                auth.warnings.push(format!(
+                    "pnpm_config_auth[{url:?}] ignored: key must be a registry URL.",
+                ));
+                continue;
+            }
+            for (scope, creds) in scopes {
+                if !is_json_auth_scope(&scope) {
+                    auth.warnings.push(format!(
+                        "pnpm_config_auth[{url:?}][{scope:?}] ignored: scope must be \"@\" or a package scope like \"@org\".",
+                    ));
+                    continue;
+                }
+                let serde_json::Value::Object(creds) = creds else {
+                    auth.warnings.push(format!(
+                        "pnpm_config_auth[{url:?}][{scope:?}] ignored: value must be an auth object.",
+                    ));
Evidence
Warnings are constructed using the raw url key and later emitted to logs; nerf_dart explicitly
strips userinfo/query, demonstrating that secret-bearing URL components are expected and should be
redacted before logging.

pacquet/crates/config/src/npmrc_auth.rs[237-316]
pacquet/crates/config/src/npmrc_auth.rs[599-608]
pacquet/crates/network/src/auth/tests.rs[11-21]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`pnpm_config_auth` JSON parsing in pacquet formats warnings by interpolating the *raw* registry URL key (e.g. `pnpm_config_auth[{url:?}] ...`). Those warnings are later flushed to `tracing::warn!`, which can end up in CI logs.
Registry URL strings can legally (or accidentally) contain secret-bearing components like `user:pass@host` or query parameters; logging them is a secret disclosure risk.
## Issue Context
- Warnings are emitted on malformed JSON, non-object shapes, unsupported fields, etc. Even if the config is ignored, the warning path still prints the URL key.
- `nerf_dart` already exists and is designed to strip protocol/query/fragment/basic-auth from URLs.
## Fix Focus Areas
- pacquet/crates/config/src/npmrc_auth.rs[237-316]
- pacquet/crates/config/src/npmrc_auth.rs[599-608]
- pacquet/crates/network/src/auth/tests.rs[11-21]
## Suggested fix approach
1. Introduce a small helper to produce a *log-safe* label for a registry URL key, e.g.:
- If `nerf_dart(url)` is non-empty, use that (or a host-only representation derived from it).
- Otherwise, avoid printing the raw string; use a constant like `"<invalid registry url>"`.
2. Replace **all** warning format strings in `from_json_env` / `apply_json_auth_creds` that currently include `{url:?}` with the sanitized label.
3. Keep the rest of the warning context (scope/field/type label), which is not secret-bearing.
4. Add/adjust a unit test to assert that a URL containing `user:pw@` or `?token=` does not appear in emitted warnings (string match).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Scope auth token exfiltration 🐞 Bug ⛨ Security
Description
Scope-keyed pnpm_config_auth entries are synthesized using pnpmConfig.registries, which can be
set by repo-controlled pnpm-workspace.yaml / project .npmrc, letting an attacker-controlled repo
redirect a victim’s env token/cert/key to an attacker registry and have it sent on network requests.
Code

pnpm11/config/reader/src/index.ts[R546-556]

+  const synthesizedAuth = synthesizeAuthFromScopes(
+    npmrcResult.envJsonAuth.byScope,
+    pnpmConfig.registries,
+    warnings
+  )
+  if (Object.keys(synthesizedAuth).length > 0 || Object.keys(cliUrlScopedAuth).length > 0) {
+    pnpmConfig.authConfig = {
+      ...pnpmConfig.authConfig,
+      ...synthesizedAuth,
+      ...cliUrlScopedAuth,
+    }
Evidence
pnpmConfig.registries is built by merging repo-controlled registries (workspace manifest/yaml) and
is then passed into synthesizeAuthFromScopes, whose output is merged into authConfig and used to
generate auth headers for requests.

pnpm11/config/reader/src/index.ts[466-562]
pnpm11/config/reader/src/loadNpmrcFiles.ts[361-404]
pnpm11/network/auth-header/src/getAuthHeadersFromConfig.ts[63-75]
pacquet/crates/config/src/lib.rs[2031-2063]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Scope-keyed `pnpm_config_auth` values (e.g. `{ "@org": ":_authToken=..." }`) are turned into URL-scoped `//host/...` keys using `pnpmConfig.registries`, which includes repo-controlled registry URLs. This allows credential exfiltration: a malicious repo can set `registries.@org` (or `default`) to `https://attacker.example/` and pnpm will synthesize `//attacker.example/:_authToken`, causing the victim’s env secret to be sent.
### Issue Context
- This is a new trust-binding: env secrets (trusted) are being bound to a host selected by repo config (untrusted).
- Downstream, `getNetworkConfigs` + `getAuthHeadersFromCreds` convert these keys into `Authorization: Bearer ...` (or TLS materials) for requests.
### Fix Focus Areas
- pnpm11/config/reader/src/index.ts[488-562]
- pnpm11/config/reader/src/loadNpmrcFiles.ts[377-404]
- pacquet/crates/config/src/lib.rs[2031-2063]
### Suggested fix direction
Pick one of:
1) **Only synthesize against trusted registry mappings** (e.g. `packageManagerRegistries` / user+auth.ini+env+CLI), not workspace/project registries.
2) Require scope-keyed entries to include an explicit, trusted host mapping (reintroduce an env-provided mapping, or a new allowlist), so repo config cannot choose the destination host.
3) Refuse scope-keyed synthesis when the scope’s registry URL originated from untrusted sources (workspace yaml/project `.npmrc`)—requires provenance tracking.
Add a regression test demonstrating that repo-controlled registry cannot redirect a scope-keyed env token to a different host.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Scope suffix allows tokenHelper 🐞 Bug ⛨ Security
Description
readJsonAuthEnv blocks top-level :tokenHelper keys but scope-keyed suffixes can still synthesize
//host/:tokenHelper entries, which violates the intended executable trust boundary and will
trigger TOKEN_HELPER_IN_PROJECT_CONFIG and abort config loading.
Code

pnpm11/config/reader/src/loadNpmrcFiles.ts[R315-350]

+  const literal: Record<string, string> = {}
+  const byScope: Record<string, string[]> = {}
+  for (const [key, rawValue] of Object.entries(parsed as Record<string, unknown>)) {
+    // Same trust-boundary reason as `readUrlScopedEnvConfig`:
+    // `tokenHelper` names an executable and only the user `.npmrc`
+    // may declare it. The env layer isn't that file, so admitting a
+    // `:tokenHelper` key here would trip TOKEN_HELPER_IN_PROJECT_CONFIG.
+    if (key.endsWith(':tokenHelper')) continue
+
+    if (key.startsWith('//')) {
+      // Literal `.npmrc` URL-scoped key — value is the literal token
+      // string. A `:registry` suffix is a valid `.npmrc` key shape but
+      // inert here — `getNetworkConfigs` only processes `@scope:registry`
+      // keys, and a URL-scoped `:registry` would silently drop. Warn so
+      // the user knows to use the scope form in yaml/CLI instead.
+      if (key.endsWith(':registry')) {
+        warnings.push(`pnpm_config_auth[${JSON.stringify(key)}] ignored: URL-scoped \`:registry\` keys are not honored. Declare the registry in \`pnpm-workspace.yaml\`'s \`registries:\` field, \`.npmrc\`, or via \`--@scope:registry\`.`)
+        continue
+      }
+      if (typeof rawValue !== 'string') {
+        warnings.push(`pnpm_config_auth[${JSON.stringify(key)}] ignored: value must be a string.`)
+        continue
+      }
+      literal[key] = rawValue
+    } else {
+      // Scope name — value is the `.npmrc` suffix (or array of
+      // suffixes). The consumer synthesizes the final `//host/...` key
+      // by resolving the scope→URL from `pnpmConfig.registries` and
+      // appending the suffix to the nerf-darted URL.
+      const suffixes = toStringArray(rawValue)
+      if (suffixes == null) {
+        warnings.push(`pnpm_config_auth[${JSON.stringify(key)}] ignored: value must be a string or array of strings.`)
+        continue
+      }
+      byScope[key] = suffixes
+    }
Evidence
Scope-keyed values are only validated as strings/arrays; the synthesized key is formed from
nerfDart(url) + the suffix key-part, and tokenHelper execution is part of auth-header generation
while index.ts enforces tokenHelper must come from user config.

pnpm11/config/reader/src/loadNpmrcFiles.ts[285-404]
pnpm11/config/reader/src/index.ts[347-357]
pnpm11/network/auth-header/src/getAuthHeadersFromConfig.ts[63-95]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `:tokenHelper` exclusion in `readJsonAuthEnv` only checks the JSON object’s *top-level keys*. A user can still set scope-keyed values like:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. Bootstrap ignores JSON registries 🐞 Bug ☼ Reliability
Description
In the TS config reader, pnpm_config_auth.registries is merged into pnpmConfig.registries but
not into pnpmConfig.packageManagerRegistries, so package-manager bootstrap/self-download flows
(e.g. switchCliVersion) won’t honor JSON env scoped registries and may resolve against the wrong
registry or fail in CI.
Code

pnpm11/config/reader/src/index.ts[R485-495]

const workspaceRegistries = pnpmConfig.registries as Record<string, string> | undefined
pnpmConfig.registries = {
...registriesFromNpmrc,
...workspaceRegistries,
+    // Trusted env layer wins over the repo-controlled yaml on conflicting
+    // scope keys (mirrors the env-over-workspace ordering in loadNpmrcFiles.ts).
+    ...npmrcResult.envJsonAuth.registries,
+    // CLI per-scope registries re-applied last so an explicit
+    // `--@scope:registry=...` flag wins over yaml AND env JSON, matching
+    // the documented "CLI > env > yaml" precedence.
+    ...cliScopedRegistries,
Evidence
The code merges JSON env registries into pnpmConfig.registries, but packageManagerRegistries is
created earlier from trusted sources that do not include envJsonAuth.registries. Bootstrap flows
consume packageManagerRegistries via getPackageManagerBootstrapConfig, so they won’t see JSON
env scoped registries.

pnpm11/config/reader/src/index.ts[325-339]
pnpm11/config/reader/src/index.ts[466-513]
pnpm11/config/reader/src/loadNpmrcFiles.ts[135-177]
pnpm11/pnpm/src/packageManagerRegistries.ts[19-37]
pnpm11/pnpm/src/switchCliVersion.ts[31-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`pnpm_config_auth.registries` is applied only to `pnpmConfig.registries`. However, pnpm’s package-manager bootstrap path (used for pnpm self-download/version switching) uses `config.packageManagerRegistries`, which is built from `trustedConfig` and does not include `envJsonAuth.registries`. This makes the new JSON env registry routing ineffective for bootstrap flows.
## Issue Context
- `envJsonAuth.registries` is intentionally kept out of `mergedConfig`/`trustedConfig` (it is merged later in `index.ts`).
- `packageManagerRegistries` is derived from `trustedAuthConfig`/`trustedNetworkConfigs`, so it will miss JSON env registries unless they are explicitly merged.
## Fix Focus Areas
- pnpm11/config/reader/src/index.ts[466-513]
### Concrete fix
After merging/normalizing `pnpmConfig.registries`, also merge `npmrcResult.envJsonAuth.registries` into `pnpmConfig.packageManagerRegistries` (normalize URLs), so bootstrap uses the same trusted env registry routing.
### Test
Add a config-reader test that sets `pnpm_config_auth={"registries":{"@pnpm":"https://mirror.example/"}}` and asserts `config.packageManagerRegistries['@pnpm']` (and/or bootstrap registries) reflects it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Bootstrap drops JSON registries 🐞 Bug ≡ Correctness
Description
In pacquet, pnpm_config_auth.registries is removed from env_json_auth before building
PackageManagerBootstrap, and only re-inserted into Config.registries later, so bootstrap
registry routing can miss JSON-provided scope routes. This can cause pnpm auto-switch downloads
(bootstrap path) to resolve scoped packages against the wrong registry or fail entirely in
restricted environments.
Code

pacquet/crates/config/src/lib.rs[R1884-1918]

+        let mut env_json_auth = crate::npmrc_auth::NpmrcAuth::from_json_env::<Sys>();
+        let env_json_registries = std::mem::take(&mut env_json_auth.scoped_registries);
+        // Gate on creds OR warnings: a malformed `pnpm_config_auth` (or a
+        // non-object sub-field) may push warnings without producing any
+        // creds. Dropping the source in that case would silently discard
+        // the warnings too, since `apply_registry_and_warn` is what flushes
+        // them through `tracing::warn!`. Warrant a seat in the merge chain
+        // whenever there is anything to surface.
+        let env_json_source = (!env_json_auth.creds_by_scope_by_uri.is_empty()
+            || !env_json_auth.warnings.is_empty())
+        .then_some(env_json_auth);
+
// Capture the trusted sources (everything but `project_source`) for
// [`PackageManagerBootstrap`] before the fold below consumes them.
-        let trusted_sources =
-            [env_scoped_source.clone(), auth_ini_source.clone(), user_source.clone()];
+        let trusted_sources = [
+            env_json_source.clone(),
+            env_scoped_source.clone(),
+            auth_ini_source.clone(),
+            user_source.clone(),
+        ];
// Fold high-priority-first: the first present source is the
// base, each lower source fills the gaps it left
-        // ([`NpmrcAuth::merge_under`]).
+        // ([`NpmrcAuth::merge_under`]). `env_json_source` is listed before
+        // `env_scoped_source` so the JSON envelope wins on the rare occasion
+        // both define the same `//host/:_authToken` key — matches pnpm's
+        // TS merge, where `envJsonAuth.auth` is spread after
+        // `envScopedConfig` so later wins.
let mut sources =
-            [env_scoped_source, project_source, auth_ini_source, user_source].into_iter().flatten();
+            [env_json_source, env_scoped_source, project_source, auth_ini_source, user_source]
+                .into_iter()
+                .flatten();
let mut npmrc_auth = sources.next().unwrap_or_default();
for lower in sources {
npmrc_auth.merge_under(lower);
Evidence
env_json_auth.scoped_registries is taken out before trusted_auth is folded and before
build_package_manager_bootstrap runs, while the re-application of those registries happens only
later on the main config. Bootstrap registries are derived via apply_registry_and_warn, which
consumes scoped_registries, so an emptied env_json_source cannot influence bootstrap routing.

pacquet/crates/config/src/lib.rs[1875-1929]
pacquet/crates/config/src/lib.rs[2037-2049]
pacquet/crates/config/src/npmrc_auth.rs[590-599]
pacquet/crates/config/src/lib.rs[2140-2162]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PackageManagerBootstrap` is built from `trusted_auth`, but `env_json_auth.scoped_registries` is emptied via `mem::take` before `trusted_auth` is folded and before the bootstrap is built. The JSON registries are only applied later to `self.registries`, so the bootstrap path can’t see them.
### Issue Context
Bootstrap is intended to use *trusted* sources (user/auth.ini/env/CLI), and `pnpm_config_auth` is explicitly an env-sourced trusted layer; its `registries` should influence bootstrap resolution of the package manager.
### Fix Focus Areas
- pacquet/crates/config/src/lib.rs[1875-1929]
- pacquet/crates/config/src/lib.rs[2037-2049]
### Suggested fix
Avoid stripping `scoped_registries` out of the value that participates in the trusted fold used to build `PackageManagerBootstrap`.
One concrete approach:
1. Parse `env_json_auth`.
2. **Clone** `env_json_auth.scoped_registries` into `env_json_registries` for the later “re-apply after workspace yaml” step.
3. Keep `env_json_auth.scoped_registries` intact so it can contribute to the `trusted_auth` fold and thus to `build_package_manager_bootstrap`.
4. Still re-insert `env_json_registries` after `workspace_yaml.apply_to` to preserve env-over-yaml precedence.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Default registry ignored 🐞 Bug ≡ Correctness
Description
Pacquet re-applies pnpm_config_auth.registries by inserting every key into Config.registries, so
a JSON env entry for default never updates Config.registry and is overwritten when computing
resolved registries. This makes pnpm_config_auth.registries.default silently ineffective in
pacquet, diverging from how pnpm-workspace.yaml and the TS reader treat default.
Code

pacquet/crates/config/src/lib.rs[R2047-2049]

+        for (scope, url) in env_json_registries {
+            self.registries.insert(scope, url);
+        }
Evidence
The pacquet reapply loop inserts all JSON env registries into self.registries, but pacquet’s
resolved registry map always overwrites default from self.registry; workspace-yaml explicitly
special-cases default to set config.registry, so JSON env should do the same to avoid silently
ignoring default.

pacquet/crates/config/src/lib.rs[2037-2049]
pacquet/crates/config/src/lib.rs[1531-1537]
pacquet/crates/config/src/workspace_yaml.rs[764-772]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In pacquet, `pnpm_config_auth.registries` is re-applied by inserting all entries into `Config.registries`. Unlike workspace-yaml handling, the `default` key must update `Config.registry`; otherwise it gets overwritten by `resolved_registries()` which always sets `default` from `Config.registry`.
## Issue Context
- `WorkspaceSettings::apply_to` treats `registries.default` specially (updates `config.registry`).
- `Config::resolved_registries()` always inserts `default` from `self.registry`, overwriting any `default` key that may exist in `self.registries`.
## Fix Focus Areas
- pacquet/crates/config/src/lib.rs[2037-2049]
- pacquet/crates/config/src/workspace_yaml.rs[764-772]
- pacquet/crates/config/src/lib.rs[1531-1537]
## Suggested approach
1. In the `for (scope, url) in env_json_registries` loop, add:
- `if scope == "default" { self.registry = url; continue; }`
- else `self.registries.insert(scope, url)`.
2. Add an end-to-end test in `pacquet/crates/config/src/tests.rs` asserting that `pnpm_config_auth={"registries":{"default":"https://.../"}}` updates `config.registry` (or `config.resolved_registries()["default"]`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

7. Scope error log injection 🐞 Bug ⛨ Security
Description
In pacquet, JsonAuthScope::try_from interpolates the raw (unescaped) scope string into an error
message; a malformed _auth value containing newlines/ANSI control chars can forge or obscure
CLI/CI logs when config loading aborts. This string is propagated to user-visible output via
LoadWorkspaceYamlError::InvalidJsonAuth.
Code

pacquet/crates/config/src/npmrc_auth.rs[R1098-1106]

+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        if value == DEFAULT_REGISTRY_SCOPE {
+            return Ok(JsonAuthScope::Default);
+        }
+        if is_package_scope(&value) {
+            return Ok(JsonAuthScope::Package(value));
+        }
+        Err(format!("scope \"{value}\" must be \"@\" or a package scope like \"@org\""))
+    }
Evidence
JsonAuthScope::try_from formats the raw scope into the error string, and InvalidJsonAuth
displays the underlying serde_json::Error text directly, so any control characters in the scope
can reach logs/CLI output.

pacquet/crates/config/src/npmrc_auth.rs[1095-1106]
pacquet/crates/config/src/workspace_yaml.rs[533-537]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`JsonAuthScope::try_from` builds an error string containing the raw `value` (scope). If the scope contains control characters (e.g. `\n`, `\r`, ANSI escapes), the resulting error text can inject/forge log lines when surfaced through `InvalidJsonAuth`.
### Issue Context
This PR intentionally makes `_auth` parsing strict (hard error). That makes error-message safety more important because the error is expected to reach CLI output/CI logs.
### Fix Focus Areas
- pacquet/crates/config/src/npmrc_auth.rs[1095-1106]
- pacquet/crates/config/src/workspace_yaml.rs[533-537]
### Implementation notes
- Avoid echoing the raw scope string, or escape it before formatting (e.g., use `{:?}` debug formatting to get escaped control chars, or implement a small sanitizer that replaces control chars with visible escapes).
- Keep the existing property that URL keys never leak secrets; apply the same log-safety principle to scope strings.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Unnecessary JSON auth clone 🐞 Bug ➹ Performance
Description
NpmrcAuth::from_json_sources clones the entire global _auth JSON value before parsing,
duplicating potentially large auth maps on every config load and adding avoidable allocation/CPU
overhead on a common startup path.
Code

pacquet/crates/config/src/npmrc_auth.rs[R228-232]

+    pub fn from_json_sources<Sys: EnvVar>(global_value: Option<&serde_json::Value>) -> Self {
+        let mut auth = NpmrcAuth::default();
+        if let Some(global_value) = global_value {
+            auth.apply_json_object(global_value.clone(), "_auth");
+        }
Evidence
The code currently deep-clones the global _auth JSON even though the parser only needs read
access; the repo’s review guide explicitly calls out avoiding extra work in common paths.

pacquet/crates/config/src/npmrc_auth.rs[228-232]
REVIEW_GUIDE.md[115-127]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`NpmrcAuth::from_json_sources` accepts `global_value: Option<&serde_json::Value>` but immediately calls `global_value.clone()` to feed `apply_json_object`, causing a full deep clone of the `_auth` value.
### Issue Context
Config loading runs on essentially every pnpm/pacquet invocation; per `REVIEW_GUIDE.md`, avoid extra work on common flows.
### Fix Focus Areas
- pacquet/crates/config/src/npmrc_auth.rs[228-260]
### Suggested fix
- Change `apply_json_object(&mut self, parsed: serde_json::Value, source: &str)` to accept `&serde_json::Value` (or `Option<&Map<..>>`) and iterate with `as_object()` / `iter()`.
- Update both call sites:
- `from_json_sources`: pass `global_value` by reference without cloning.
- env JSON path: keep the parsed `Value` in a local and pass `&parsed`.
- Keep behavior identical (warnings, last-write-wins, ordering) by cloning only the specific strings you persist into `self` (URLs/scopes/tokens), not the whole JSON tree.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Loose scope validation 🐞 Bug ☼ Reliability
Description
pacquet’s JSON _auth parser accepts any scope starting with @ (excluding / and :), so values
like "@org " or "@org\n" are treated as valid and stored as routing/auth keys. Auth-header
lookup derives the scope from the package name verbatim (e.g. "@org"), so these non-canonical
scope keys will never match and credentials/routing silently won’t apply.
Code

pacquet/crates/config/src/npmrc_auth.rs[R1095-1097]

+fn is_json_auth_scope(scope: &str) -> bool {
+    scope == DEFAULT_REGISTRY_SCOPE || is_package_scope(scope)
+}
Evidence
The JSON _auth path uses is_json_auth_scope() which delegates to a permissive
is_package_scope(); downstream auth selection uses a scope derived directly from the package name,
so any malformed scope string (with whitespace/control chars) cannot be selected and the configured
credential/route becomes ineffective without warning.

pacquet/crates/config/src/npmrc_auth.rs[1095-1097]
pacquet/crates/config/src/npmrc_auth.rs[1071-1073]
pacquet/crates/network/src/auth.rs[219-229]
pacquet/crates/network/src/auth.rs[311-320]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`NpmrcAuth`'s structured JSON `_auth` scope validation is too permissive: it accepts strings like `@org ` / `@org\n` as valid scopes. Those scopes are then used as map keys for both inferred registry routing and scoped credentials, but downstream auth lookup derives scopes from package names (e.g. `@org/foo` → `@org`) and will never match the malformed keys, causing auth/routing to silently not apply.
### Issue Context
This specifically affects the new structured `_auth` parsing path (`from_json_sources` → `apply_json_object`), which relies on `is_json_auth_scope()`.
### Fix Focus Areas
- pacquet/crates/config/src/npmrc_auth.rs[1095-1097]
- pacquet/crates/config/src/npmrc_auth.rs[1071-1073]
Suggested implementation direction:
- Make `is_json_auth_scope()` stricter than the existing `is_package_scope()` (to avoid changing semantics for other `.npmrc` parsing paths).
- Reject scopes containing any whitespace/control characters and/or enforce a conservative scope regex (e.g. `^@[A-Za-z0-9][A-Za-z0-9._-]*$`).
- When rejecting, emit a warning and skip the entry so misconfigurations fail loudly rather than silently.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (5)
10. Default port not normalized 🐞 Bug ≡ Correctness
Description
In pacquet’s JSON _auth parser, inferred registry routes are stored using
normalize_registry_url(), which only appends a trailing slash and preserves explicit default ports
like :443/:80. This diverges from the TS reader (which canonicalizes via `new
URL(...).href/normalizeRegistryUrl`) and can yield inconsistent
config.registry/config.registries values across implementations for the same logical registry.
Code

pacquet/crates/config/src/npmrc_auth.rs[R296-335]

+            let normalized = normalize_registry_url(&url);
+            let nerfed = nerf_dart(&normalized);
+            if nerfed.is_empty() {
+                self.warnings.push(format!(
+                    "{source}[{label}] ignored: key must be an http(s) registry URL.",
+                ));
+                continue;
+            }
+            for (scope, creds) in scopes {
+                if !is_json_auth_scope(&scope) {
+                    self.warnings.push(format!(
+                        "{source}[{label}][{scope:?}] ignored: scope must be \"@\" or a package scope like \"@org\".",
+                    ));
+                    continue;
+                }
+                let serde_json::Value::Object(creds) = creds else {
+                    self.warnings.push(format!(
+                        "{source}[{label}][{scope:?}] ignored: value must be an auth object.",
+                    ));
+                    continue;
+                };
+                let accepted_creds =
+                    apply_json_auth_creds(self, &nerfed, &label, &scope, creds, source);
+                if !accepted_creds {
+                    continue;
+                }
+                // The host and secret arrive in one trusted value, so the
+                // same entry is a safe source of registry routing — repo
+                // config cannot pick a different destination for this
+                // credential. "@" routes the default registry; a package
+                // scope routes that scope. Last-wins on duplicate scope
+                // keys, matching how yaml and CLI handle duplicate scope
+                // keys.
+                let route_key = if scope == DEFAULT_REGISTRY_SCOPE {
+                    "default".to_string()
+                } else {
+                    scope.clone()
+                };
+                self.json_env_registries.insert(route_key, normalized.clone());
+            }
Evidence
pacquet derives the stored route from the raw JSON key using normalize_registry_url(&url) and
inserts it into json_env_registries, but that helper only appends a trailing slash. In contrast,
the TS reader normalizes via WHATWG URL (parsed.href) and normalizeRegistryUrl, and a test
asserts that an explicit :443 is removed.

pacquet/crates/config/src/npmrc_auth.rs[262-335]
pacquet/crates/config/src/npmrc_auth.rs[847-851]
pnpm11/config/reader/src/loadNpmrcFiles.ts[320-345]
pnpm11/config/reader/test/index.ts[1241-1259]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
pacquet normalizes JSON `_auth` registry URL keys by only appending a trailing slash, so explicit default ports (e.g. `https://reg.example:443`) remain in `config.registry`/`config.registries` as `https://reg.example:443/`. The TS implementation canonicalizes these away (via WHATWG URL normalization), so the same input produces `https://reg.example/`.
### Issue Context
This is a pacquet/TS parity problem introduced specifically on the new JSON `_auth` path. It can lead to surprising routing strings and inconsistent equality/comparison behavior downstream.
### Fix Focus Areas
- pacquet/crates/config/src/npmrc_auth.rs[296-335]
### Suggested fix
Implement a stronger registry URL canonicalization for JSON `_auth` keys (at least: lowercase scheme/host and strip default ports 80/443 for http/https) before inserting into `json_env_registries` (and thus `config.registry`/`config.registries`). Prefer reusing an existing shared URL parsing/normalization utility if one exists in pacquet, otherwise add a small helper dedicated to registry URL normalization that matches pnpm’s `normalizeRegistryUrl` behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. JSON URL casing mismatch 🐞 Bug ☼ Reliability
Description
In pacquet, NpmrcAuth::from_json_env only appends a trailing slash before calling nerf_dart, so
scheme/host casing (and default-port stripping, which is scheme-case-sensitive) can differ from the
actual request URL and cause JSON-env credentials and inferred registry routes to be missed
unexpectedly.
Code

pacquet/crates/config/src/npmrc_auth.rs[R283-285]

+            let normalized = normalize_registry_url(&url);
+            let nerfed = nerf_dart(&normalized);
+            if nerfed.is_empty() {
Evidence
from_json_env derives normalized via a helper that only adds a trailing slash, and feeds it
directly into nerf_dart. nerf_dart’s parser does not lowercase scheme/host, and its default-port
stripping is case-sensitive on the scheme, so semantically equivalent URL spellings can produce
different nerf-dart keys and miss auth/routing lookups.

pacquet/crates/config/src/npmrc_auth.rs[237-326]
pacquet/crates/config/src/npmrc_auth.rs[836-840]
pacquet/crates/network/src/auth.rs[327-369]
pacquet/crates/network/src/auth.rs[419-421]
pnpm11/config/reader/src/loadNpmrcFiles.ts[286-310]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`pnpm_config_auth` JSON registry URL keys are not canonicalized enough on the Rust side. The code currently only ensures a trailing slash before computing `nerf_dart`, but `nerf_dart` preserves the original scheme/host casing and strips default ports only when the scheme equals `"https"`/`"http"` exactly.
This can cause credentials and inferred registry routes to be keyed under a different nerf-dart value than later lookups (which typically come from parsed/normalized URLs), resulting in missing auth headers or routing.
### Issue Context
TypeScript uses `new URL(url)` and then `normalizeRegistryUrl(parsed.href)`, which canonicalizes scheme/hostname casing and default ports before keying auth.
### Fix Focus Areas
- pacquet/crates/config/src/npmrc_auth.rs[283-285]
- pacquet/crates/config/src/npmrc_auth.rs[836-840]
- pacquet/crates/network/src/auth.rs[327-369]
- pacquet/crates/network/src/auth.rs[419-421]
### Suggested fix
- Introduce a Rust-side canonicalization for registry URLs used by `from_json_env` that matches the TS behavior:
- Require http/https.
- Lowercase the scheme and hostname.
- Strip default ports (80 for http, 443 for https) regardless of scheme casing.
- Preserve path semantics and ensure trailing slash.
- Use that canonicalized URL both for:
- `nerf_dart` computation (auth keying), and
- `json_env_registries` routing values.
- Add a unit test for `from_json_env` proving that e.g. `"HTTPS://JSON-Test.Example:443"` yields the same effective nerf-dart key and routing as `"https://json-test.example"` (mirroring the TS test coverage).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Log injection in warnings 🐞 Bug ⛨ Security
Description
In pacquet, json_auth_entry_label() interpolates the raw host_port substring from
pnpm_config_auth into warning strings without escaping control characters. If an attacker can
influence the environment, they can inject newlines/ANSI escapes into logs when these warnings are
emitted via tracing::warn!, obscuring or forging CI output.
Code

pacquet/crates/config/src/npmrc_auth.rs[R1126-1144]

+fn json_auth_entry_label(url: &str, entry_number: usize) -> String {
+    let entry_label = format!("entry {entry_number}");
+    let Some((scheme, rest)) = url.split_once("://") else {
+        return entry_label;
+    };
+    if scheme != "https" && scheme != "http" {
+        return entry_label;
+    }
+    let authority = rest.split(['/', '?', '#']).next().unwrap_or(rest);
+    let host_port = authority.rsplit_once('@').map_or(authority, |(_, host_port)| host_port);
+    let host = match host_port.rsplit_once(':') {
+        Some((host, _)) if !host.contains('[') => host,
+        _ => host_port,
+    };
+    if host.is_empty() {
+        return entry_label;
+    }
+    format!("{entry_label} ({scheme}://{host_port})")
+}
Evidence
from_json_env() generates label via json_auth_entry_label() and interpolates it into warning
strings. Those warnings are later emitted verbatim via tracing::warn!, so any control characters
present in the label will reach logs unchanged.

pacquet/crates/config/src/npmrc_auth.rs[263-280]
pacquet/crates/config/src/npmrc_auth.rs[1120-1144]
pacquet/crates/config/src/npmrc_auth.rs[609-617]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`json_auth_entry_label()` formats a warning label using `{host_port}` derived directly from the `pnpm_config_auth` env var. Because `host_port` is not sanitized/escaped, control characters (e.g. `\n`, `\r`, ANSI escapes) can be embedded into warnings and then logged via `tracing::warn!`, enabling log-forging/obfuscation.
## Issue Context
This is not a secret-leak issue (userinfo/query/fragment are already guarded), but it is a log integrity issue under the repository’s security guidance (env vars are attacker-controlled inputs).
## Fix Focus Areas
- pacquet/crates/config/src/npmrc_auth.rs[1120-1144]
- pacquet/crates/config/src/npmrc_auth.rs[263-280]
- pacquet/crates/config/src/npmrc_auth.rs[609-617]
## Suggested fix
- Sanitize the displayed host/host:port before embedding into the label. For example:
- Build `safe_host_port` via `host_port.chars().flat_map(|c| c.escape_default()).collect::<String>()`, and use that in the `format!(...)`.
- Alternatively, format it using debug (`{host_port:?}`) to force escaping, though this changes the label shape.
- Consider also bounding the label length to avoid extremely large log lines from env input.
- Add a unit test with a host containing `\n` and/or `\u001b[` to assert warnings contain escaped sequences rather than raw control characters.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Suffix map prototype bypass 🐞 Bug ⛨ Security
Description
appendJsonAuthCreds() looks up JSON_AUTH_FIELD_SUFFIXES[field] on a plain object without an
own-property check, so fields like "__proto__"/"toString" can be misclassified as supported and mark
the entry as accepted. This can incorrectly infer and apply registries[...] = normalized routing
for an entry that provided no actually-supported auth fields, changing registry resolution
unexpectedly.
Code

pnpm11/config/reader/src/loadNpmrcFiles.ts[R307-321]

+  const keyPrefix = `${nerfed}:${scope === '@' ? '' : `${scope}:`}`
+  let accepted = false
+  for (const [field, value] of Object.entries(creds)) {
+    const suffix = JSON_AUTH_FIELD_SUFFIXES[field]
+    if (suffix == null) {
+      warnings.push(`pnpm_config_auth[${JSON.stringify(url)}][${JSON.stringify(scope)}][${JSON.stringify(field)}] ignored: unsupported auth field.`)
+      continue
+    }
+    if (typeof value !== 'string') {
+      warnings.push(`pnpm_config_auth[${JSON.stringify(url)}][${JSON.stringify(scope)}][${JSON.stringify(field)}] ignored: value must be a string.`)
+      continue
+    }
+    result[`${keyPrefix}${suffix}`] = value
+    accepted = true
+  }
Evidence
The suffix lookup is done via a plain object property access (prototype chain visible), and route
inference is gated solely on acceptedCreds returned by appendJsonAuthCreds, so a spurious
accepted=true directly enables registry route insertion. Additionally, any //... key is
considered INI/npmrc-readable, so malformed synthesized keys can enter the merged config pipeline.

pnpm11/config/reader/src/loadNpmrcFiles.ts[231-285]
pnpm11/config/reader/src/loadNpmrcFiles.ts[299-331]
pnpm11/config/reader/src/localConfig.ts[186-199]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`appendJsonAuthCreds()` uses `JSON_AUTH_FIELD_SUFFIXES[field]` on a normal object literal. In JS, property access traverses the prototype chain, so attacker-controlled JSON keys like `"__proto__"`, `"toString"`, or `"constructor"` can return a non-`null` value, bypass the unsupported-field check, and set `accepted = true`. That makes `readJsonAuthEnv()` infer registry routes (`registries[...]=normalized`) even though no supported auth field was provided.
### Issue Context
This code runs on the new `pnpm_config_auth` JSON env var parsing path and is explicitly designed to be robust against malformed input (warn + continue). Prototype-chain field lookups undermine that validation.
### Fix Focus Areas
- pnpm11/config/reader/src/loadNpmrcFiles.ts[309-330]
Suggested implementation direction:
- Replace the object-literal lookup with an own-property guard:
- `const suffix = Object.prototype.hasOwnProperty.call(JSON_AUTH_FIELD_SUFFIXES, field) ? JSON_AUTH_FIELD_SUFFIXES[field] : undefined`
- or use `Object.hasOwn(JSON_AUTH_FIELD_SUFFIXES, field)` where available.
- Alternatively define the suffix map as `Object.create(null)` (or a `Map`) so prototype properties are impossible.
- Ensure `accepted` only flips to `true` when the field is one of the explicitly supported ones.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


14. Unnormalized CLI scoped registry 🐞 Bug ≡ Correctness
Description
getConfig() re-applies --@scope:registry CLI values into packageManagerRegistries without
normalizeRegistryUrl(), potentially leaving a non-canonical base URL (e.g. missing trailing slash)
that can break URL joins or registry matching in bootstrap/self-download flows. This de-normalizes
values that were already normalized when read via getNetworkConfigs() for other registry maps,
creating inconsistent behavior between registries and packageManagerRegistries.
Code

pnpm11/config/reader/src/index.ts[R332-350]

+  const cliScopedRegistries: Record<string, string> = {}
+  for (const [key, value] of Object.entries(cliOptions)) {
+    if (key.startsWith('@') && key.endsWith(':registry') && typeof value === 'string') {
+      cliScopedRegistries[key.slice(0, -':registry'.length)] = value
+    }
+  }
pnpmConfig.registries = { ...registriesFromNpmrc }
if (explicitlySetKeys.has('registry') && typeof pnpmConfig.registry === 'string') {
pnpmConfig.registries.default = normalizeRegistryUrl(pnpmConfig.registry)
}
pnpmConfig.packageManagerRegistries = {
default: normalizeRegistryUrl(trustedAuthConfig.registry as string),
...trustedNetworkConfigs.registries,
+    // Trusted env-inferred routes from `pnpm_config_auth` — the host
+    // and credential arrive in one env value, so bootstrap (self-download
+    // / version switching) honors the same routing as regular installs.
+    ...npmrcResult.envJsonAuth.registries,
+    ...cliScopedRegistries,
+  }
Evidence
The PR introduces cliScopedRegistries by copying raw string values from cliOptions and spreading
them into packageManagerRegistries without normalization. Elsewhere, scoped registries are
normalized through getNetworkConfigs() (which calls normalizeRegistryUrl for @scope:registry
keys), and getPackageManagerRegistries() assumes config.packageManagerRegistries is already in
the correct shape (no normalization occurs there).

pnpm11/config/reader/src/index.ts[332-353]
pnpm11/config/reader/src/getNetworkConfigs.ts[13-21]
pnpm11/pnpm/src/packageManagerRegistries.ts[19-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`cliScopedRegistries` is populated from raw `cliOptions` and then spread into `pnpmConfig.packageManagerRegistries` without passing values through `normalizeRegistryUrl()`. Unlike `getNetworkConfigs()` (which normalizes `@scope:registry` values) and unlike `pnpmConfig.registries` (which is normalized in a later loop), `packageManagerRegistries` currently has no post-normalization step.
### Issue Context
`packageManagerRegistries` is used for pnpm's bootstrap/self-download flows (e.g. `switchCliVersion`) via `getPackageManagerBootstrapConfig()` / `getPackageManagerRegistries()`.
### Fix
Normalize the values as you collect them (recommended):
- When inserting into `cliScopedRegistries`, use `normalizeRegistryUrl(value)`.
Optionally, also add a small normalization pass over `pnpmConfig.packageManagerRegistries` after construction (similar to the `pnpmConfig.registries` normalization loop), but the minimal fix is to normalize `cliScopedRegistries`.
### Fix Focus Areas
- pnpm11/config/reader/src/index.ts[332-350]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread pacquet/crates/config/src/lib.rs Outdated
Comment thread pnpm11/config/reader/src/index.ts
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 21, 2026

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@aqeelat aqeelat force-pushed the feat/json-env-auth branch from 199923c to 67b5502 Compare June 21, 2026 16:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pacquet/crates/config/src/tests.rs (1)

449-472: ⚡ Quick win

Trim test doc comments to contract-level intent and remove implementation-history prose.

These /// blocks mostly restate the test body and include historical context (for example “mirrors TS-side merge” / “regression for review-found bug”). In this file, concise test names already carry the scenario; keep comments only for non-obvious invariants.

As per coding guidelines, “Tests are documentation. Do not duplicate test scenarios … in doc comments” and “Do not record past implementation shape/refactor history in source comments.”

Also applies to: 499-555

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pacquet/crates/config/src/tests.rs` around lines 449 - 472, Trim the doc
comment blocks (the `///` comments) for the test functions
json_env_auth_token_is_used_and_outranks_project_npmrc and other tests in the
noted range (around lines 499-555) to contain only the contract-level intent.
Remove any prose that restates the test body implementation, historical context
about past refactors or regressions, or implementation-specific details like
"mirrors pnpm's TS-side merge". Keep comments only if they describe non-obvious
invariants that aren't already clear from the test name and body.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pacquet/crates/config/src/lib.rs`:
- Around line 1884-1903: The issue is that scoped_registries are extracted from
env_json_auth via std::mem::take before env_json_source is created, leaving
env_json_source with empty scoped_registries. This causes
PackageManagerBootstrap to be built without the JSON env scoped registries. To
fix this, reorder the code so that env_json_source is created from the complete
env_json_auth (with scoped_registries still intact) before std::mem::take is
called to extract the registries into env_json_registries. This ensures
trusted_sources receives the full env_json_source with all registry information
for proper bootstrap construction.

In `@pacquet/crates/config/src/tests.rs`:
- Around line 549-572: The test
`json_env_warnings_survive_when_no_creds_present` documents that it verifies
warning emission for malformed JSON, but only asserts the absence of credentials
and registries. Add an assertion to verify that a tracing warn event is actually
emitted when the malformed `pnpm_config_auth` environment variable with an
invalid `registries` field is processed during the `load_with_fake_env` call.
This will ensure the test properly validates the warning path and prevent
regressions where warnings could be silently dropped.

---

Nitpick comments:
In `@pacquet/crates/config/src/tests.rs`:
- Around line 449-472: Trim the doc comment blocks (the `///` comments) for the
test functions json_env_auth_token_is_used_and_outranks_project_npmrc and other
tests in the noted range (around lines 499-555) to contain only the
contract-level intent. Remove any prose that restates the test body
implementation, historical context about past refactors or regressions, or
implementation-specific details like "mirrors pnpm's TS-side merge". Keep
comments only if they describe non-obvious invariants that aren't already clear
from the test name and body.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5723f6e9-5bc6-4b42-8b27-86ee4cccd576

📥 Commits

Reviewing files that changed from the base of the PR and between b4fdfe9 and 199923c.

📒 Files selected for processing (8)
  • .changeset/json-env-auth.md
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/config/src/npmrc_auth.rs
  • pacquet/crates/config/src/npmrc_auth/tests.rs
  • pacquet/crates/config/src/tests.rs
  • pnpm11/config/reader/src/index.ts
  • pnpm11/config/reader/src/loadNpmrcFiles.ts
  • pnpm11/config/reader/test/index.ts

Comment thread pacquet/crates/config/src/lib.rs Outdated
Comment thread pacquet/crates/config/src/tests.rs Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 67b5502

@codecov-commenter

codecov-commenter commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.74%. Comparing base (54c5c0e) to head (2df7ddc).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
pacquet/crates/config/src/npmrc_auth.rs 93.42% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12559      +/-   ##
==========================================
+ Coverage   88.58%   88.74%   +0.15%     
==========================================
  Files         339      342       +3     
  Lines       49171    50442    +1271     
==========================================
+ Hits        43557    44763    +1206     
- Misses       5614     5679      +65     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread pacquet/crates/config/src/lib.rs Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 67b5502

@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Commit: 2df7ddc5238d

Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD.

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.537 ± 0.101 4.393 4.653 1.69 ± 0.08
pacquet@main 4.377 ± 0.128 4.263 4.643 1.63 ± 0.08
pnpr@HEAD 2.781 ± 0.140 2.553 2.993 1.04 ± 0.07
pnpr@main 2.685 ± 0.114 2.564 2.886 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.537346464780001,
      "stddev": 0.10144930975702958,
      "median": 4.54011224908,
      "user": 4.1105765,
      "system": 2.1562740000000007,
      "min": 4.39304046558,
      "max": 4.6528624375800005,
      "times": [
        4.43271964958,
        4.49969419158,
        4.63783095758,
        4.63357072358,
        4.63038972858,
        4.6528624375800005,
        4.41693853858,
        4.49588764858,
        4.39304046558,
        4.58053030658
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.37711796748,
      "stddev": 0.12813898112042194,
      "median": 4.32565030658,
      "user": 4.1013989,
      "system": 2.1513273,
      "min": 4.26305122558,
      "max": 4.64265380258,
      "times": [
        4.31629294758,
        4.37236635958,
        4.26912552658,
        4.26655808758,
        4.26305122558,
        4.53629735958,
        4.64265380258,
        4.45353375258,
        4.33493681658,
        4.31636379658
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.78057984868,
      "stddev": 0.1403011410959097,
      "median": 2.8140635655799997,
      "user": 2.9652217,
      "system": 1.8517778,
      "min": 2.55310008958,
      "max": 2.9933366025800003,
      "times": [
        2.55310008958,
        2.92363187658,
        2.62369272958,
        2.67615034458,
        2.87675167958,
        2.9933366025800003,
        2.80486774858,
        2.68788567058,
        2.84312236258,
        2.82325938258
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.68484094858,
      "stddev": 0.11368150713576404,
      "median": 2.65461673408,
      "user": 3.0037651,
      "system": 1.8446908,
      "min": 2.56376748358,
      "max": 2.88572451758,
      "times": [
        2.61581492158,
        2.56398671858,
        2.63381952658,
        2.88572451758,
        2.56376748358,
        2.78390103958,
        2.57284904258,
        2.78175601058,
        2.67541394158,
        2.77137628358
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 467.2 ± 5.4 458.4 474.0 1.00
pacquet@main 482.6 ± 14.1 466.7 513.8 1.03 ± 0.03
pnpr@HEAD 550.7 ± 104.2 481.9 758.5 1.18 ± 0.22
pnpr@main 504.3 ± 13.6 486.2 532.1 1.08 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.46717864016000005,
      "stddev": 0.005389466998655165,
      "median": 0.4679907078600001,
      "user": 0.37421995999999996,
      "system": 0.77918126,
      "min": 0.45841776286,
      "max": 0.47397616186,
      "times": [
        0.46914151886000005,
        0.46652882886,
        0.46683989686000005,
        0.45864525886000007,
        0.45841776286,
        0.46429341886000003,
        0.47157700186000007,
        0.47031577486000004,
        0.47205077786000005,
        0.47397616186
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.48262665286000006,
      "stddev": 0.014141719941792516,
      "median": 0.4817822138600001,
      "user": 0.38343156,
      "system": 0.78372766,
      "min": 0.46668340086000004,
      "max": 0.51381761386,
      "times": [
        0.51381761386,
        0.47547922386,
        0.49412525486000003,
        0.48276678986000005,
        0.48652067686000006,
        0.48684507086000006,
        0.48079763786000007,
        0.46691172786000007,
        0.47231913186,
        0.46668340086000004
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.5507436175600001,
      "stddev": 0.10415855091342356,
      "median": 0.49637608686,
      "user": 0.39117166000000003,
      "system": 0.79283046,
      "min": 0.48190139486000005,
      "max": 0.7585019878600001,
      "times": [
        0.49136478986000004,
        0.49410398586000004,
        0.52613850386,
        0.49205945986000005,
        0.49864818786000004,
        0.48190139486000005,
        0.7585019878600001,
        0.73157544886,
        0.5411223218600001,
        0.49202009486000003
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.50431486226,
      "stddev": 0.013591432365786964,
      "median": 0.50312823586,
      "user": 0.39078685999999996,
      "system": 0.79480756,
      "min": 0.48618494786000005,
      "max": 0.53205694786,
      "times": [
        0.50500331186,
        0.51199073486,
        0.53205694786,
        0.5030921808600001,
        0.49848680986000005,
        0.49087895886000005,
        0.48618494786000005,
        0.5179947838600001,
        0.50316429086,
        0.49429565586000007
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.573 ± 0.028 4.525 4.605 1.50 ± 0.08
pacquet@main 4.603 ± 0.037 4.528 4.651 1.51 ± 0.08
pnpr@HEAD 3.041 ± 0.156 2.822 3.291 1.00
pnpr@main 3.118 ± 0.083 3.012 3.262 1.03 ± 0.06
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.572638960480001,
      "stddev": 0.028222241784826702,
      "median": 4.57866007028,
      "user": 4.1407922,
      "system": 2.10669308,
      "min": 4.52537453828,
      "max": 4.60526496428,
      "times": [
        4.60526496428,
        4.53459193728,
        4.60124746428,
        4.55504946928,
        4.52537453828,
        4.5941465282800005,
        4.59516379728,
        4.58338302628,
        4.57393711428,
        4.55823076528
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.60294087918,
      "stddev": 0.03694440202885979,
      "median": 4.60249315128,
      "user": 4.1689612,
      "system": 2.0991494799999995,
      "min": 4.52829531128,
      "max": 4.65067539128,
      "times": [
        4.60354409028,
        4.52829531128,
        4.60144221228,
        4.62696766928,
        4.56048143728,
        4.63392198928,
        4.59019219128,
        4.65067539128,
        4.60057037728,
        4.63331812228
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 3.04138917098,
      "stddev": 0.1559610096950626,
      "median": 3.0488560207799997,
      "user": 3.1196079,
      "system": 1.95139088,
      "min": 2.82171180828,
      "max": 3.29146374328,
      "times": [
        3.02205933628,
        3.22024178228,
        2.86273351128,
        2.89980997328,
        2.96750369128,
        3.16325179128,
        3.29146374328,
        3.07565270528,
        3.08946336728,
        2.82171180828
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 3.11753402358,
      "stddev": 0.08330417949297174,
      "median": 3.1070207717800002,
      "user": 3.152282,
      "system": 1.99138408,
      "min": 3.01191053228,
      "max": 3.2617328632800002,
      "times": [
        3.03471200228,
        3.06572733828,
        3.2617328632800002,
        3.01191053228,
        3.21397234328,
        3.15451637028,
        3.14831420528,
        3.05962761728,
        3.0579057992800003,
        3.16692116428
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.162 ± 0.007 1.154 1.174 1.00
pacquet@main 1.191 ± 0.030 1.163 1.271 1.02 ± 0.03
pnpr@HEAD 1.270 ± 0.031 1.235 1.352 1.09 ± 0.03
pnpr@main 1.267 ± 0.029 1.244 1.344 1.09 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.16216958934,
      "stddev": 0.007294062230137246,
      "median": 1.16176860674,
      "user": 1.32895618,
      "system": 0.99932316,
      "min": 1.1539191597399998,
      "max": 1.17442052474,
      "times": [
        1.15843292374,
        1.15553790874,
        1.15569664274,
        1.16510428974,
        1.1539191597399998,
        1.17442052474,
        1.16983710174,
        1.16540460674,
        1.1552499507399998,
        1.16809278474
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.1907129164399997,
      "stddev": 0.029828340165716193,
      "median": 1.1856789612399998,
      "user": 1.3616857800000002,
      "system": 1.0252956599999998,
      "min": 1.1633904707399998,
      "max": 1.27118523774,
      "times": [
        1.1916543207399999,
        1.17091071974,
        1.18342204774,
        1.19248550374,
        1.1633904707399998,
        1.18793587474,
        1.27118523774,
        1.19032394174,
        1.17828503074,
        1.17753601674
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.2701999501399999,
      "stddev": 0.03067899566143035,
      "median": 1.26620158274,
      "user": 0.5686285799999998,
      "system": 0.94170026,
      "min": 1.2352922977399998,
      "max": 1.35223836174,
      "times": [
        1.26717658174,
        1.2710945727399998,
        1.2652265837399999,
        1.26744397374,
        1.26369123074,
        1.25714019474,
        1.2687657697399999,
        1.35223836174,
        1.25392993474,
        1.2352922977399998
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 1.2667068689399998,
      "stddev": 0.029217496993396208,
      "median": 1.25547943274,
      "user": 0.58211578,
      "system": 0.92733566,
      "min": 1.24430904574,
      "max": 1.34406848974,
      "times": [
        1.27285098474,
        1.27219041874,
        1.24430904574,
        1.24910345474,
        1.34406848974,
        1.25337804874,
        1.27253741974,
        1.25026652474,
        1.25758081674,
        1.25078348574
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.856 ± 0.018 2.835 2.883 2.29 ± 0.04
pacquet@main 2.864 ± 0.038 2.798 2.945 2.30 ± 0.05
pnpr@HEAD 1.246 ± 0.020 1.222 1.291 1.00
pnpr@main 1.275 ± 0.075 1.217 1.483 1.02 ± 0.06
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.8560379334799997,
      "stddev": 0.0180669815772258,
      "median": 2.8469383087799995,
      "user": 1.81970992,
      "system": 1.2155314,
      "min": 2.83548129378,
      "max": 2.88316795978,
      "times": [
        2.84711476578,
        2.87452021678,
        2.83548129378,
        2.84331345478,
        2.86102541178,
        2.88316795978,
        2.88309968078,
        2.8467618517799997,
        2.84518487178,
        2.84070982778
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.86350872008,
      "stddev": 0.038312883767762214,
      "median": 2.85711376428,
      "user": 1.8403183200000002,
      "system": 1.2138078,
      "min": 2.79826782078,
      "max": 2.94522790878,
      "times": [
        2.85153856878,
        2.83265913878,
        2.88727045978,
        2.85649510878,
        2.79826782078,
        2.85773241978,
        2.85135980578,
        2.8752550117799998,
        2.87928095778,
        2.94522790878
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.24577843868,
      "stddev": 0.02004338000189225,
      "median": 1.24419524278,
      "user": 0.5639707199999999,
      "system": 0.9284816000000001,
      "min": 1.22174664678,
      "max": 1.29122857678,
      "times": [
        1.2520121107800002,
        1.2500170887800002,
        1.22174664678,
        1.22716232178,
        1.24492542078,
        1.26061781278,
        1.29122857678,
        1.2372752477800002,
        1.22933409578,
        1.24346506478
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 1.2747315779800001,
      "stddev": 0.07518041745490753,
      "median": 1.25352221028,
      "user": 0.57423942,
      "system": 0.9285940000000001,
      "min": 1.2172435807800002,
      "max": 1.4829446987800001,
      "times": [
        1.2779255707800001,
        1.2544390847800002,
        1.24650532078,
        1.23946488778,
        1.25260533578,
        1.2557604017800001,
        1.2172435807800002,
        1.4829446987800001,
        1.27566995078,
        1.24475694778
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12559
Testbedpacquet
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
4,572.64 ms
(-4.77%)Baseline: 4,801.62 ms
5,761.94 ms
(79.36%)
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
🚷 view threshold
2,856.04 ms
(-6.29%)Baseline: 3,047.90 ms
3,657.48 ms
(78.09%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,162.17 ms
(-14.55%)Baseline: 1,360.01 ms
1,632.01 ms
(71.21%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
4,537.35 ms
(-4.03%)Baseline: 4,728.02 ms
5,673.62 ms
(79.97%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
467.18 ms
(-26.94%)Baseline: 639.48 ms
767.38 ms
(60.88%)
🐰 View full continuous benchmarking report in Bencher

@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12559
Testbedpnpr

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
BenchmarkLatencymilliseconds (ms)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
3,041.39 ms
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
1,245.78 ms
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
1,270.20 ms
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
2,780.58 ms
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
550.74 ms
🐰 View full continuous benchmarking report in Bencher

aqeelat added a commit to aqeelat/pnpm that referenced this pull request Jun 21, 2026
CodeRabbit/Codecov flagged 8 uncovered lines in npmrc_auth.rs on PR
pnpm#12559. Two targeted tests close the gap:

- json_env_warns_when_a_registry_url_is_not_a_string: exercises the
  per-scope non-string value branch (lines 268-271) — e.g.
  `{"registries":{"@bad":123}}`. Confirms the warning fires and a
  neighboring valid scope still parses.

- json_type_label_names_every_variant: direct unit test of the pure
  helper across all six serde_json::Value variants (covers lines 1051,
  1052, 1053, 1056 — Null/Bool/Number/Object). from_json_env only
  reaches the helper on the non-Object error path, so the Null/Bool/
  Number arms never got hit through integration tests; Object is
  structurally unreachable from current callers (they only call the
  helper after an Object check failed) but kept for exhaustive match.

Also fixes a clippy unnecessary_get_then_check warning introduced by
the first test (.get().is_none() -> !.contains_key()).

Patch coverage for npmrc_auth.rs is now 100%; the remaining uncovered
lines in the file are pre-existing code outside this PR's diff.
aqeelat added a commit to aqeelat/pnpm that referenced this pull request Jun 21, 2026
…eset

Code review flagged two minor concerns on PR pnpm#12559:

1. **Asymmetric non-string handling**: `pnpm_config_auth.registries[scope]`
   pushed a warning when the value wasn't a string, but
   `pnpm_config_auth.auth[key]` silently dropped it. Same asymmetry on
   both sides (TS + Rust). A user typo'ing
   `{"auth":{"//host/:_authToken":123}}` got zero feedback.

2. **Changeset gap**: the release note didn't mention that
   `registries.default` updates the top-level default registry (not just
   `registries["default"]`). This is a real behavior extension — the
   existing URL-scoped env vars don't expose it — and users reading the
   release notes wouldn't know they can override the default registry
   via the JSON envelope.

Fixes:
- TS (`loadNpmrcFiles.ts`): push a per-key "value must be a string"
  warning in `readJsonAuthEnvAuth`, matching the registries-side
  behavior. Kept the tokenHelper and `//`-prefix filters above the
  type check so intentional drops stay silent.
- Rust (`npmrc_auth.rs`): same warning in `from_json_env`'s auth
  walker.
- Tests: renamed `json_env_ignores_non_string_auth_value` (Rust) to
  `json_env_warns_when_an_auth_value_is_not_a_string` and tightened
  assertions to check the warning. New TS test
  `a non-string value inside pnpm_config_auth.auth warns and is dropped`.
- Changeset: one sentence noting that `registries.default` updates the
  top-level default registry, mirroring
  `pnpm-workspace.yaml`'s `registries.default`.
@github-actions github-actions Bot added the reviewed: coderabbit CodeRabbit submitted an approving review label Jun 21, 2026
Comment thread pnpm11/config/reader/src/index.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6953236

1 similar comment
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6953236

aqeelat added a commit to aqeelat/pnpm that referenced this pull request Jun 21, 2026
…warn on inert :registry keys

Two review-driven fixes on PR pnpm#12559:

1. **packageManagerRegistries gap**: `pnpm_config_auth.registries` was
   applied only to `pnpmConfig.registries` (used by normal installs),
   not to `pnpmConfig.packageManagerRegistries` (the separate map pnpm
   uses when bootstrapping itself — self-download / version switching).
   The bootstrap map is built from `trustedAuthConfig`, which doesn't
   carry the env JSON `registries` sub-field. Result: a JSON env
   registry routing influenced normal installs but silently missed the
   bootstrap path.

   Fix: after the main `pnpmConfig.registries` merge, fold env JSON
   registries into `packageManagerRegistries` too (URL-normalized).
   `cliScopedRegistries` re-applied last to preserve the same
   "CLI > env" precedence.

   Test: `pnpm_config_auth.registries flows into packageManagerRegistries
   (bootstrap path)`.

2. **Inert `//host/:registry` keys**: my filter admitted any `//`-prefixed
   key in `auth`, so `//host/:registry` passed through into `authConfig`
   and sat there — `getNetworkConfigs` only processes `@scope:registry`
   keys, not URL-scoped `:registry`. Now warns and redirects the user to
   the `registries` sub-field with a real scope. Applied on both sides
   for parity.

   Tests: TS `a URL-scoped :registry key inside pnpm_config_auth.auth
   warns and is dropped` + Rust equivalent.

Also reverted the earlier defensive switch of `cliScopedRegistries`
iteration from raw `cliOptions` to `explicitlySetKeys`. `explicitlySetKeys`
is built from `configFromCliOpts` which camelcases keys — `@org-a:registry`
becomes `@orgA:registry` and the scoped-registry pattern no longer
matches. The existing test `CLI --@scope:registry overrides pnpm_config_auth`
caught this immediately. Raw `cliOptions` iteration is correct (and
matches how `cliOptions` is used elsewhere in `index.ts`); a comment now
documents why `explicitlySetKeys` isn't suitable here.
Comment thread pacquet/crates/config/src/npmrc_auth.rs Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit de06ec0

1 similar comment
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit de06ec0

aqeelat added a commit to aqeelat/pnpm that referenced this pull request Jun 22, 2026
Both failures on PR pnpm#12559 are environmental:

- Rust CI / Test / macos: install step failed fetching ts-toolbelt from
  npmjs.org (network/registry flake before any test ran).
- TS CI / Test / windows / Node 22: EBUSY file-lock during cleanup of a
  dlx e2e test in node_modules. Known intermittent on Windows.

Same test suites passed on ubuntu (Rust + TS) and Rust/windows, so this
is not a code regression. Pushing an empty commit to trigger fresh runs.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 16b2bfa

zkochan pushed a commit to aqeelat/pnpm that referenced this pull request Jun 25, 2026
Adds two tests for branches that llvm-cov flagged as untested in PR pnpm#12559:

- json_env_warns_when_host_key_has_non_http_scheme: a pnpm_config_auth key
  with a non-http(s) scheme (ftp://) hits the scheme-check rejection in
  is_valid_json_auth_registry_url, distinct from the missing-separator
  path already covered.
- env_registry_override_appends_missing_trailing_slash: a PNPM_CONFIG_REGISTRY
  value without a trailing slash exercises the format! normalization arm
  in Config::load, asserting config.registry, config.registries["default"],
  and package_manager_bootstrap.registries["default"] all receive the
  normalized form.
@zkochan zkochan force-pushed the feat/json-env-auth branch from 16b2bfa to 976f449 Compare June 25, 2026 07:45
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 976f449

1 similar comment
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 976f449

Add an `_auth` setting that carries registry authentication as one
structured, URL-keyed value instead of many `//host/:_authToken=…` keys.

GitHub Actions silently drops env vars whose names contain `/`, `:`, or `.`
(and bash/zsh reject them outright), which broke the
`pnpm_config_//host/:_authToken=…` form on CI (pnpm#12314). The
`pnpm_config__auth` env var — the env projection of the `_auth` setting —
sidesteps the runner-level filter while preserving host scoping. The same
value can be set in the global pnpm config.yaml as a native YAML map.

Design:
- `_auth` is honored only from the env var and the global config.yaml —
  never a project pnpm-workspace.yaml / .npmrc — so repo-controlled config
  can never supply registry auth. The env var wins over the global config
  on a conflicting key.
- The value is host-keyed: `{ "https://registry.example": { "@": {...},
  "@org": {...} } }`. The host and credential arrive in one trusted value,
  so the same entry is a safe source of registry routing — repo config
  cannot redirect an env token to a different host. Routes inferred from
  `_auth` are applied after workspace yaml (env/global wins over repo
  config) and before PNPM_CONFIG_* so an explicit registry override still
  wins — matching pnpm's "CLI > _auth > yaml" precedence.
- Reuses npm's deprecated `_auth` key name on purpose: pnpm reads the
  structured form only from its own yaml (and the env projection), so it
  never collides with npm's legacy `.npmrc` `_auth` scalar, and it is
  already covered by the existing protected-settings redaction (prints as
  `(protected)`).
- Only `authToken` is accepted (maps byte-for-byte to the existing
  `//host/:_authToken` .npmrc key), so the parser is a thin adapter onto
  the existing auth pipeline. The deprecated `basicAuth` / `username` +
  `password` forms and `tokenHelper` are excluded — dropped with a warning.
- Malformed values, non-object shapes, non-URL keys, non-http(s) schemes,
  URLs carrying credentials/query/fragment, invalid scopes, and unsupported
  auth fields each push a distinct warning and are skipped without aborting
  the load. Warnings never leak raw URL credentials.

Parity: implemented on both sides (TS CLI + pacquet). Both support the
single credential field `authToken`.

Closes pnpm#12314.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
@zkochan zkochan force-pushed the feat/json-env-auth branch from 976f449 to 36dda4f Compare June 25, 2026 08:06
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 36dda4f

1 similar comment
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 36dda4f

@zkochan zkochan enabled auto-merge June 25, 2026 09:15
@zkochan zkochan added this pull request to the merge queue Jun 25, 2026
@KSXGitHub KSXGitHub removed this pull request from the merge queue due to a manual request Jun 25, 2026
@KSXGitHub KSXGitHub marked this pull request as draft June 25, 2026 09:18

@KSXGitHub KSXGitHub left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some requests/suggestions.

Some of them are about styling.

Others are about design decisions.

Comment thread pacquet/crates/config/src/lib.rs Outdated
Comment thread pacquet/crates/config/src/lib.rs Outdated
Comment thread pacquet/crates/config/src/lib.rs Outdated
Comment thread pacquet/crates/config/src/npmrc_auth.rs
Comment thread pacquet/crates/config/src/npmrc_auth.rs Outdated
Comment thread pacquet/crates/config/src/npmrc_auth/tests.rs Outdated
Comment thread pacquet/crates/config/src/npmrc_auth.rs Outdated
Comment thread pacquet/crates/config/src/npmrc_auth.rs Outdated
Comment thread pacquet/crates/config/src/workspace_yaml.rs
…nput

Per review, `_auth` now parses strictly: a malformed value (bad JSON,
wrong shape, invalid registry URL or scope, an unsupported credential
field, a missing/non-string `authToken`) is a hard error instead of a
warn-and-skip. Both sources are user-controlled, so a typo surfaces
immediately rather than silently dropping auth.

pacquet: model the value as `serde::Deserialize` types — a `url::Url`-
validated registry key, a scope enum, and a creds struct with
`deny_unknown_fields` — which replaces the hand-rolled JSON validation.
Errors never echo the raw URL key (it can embed secrets). `from_json_sources`
returns `Result`, surfaced as `LoadWorkspaceYamlError::InvalidJsonAuth`.

TS: `parseJsonAuth` throws `PnpmError('INVALID_AUTH_SETTING')` on any
malformed entry; `_auth` is still read only from the env var and global
config (project files never reach the strict parser).

Also applies the review's style fixes: pipe-trait the `from_json_sources`
call, import `NpmrcAuth`, and extract the env-json gate into a variable.
@zkochan

zkochan commented Jun 25, 2026

Copy link
Copy Markdown
Member

Thanks for the review @KSXGitHub — addressed in 47a4a28d43.

Design: error-tolerant → strict. Agreed, and switched both stacks to fail-fast. A malformed _auth (bad JSON, wrong shape, invalid registry URL/scope, an unsupported field, a missing/non-string authToken) is now a hard error instead of warn-and-skip.

  • pacquet now models the value with serde::Deserialize types: a url::Url-validated registry key, a scope enum, and a creds struct with #[serde(deny_unknown_fields)]. This removed the hand-rolled JSON validation entirely (net −800 lines across the tests). from_json_sources returns Result, surfaced as LoadWorkspaceYamlError::InvalidJsonAuth.
  • TS parseJsonAuth throws PnpmError('INVALID_AUTH_SETTING').

url crate. Done — the registry key is parsed/validated with url::Url (which also handles scheme/host case-folding and default-port stripping like the TS new URL()), and the parsed result is the map key, as you suggested.

Secret-safe errors. The error messages never echo the raw URL key (it can carry userinfo/query secrets) — they use a credential-free scheme://host[:port] label, with regression tests asserting no leak.

Style fixes: from_json_sources is now invoked via pipe-trait, NpmrcAuth is imported (no more qualified path), and the env-json gate is extracted into a variable.

Rationale in doc comments / warning-message format: both moot now — the verbose rationale was already moved to the changeset, and the warning-message concern disappears with strict parsing (no warnings; serde/PnpmError produce the message).


Written by an agent (Claude Code, claude-opus-4-8).

@zkochan zkochan marked this pull request as ready for review June 25, 2026 10:21
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 47a4a28

@zkochan zkochan requested a review from KSXGitHub June 25, 2026 10:32
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 47a4a28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pnpm11/config/reader/src/loadNpmrcFiles.ts (1)

276-276: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use an options object for jsonAuthToken context.

jsonAuthToken now takes 4 positional arguments, which violates the repo’s TypeScript convention to keep functions to 2–3 args and use an options object beyond that.

Suggested refactor
-      const token = jsonAuthToken(rawCreds as Record<string, unknown>, registry, scope, source)
+      const token = jsonAuthToken(rawCreds as Record<string, unknown>, { registry, scope, source })
 function jsonAuthToken (
   creds: Record<string, unknown>,
-  registry: JsonAuthRegistry,
-  scope: string,
-  source: string
+  opts: { registry: JsonAuthRegistry, scope: string, source: string }
 ): string {
+  const { registry, scope, source } = opts

Also applies to: 348-353

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pnpm11/config/reader/src/loadNpmrcFiles.ts` at line 276, The `jsonAuthToken`
call in `loadNpmrcFiles` is using 4 positional arguments, which breaks the
repo’s convention; update the `jsonAuthToken` API and its call sites to pass the
context as a single options object instead of separate `registry`, `scope`, and
`source` parameters. Make the change in the `jsonAuthToken` usage(s) referenced
by `loadNpmrcFiles` so the function signature stays within the 2–3 argument
guideline while preserving the same values in the options object.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pacquet/crates/config/src/npmrc_auth.rs`:
- Around line 255-273: The auth/env route precedence in apply_json_auth is being
altered by BTreeMap-style reordering, so duplicate "@"/@scope entries do not
follow pnpm’s “last entry wins” behavior. Update the parsed JSON auth
representation and iteration so the outer registry entries preserve their
original order (instead of sorting) before apply_json_auth processes parsed.0,
ensuring json_env_registries and the creds updates reflect pnpm’s exact
precedence semantics for duplicate routes.

---

Nitpick comments:
In `@pnpm11/config/reader/src/loadNpmrcFiles.ts`:
- Line 276: The `jsonAuthToken` call in `loadNpmrcFiles` is using 4 positional
arguments, which breaks the repo’s convention; update the `jsonAuthToken` API
and its call sites to pass the context as a single options object instead of
separate `registry`, `scope`, and `source` parameters. Make the change in the
`jsonAuthToken` usage(s) referenced by `loadNpmrcFiles` so the function
signature stays within the 2–3 argument guideline while preserving the same
values in the options object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d2d7f0d-b16a-4e56-a010-85dff4ba950b

📥 Commits

Reviewing files that changed from the base of the PR and between ea1abfc and 47a4a28.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (12)
  • .changeset/json-env-auth.md
  • pacquet/crates/cli/src/config_overrides.rs
  • pacquet/crates/cli/src/config_overrides/tests.rs
  • pacquet/crates/config/Cargo.toml
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/config/src/npmrc_auth.rs
  • pacquet/crates/config/src/npmrc_auth/tests.rs
  • pacquet/crates/config/src/tests.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pnpm11/config/reader/src/index.ts
  • pnpm11/config/reader/src/loadNpmrcFiles.ts
  • pnpm11/config/reader/test/index.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/cli/src/config_overrides.rs
  • pacquet/crates/config/Cargo.toml
  • pacquet/crates/cli/src/config_overrides/tests.rs
  • pnpm11/config/reader/src/index.ts
  • pacquet/crates/config/src/lib.rs

Comment thread pacquet/crates/config/src/npmrc_auth.rs
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Looking for bugs?

Check back in a few minutes. Qodo's review agents are on it.

@zkochan zkochan enabled auto-merge June 25, 2026 11:34
@zkochan zkochan disabled auto-merge June 25, 2026 12:25
@zkochan zkochan merged commit 3425e80 into pnpm:main Jun 25, 2026
32 of 35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't auth to npmjs any more

5 participants