Skip to content

fix(config/reader): pin unscoped per-registry settings to their source's registry at load time#11953

Merged
zkochan merged 12 commits into
mainfrom
vuln1
May 26, 2026
Merged

fix(config/reader): pin unscoped per-registry settings to their source's registry at load time#11953
zkochan merged 12 commits into
mainfrom
vuln1

Conversation

@zkochan

@zkochan zkochan commented May 26, 2026

Copy link
Copy Markdown
Member

Summary

Each .npmrc, auth.ini, and CLI source's unscoped per-registry settings (_authToken, _auth, username, _password, tokenHelper, cert, key) are rewritten to their URL-scoped equivalent during load, using the same source's registry= value — or the npmjs default if the source declares none. After this rewrite the merged config contains only URL-scoped settings, so a later layer overriding registry= cannot rebind a credential or a client certificate to its own registry.

Every rescope emits a deprecation warning telling the user where the setting was pinned and how to write it directly:

[WARN] Unscoped per-registry settings (_authToken) in "~/.npmrc" are deprecated.
       pnpm pinned them to "//registry.example.com/" for this run, but a future
       release will stop supporting unscoped per-registry settings. Write them as
       "//registry.example.com/:_authToken=..." instead.

npm has rejected unscoped credentials outright since npm@9 (ERR_INVALID_AUTH); pnpm will catch up in a future major.

Threat the change closes

A project-local .npmrc (or pnpm-workspace.yaml, or a CLI flag) that overrides registry= would cause pnpm to send user-trusted credentials (Authorization: Bearer <user-secret>) or a client TLS cert/key to the new registry, even though the user had written them into ~/.npmrc (or ~/.config/pnpm/auth.ini) for a different destination.

With this PR, those user-level settings are pinned at load time to whichever registry the file named (or to the npmjs default if it named none). The workspace's registry= override changes where the merged registry resolves, but does not move the setting.

What gets rescoped

Key Rescoped? Why
_authToken, _auth, username, _password, tokenHelper Yes Auth credentials — primary vector
cert, key (inline PEM) Yes Client TLS credentials — identity leak risk
ca, cafile No Trust anchors, not credentials. Corporate MITM-proxy setups rely on global trust adjustments applying to every HTTPS request. The default-registry override can't weaponize an unscoped CA (attacker needs a cert signed by it)
certfile, keyfile (file-path variants) No certfile isn't read unscoped today; supporting only keyfile would be asymmetric. Users wanting the path form can write //host/:certfile=... directly
strict-ssl No Not URL-scopable in npm's format. Downgrade-attack vector but needs a different fix

Why this shape

A prior version of this fix lived post-merge in getConfig and used a source-tracking helper (workspaceRegistryRebindsUserCreds) to detect when a workspace override would rebind user-level credentials, then dropped the binding. That worked but carried two complications: the helper had to reason about three source maps (userConfig, pnpmAuthConfig, workspaceNpmrc), and it had to honor an explicitlySetKeys.has('registry') carve-out so the CLI's --registry could still pull an unscoped token along.

The load-time rescope is simpler:

  • Each source is self-contained. Read it, look at its registry= value (or fall back to npmjs default), rewrite its unscoped per-registry keys, done.
  • getConfig no longer needs the helper at all — getNetworkConfigs naturally picks up the URL-scoped form.
  • The CLI carve-out goes away. If a user runs pnpm install --registry=Y while ~/.npmrc has an unscoped _authToken=T (no registry=), T is pinned to the npmjs default at load and is NOT sent to Y. Per the discussion on this PR, this is the correct behavior, not a regression: an unscoped credential is ambient; it shouldn't follow whatever registry the CLI happens to point at. Users who want T sent to Y should write //Y/:_authToken=T.

Follow-on cleanup: the empty-string configByUri[''] placeholder slot (the previous "default registry" re-keying target) is now structurally unreachable, so the runtime fallback in getAuthHeadersFromCreds and publishPackedPkg.ts is removed, along with the now-unused defaultRegistry parameter on createGetAuthHeaderByURI and getAuthHeadersFromCreds. Bumps @pnpm/network.auth-header major.

Implementation

config/reader/src/loadNpmrcFiles.ts:

  • New rescopeUnscopedCreds(source, sourceLabel, warnings) helper. For each unscoped key in source, it computes the nerf-darted form of source.registry (or npmDefaults.registry), copies the value to <nerf>:<key> unless an explicit scoped value is already there, deletes the unscoped key, and emits a deprecation warning naming the source.
  • readAndFilterNpmrc runs the rescope on every file (workspace .npmrc, user .npmrc, auth.ini, pnpmrc builtin).
  • opts.cliOptions is cloned and rescoped before it joins the merge.
  • Adds dep on @pnpm/config.nerf-dart.

config/reader/src/index.ts:

  • Removed workspaceRegistryRebindsUserCreds, UNSCOPED_CRED_KEYS, and the getDefaultCreds call.

network/auth-header/src/getAuthHeadersFromConfig.ts and network/auth-header/src/index.ts:

  • Dropped empty-string handling and the defaultRegistry parameter.

releasing/commands/src/publish/publishPackedPkg.ts: removed the creds ??= configByUri['']?.creds fallback.

All ~16 call sites of createGetAuthHeaderByURI updated to drop the now-unused second argument.

Pacquet

Pacquet has the same logical sink but only reads one .npmrc at a time (no merge), so it isn't vulnerable today. The day pacquet adds layered .npmrc support, the equivalent load-time rescope should land in the same change. Tracked in #11950.

Test plan

  • JUNYI repro on main (pnpm v11.3.0): 3 attacker auth hits.
  • After this PR: 0 attacker auth hits; deprecation warning shown naming the source and the registry the setting was pinned to; install still succeeds.
  • Positive scenarios still work:
    • User _authToken + same-file registry=trusted, no workspace .npmrc → token sent to trusted.
    • Workspace .npmrc re-declares same registry → token sent to that registry.
    • Workspace declares its own registry + _authToken → workspace token sent to workspace registry.
    • URL-scoped //host/:_authToken= passes through unchanged with no deprecation warning.
    • Corporate cafile=/path/to/corp-ca.pem still applies globally (CA is not rescoped).
  • 13 new unit tests in config/reader/test/index.ts:
    • 9 cover the rescope behavior including the auth.ini-split-from-registry case (pins to npmjs default, not to user-level registry), the CLI override case (pins to npmjs default, not to --registry), and the inline cert/key case.
    • 4 cover the deprecation warning.
  • auth-header tests pass (20/20 — dropped the dedicated empty-key test).
  • cspell green.
  • CI green.

Behavior changes (intentional)

Two cases that previously bound an unscoped user-level credential to a lower-trust registry now don't. Both are documented in the deprecation warning; users get a concrete suggestion on how to write the credential URL-scoped.

Scenario Before this PR After this PR
~/.npmrc has registry=trusted only; auth.ini has unscoped _authToken=T T sent to trusted (merged registry from ~/.npmrc) T pinned to npmjs default at auth.ini load; not sent to trusted
~/.npmrc has unscoped _authToken=T only; CLI --registry=Y T sent to Y T pinned to npmjs default; not sent to Y

The fix path is the same for both: write the credential URL-scoped (//that-registry/:_authToken=T) in either of the user-level files.

Note on disclosure

This is a public draft because the reporter's repro and the fix are already shareable. Six unrelated tests in config/reader/test/index.ts fail identically on main without this PR — they appear to leak the maintainer's real ~/.config/pnpm/auth.ini into URL-scoped-token assertions; not addressed here.


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

Summary by CodeRabbit

  • Bug Fixes

    • Prevented credential disclosure: unscoped credentials (including CLI-provided ones and inline TLS cert/key) are pinned to the registry where they were defined; CA/cafile excluded. Later registry overrides can no longer redirect them.
    • Unscoped auth fields emit deprecation warnings and are treated as deprecated/rejected.
  • Tests

    • Added tests validating registry pinning and deprecation warning behavior.
  • Documentation

    • Updated release notes and migration guidance.

Review Change Stack

…ides registry

When a workspace `.npmrc` overrides `registry=` to a different value than the
user's `~/.npmrc` or `~/.config/pnpm/auth.ini` would have set, do not bind
unscoped/default credentials (`_authToken`, `_auth`, `username`/`_password`)
from the user-level config to the workspace-selected registry. The previous
behavior leaked user-trusted credentials to whatever registry an untrusted
workspace `.npmrc` pointed at. Reported by JUNYI LIU.
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

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
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR rescopes unscoped npm auth/TLS keys to URL-scoped form at config load time (pinned to the declaring source’s registry), removes the default-registry placeholder from auth-header construction, and updates callers and tests accordingly.

Changes

Credential Rebind Defense

Layer / File(s) Summary
Release documentation and dependency setup
.changeset/credential-rebind-defense.md, config/reader/package.json, cspell.json
Patch release note documents credential-rebind fix and API change; adds @pnpm/config.nerf-dart dependency and expands cspell allowlist.
Credential rescoping implementation
config/reader/src/loadNpmrcFiles.ts
Imports normalize/nerf utilities, defines UNSCOPED_RESCOPABLE_KEYS, clones and rescopes CLI options, and adds rescopeUnscopedCreds which rewrites eligible unscoped auth and inline cert/key into nerfed URL-scoped keys, deletes originals, and records deprecation warnings.
Configuration reader integration
config/reader/src/index.ts, config/reader/src/loadNpmrcFiles.ts, config/reader/src/localConfig.ts
index.ts no longer augments configByUri with default creds under ''; pnpmConfig.configByUri comes from networkConfigs.configByUri. The rescoped cliOptions is threaded through CA loading, merged/raw config construction, and NPM_AUTH_SETTINGS was adjusted.
Registry pinning and deprecation warning tests
config/reader/test/index.ts
Adds Jest suites that assert unscoped credentials are pinned to source registry (with npmjs default fallback when needed) and that deprecation warnings are emitted for unscoped auth fields (including source path), while URL-scoped keys do not trigger warnings.
Auth-header API and callers
network/auth-header/src/*, network/auth-header/test/*, multiple callers under registry-access/, installing/, installing/env-installer/, deps/, releasing/, resolving/
Removes the optional defaultRegistry parameter from createGetAuthHeaderByURI and getAuthHeadersFromCreds; tests updated. Call sites now call the factory with only configByUri and invoke the returned getter with registry URIs when needed. releasing removed fallback that read creds from configByUri[''] for the default registry.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • pnpm/pnpm#11471: Related — both PRs modify config/reader/src/index.ts handling around default credentials.
  • pnpm/pnpm#11726: Related — adjacent changes around .npmrc loading and cafile handling.
  • pnpm/pnpm#11405: Related — overlaps in audit/signatures auth-header construction changes.

Poem

"I sniffed through .npmrc leaves and found loose strings,
I bound each token where its source bell rings.
No secret slip, no sly redirect to roam,
Each key now nestles safe where it was sown.
🥕 — hop, guard, and tuck them home."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: unscoped per-registry settings are pinned to their source registry at load time, which is the core objective of this security-focused PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vuln1

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 and usage tips.

zkochan added 3 commits May 26, 2026 11:31
…gistry

Move the rebind defense to after all config layers (CLI, env vars,
pnpm-workspace.yaml, .npmrc) have settled. Compare the final resolved
default registry against what the user-level config alone would produce,
and skip the check entirely if the user requested a registry via CLI/env
themselves.
Emit a per-file warning whenever an .npmrc or auth.ini contains an
unscoped auth value (_authToken, _auth, username, _password,
tokenHelper). URL-scoped tokens have been npm's recommended pattern
since npm@9, and unscoped credentials are slated for removal in a
future major. The warning fires independently of whether the rebind
defense rejects the credentials, so users see the deprecation even when
their setup happens to be safe today.
@zkochan zkochan changed the title fix(config/reader): drop user-level default auth when workspace overrides registry fix(config/reader): defend unscoped auth from cross-source registry override; deprecate unscoped creds May 26, 2026
…stead of detecting rebinds post-merge

Each .npmrc / auth.ini / CLI source's unscoped credential keys
(_authToken, _auth, username, _password, tokenHelper) are rewritten to
their URL-scoped equivalent during load, using the same source's
registry= value (or the npmjs default if it declares none). A later
layer overriding registry= can no longer rebind a credential to its own
registry — the credential is already pinned to the URL its author
intended.

This removes the post-merge source-tracking defense and replaces it
with the simpler per-source normalization. Each rescope emits a
deprecation warning so users migrate to writing the URL-scoped form
directly.
@zkochan zkochan changed the title fix(config/reader): defend unscoped auth from cross-source registry override; deprecate unscoped creds fix(config/reader): pin unscoped credentials to their source's registry at load time May 26, 2026
@zkochan zkochan marked this pull request as ready for review May 26, 2026 10:37
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@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.

🧹 Nitpick comments (1)
config/reader/test/index.ts (1)

857-1040: 💤 Low value

Consider adding test coverage for tokenHelper rescoping.

The UNSCOPED_CRED_KEYS constant includes tokenHelper, but the new test suite only exercises _authToken, _auth, and username/_password. While the rescoping logic is shared, an explicit test would guard against regressions if tokenHelper handling ever diverges.

🤖 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 `@config/reader/test/index.ts` around lines 857 - 1040, Add a test in this
suite that exercises rescoping for the tokenHelper key (which is listed in
UNSCOPED_CRED_KEYS) similar to the existing tests for
_authToken/_auth/username+_password: write a user-level file containing
"tokenHelper=some-helper" (and optionally a workspace .npmrc with a different
registry), call getConfig(...) with cliOptions: { userconfig } and env including
XDG_CONFIG_HOME, then assert that config.configByUri pins the tokenHelper entry
to the source file's registry (e.g. '//registry.npmjs.org/' or the userconfig's
registry) and that it is not present under the workspace/CLI registry; reference
the test helpers getConfig and the config.configByUri map when adding the
assertion.
🤖 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.

Nitpick comments:
In `@config/reader/test/index.ts`:
- Around line 857-1040: Add a test in this suite that exercises rescoping for
the tokenHelper key (which is listed in UNSCOPED_CRED_KEYS) similar to the
existing tests for _authToken/_auth/username+_password: write a user-level file
containing "tokenHelper=some-helper" (and optionally a workspace .npmrc with a
different registry), call getConfig(...) with cliOptions: { userconfig } and env
including XDG_CONFIG_HOME, then assert that config.configByUri pins the
tokenHelper entry to the source file's registry (e.g. '//registry.npmjs.org/' or
the userconfig's registry) and that it is not present under the workspace/CLI
registry; reference the test helpers getConfig and the config.configByUri map
when adding the assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 08f11bb7-d97b-429c-aacf-f7896bf38725

📥 Commits

Reviewing files that changed from the base of the PR and between 35d2355 and 69f8548.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • .changeset/credential-rebind-defense.md
  • config/reader/package.json
  • config/reader/src/index.ts
  • config/reader/src/loadNpmrcFiles.ts
  • config/reader/test/index.ts
  • cspell.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ubuntu-latest / Node.js 24 / Test
  • GitHub Check: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Follow Standard Style with trailing commas, preferring functions over classes, and declaring functions after they are used (relying on hoisting)
Use a single options object instead of multiple parameters when a function needs more than two or three arguments
Follow Import Order: Standard libraries first, then external dependencies (alphabetically), then relative imports
Write self-documenting code where function names, parameters, and types explain what a function does without requiring prose comments
Do not write comments that restate what the code already says; refactor via renaming, splitting helpers, or restructuring instead
Do not repeat documentation at call sites that already exists in JSDoc on the callee; update JSDoc once for all call sites to benefit
Use JSDoc only for a function's contract (preconditions, postconditions, edge cases, why the function exists), not for re-narrating the body
Do not record past implementation shape, refactor history, or 'the previous code did X' framing in code; use git log and git blame instead
Write comments only when: the reason for code is non-obvious (hidden invariant, workaround for known bug, deliberate exception), or the right name doesn't fit (temporary technical constraint)

Files:

  • config/reader/src/index.ts
  • config/reader/test/index.ts
  • config/reader/src/loadNpmrcFiles.ts
🧠 Learnings (2)
📚 Learning: 2026-05-05T23:03:04.286Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11479
File: __utils__/scripts/package.json:6-9
Timestamp: 2026-05-05T23:03:04.286Z
Learning: The pattern cross-env NODE_OPTIONS="$NODE_OPTIONS ..." in package.json scripts is an established convention in the pnpm/pnpm repository and is used across many packages (e.g., fs/hard-link-dir, worker, __utils__/scripts). Do not flag this as a cross-platform issue in individual files; if a change is needed, apply it as a repo-wide change in a separate PR. Scope this guidance to all package.json files in the repo; use the minimatch pattern '**/package.json' to identify relevant files and review changes at the repository level rather than per-file.

Applied to files:

  • config/reader/package.json
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.

Applied to files:

  • config/reader/src/index.ts
  • config/reader/test/index.ts
  • config/reader/src/loadNpmrcFiles.ts
🔇 Additional comments (10)
.changeset/credential-rebind-defense.md (1)

1-11: LGTM!

config/reader/package.json (1)

40-40: LGTM!

cspell.json (1)

145-145: LGTM!

Also applies to: 289-291

config/reader/src/loadNpmrcFiles.ts (4)

6-7: LGTM!

Also applies to: 75-78, 132-139, 192-215


141-178: LGTM!


93-99: LGTM!


103-120: LGTM!

config/reader/src/index.ts (1)

41-41: LGTM!

Also applies to: 324-325

config/reader/test/index.ts (2)

857-1040: LGTM!


1042-1134: LGTM!

zkochan added 2 commits May 26, 2026 12:46
After load-time rescoping, no source can populate configByUri[''] —
every credential is either URL-scoped from the start or rewritten to
the URL-scoped form during the .npmrc / auth.ini / CLI parse. The
runtime fallback that re-keyed configByUri[''] onto the merged default
registry, and the publish-side fallback that read it, are both dead
code.

Removed:
- empty-string handling in getAuthHeadersFromCreds, including its
  defaultRegistry parameter
- defaultRegistry parameter from createGetAuthHeaderByURI
- the corresponding dedicated unit test
- the configByUri['']?.creds fallback in publishPackedPkg.ts
- empty-key assertions in config/reader tests

Updated all ~16 call sites of createGetAuthHeaderByURI to drop the now
unused second argument.
The same trust-boundary issue that affected unscoped credentials applies
to client TLS settings: an unscoped cert=/key= would be presented to
whatever registry the merged config settles on, even if a later layer
(workspace .npmrc, pnpm-workspace.yaml, CLI flag) overrode it. The
existing rescope helper now also rewrites unscoped `cert` and `key`
to their URL-scoped form, pinning them to the registry their author
named in the same source.

`ca`/`cafile` are intentionally left unscoped: they're trust anchors,
not credentials, and corporate MITM-proxy setups depend on them
applying to every HTTPS request. The default-registry override can't
weaponize an unscoped CA — the attacker would need a cert signed by it.

`certfile`/`keyfile` (file-path variants) are not rescoped either:
`certfile` isn't read unscoped by pnpm today (asymmetric vs. `keyfile`
in NPM_AUTH_SETTINGS), and supporting only one of them would be
confusing. Users wanting the path form can write it URL-scoped
directly.
@zkochan zkochan changed the title fix(config/reader): pin unscoped credentials to their source's registry at load time fix(config/reader): pin unscoped per-registry settings to their source's registry at load time May 26, 2026
zkochan added 3 commits May 26, 2026 13:03
`keyfile` was listed in NPM_AUTH_SETTINGS so unscoped `keyfile=<path>`
passed the .npmrc filter and ended up in authConfig — but nothing in
the codebase ever read it from there. The dispatcher uses `opts.key`
(inline PEM) and `configByUri[host].tls.key` (URL-scoped path/inline
content), neither of which is populated from unscoped `keyfile=`.

`certfile` was already absent from the allowlist for the same reason,
so this also removes the asymmetry between the two file-path variants.
URL-scoped `//host/:certfile=...` and `//host/:keyfile=...` continue
to work via `tryParseSslKey` and are unaffected.
This test exercised the configByUri[''] re-keying path that was
removed in the rescope-at-load refactor. With createGetAuthHeaderByURI
no longer accepting a defaultRegistry parameter and unscoped
credentials no longer reaching the merged config, the scenario the
test described is structurally unreachable.
Two CI fixes:

1. When a source's `registry=` resolves to an empty string (e.g. an
   unresolved `${ENV_VAR}` placeholder), `new URL(...)` inside
   `nerfDart` throws. Guard the call with try/catch: drop the
   unscoped per-registry keys (a bare token has nowhere safe to bind)
   and emit a warning naming the offending source.

2. Update `.npmrc does not load pnpm settings` to expect the rescoped
   form of unscoped `_authToken`/`username` in `authConfig` — they
   now appear as `//registry.npmjs.org/:_authToken` etc. since the
   test's .npmrc declares no `registry=` of its own.
@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.608 ± 0.031 1.562 1.660 1.00
pacquet@main 1.631 ± 0.071 1.542 1.762 1.01 ± 0.05
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.6084010764200003,
      "stddev": 0.031025033475340257,
      "median": 1.5955710108199999,
      "user": 2.9654172,
      "system": 2.01188738,
      "min": 1.56159312632,
      "max": 1.6595527803199999,
      "times": [
        1.5971078373199998,
        1.64328949132,
        1.5940341843199999,
        1.6343201513199999,
        1.58526918132,
        1.59353623832,
        1.56159312632,
        1.6595527803199999,
        1.62857497632,
        1.5867327973199998
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.63145189142,
      "stddev": 0.07068976166748245,
      "median": 1.64684293232,
      "user": 2.9556261,
      "system": 2.06965588,
      "min": 1.54242822832,
      "max": 1.7622597623199998,
      "times": [
        1.6612679153199998,
        1.64106132132,
        1.7622597623199998,
        1.66177972032,
        1.54242822832,
        1.6956319573199998,
        1.5690323093199998,
        1.54791296832,
        1.65262454332,
        1.58052018832
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 469.4 ± 37.4 447.2 574.6 1.00 ± 0.09
pacquet@main 468.1 ± 15.9 453.3 503.1 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.46941238502000004,
      "stddev": 0.03741102128496132,
      "median": 0.45963804302000005,
      "user": 0.3507476,
      "system": 0.79815152,
      "min": 0.44723359652000005,
      "max": 0.57462404852,
      "times": [
        0.57462404852,
        0.46478847252000005,
        0.46244865252000006,
        0.45426634252000003,
        0.46214840452000006,
        0.45712768152000005,
        0.4644066975200001,
        0.45231765252000006,
        0.44723359652000005,
        0.4547623015200001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.46810197662,
      "stddev": 0.015866199284764945,
      "median": 0.46088623452000005,
      "user": 0.35844510000000007,
      "system": 0.79511082,
      "min": 0.45329342352000007,
      "max": 0.5031256875200001,
      "times": [
        0.5031256875200001,
        0.47451144052000005,
        0.46182957052000007,
        0.46853994952000005,
        0.45329342352000007,
        0.45982710252000003,
        0.4866109035200001,
        0.45678008752000004,
        0.45655870252,
        0.45994289852000003
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.011 ± 0.022 1.977 2.041 1.00 ± 0.02
pacquet@main 2.007 ± 0.029 1.945 2.056 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.01052426682,
      "stddev": 0.02231347543830063,
      "median": 2.01052034642,
      "user": 4.1016899,
      "system": 1.93856322,
      "min": 1.97670875242,
      "max": 2.0410153744199997,
      "times": [
        1.99174447542,
        2.01162176442,
        1.99690369242,
        2.03532871742,
        1.98577812742,
        1.97670875242,
        2.02963362642,
        2.0270892094199997,
        2.0410153744199997,
        2.0094189284199997
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.0066116558199996,
      "stddev": 0.02891035265400616,
      "median": 2.01118514942,
      "user": 4.111163500000001,
      "system": 1.9431165199999998,
      "min": 1.94529370442,
      "max": 2.05611220042,
      "times": [
        1.9781146194199999,
        2.01887113142,
        2.0189433174199998,
        2.00242406742,
        2.05611220042,
        1.94529370442,
        2.0176435434199997,
        2.01405670742,
        2.0063436754199997,
        2.00831359142
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.215 ± 0.011 1.199 1.231 1.01 ± 0.03
pacquet@main 1.208 ± 0.032 1.181 1.289 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.21464340682,
      "stddev": 0.010889947712912233,
      "median": 1.21340820032,
      "user": 1.6863376999999997,
      "system": 1.14733874,
      "min": 1.19925392982,
      "max": 1.23096962582,
      "times": [
        1.23096962582,
        1.22820195682,
        1.20424958182,
        1.20164842282,
        1.22074620182,
        1.21272051682,
        1.21236884582,
        1.21409588382,
        1.22217910282,
        1.19925392982
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.2080053690199999,
      "stddev": 0.032468360693195364,
      "median": 1.20455907932,
      "user": 1.6835093,
      "system": 1.1152469399999998,
      "min": 1.18076938882,
      "max": 1.28914055682,
      "times": [
        1.2018738068200001,
        1.18156219382,
        1.18076938882,
        1.20724435182,
        1.18463397782,
        1.18320198282,
        1.21591042982,
        1.21979856382,
        1.28914055682,
        1.21591843782
      ]
    }
  ]
}

@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/11953
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
2,010.52 ms
(-37.88%)Baseline: 3,236.37 ms
3,883.65 ms
(51.77%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,214.64 ms
(-47.51%)Baseline: 2,313.90 ms
2,776.68 ms
(43.74%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
1,608.40 ms
(-24.43%)Baseline: 2,128.46 ms
2,554.15 ms
(62.97%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
469.41 ms
(-28.90%)Baseline: 660.23 ms
792.28 ms
(59.25%)
🐰 View full continuous benchmarking report in Bencher

zkochan added 2 commits May 26, 2026 15:43
This test passed credentials via the configByUri[''] empty-string slot,
which the auth-header layer re-keyed to the merged default registry at
request time. That slot was removed in the rescope-at-load refactor —
credentials are now always URL-scoped before they reach configByUri,
so the empty-key entry is unreachable from any code path.

The scenario the test covered (basicAuth via username/password) is
already exercised by the existing "installing a package that need
authentication, using password" test using the URL-scoped form.
@zkochan zkochan merged commit a23956e into main May 26, 2026
16 checks passed
@zkochan zkochan deleted the vuln1 branch May 26, 2026 14:46
zkochan added a commit that referenced this pull request May 27, 2026
Removing the unscoped credential fallback in getAuthHeadersFromConfig
is a breaking change to the package's contract — callers passing
unscoped _authToken/_auth/username+_password/tokenHelper alongside a
default registry no longer get a header back. Matches the major bump
in #11953.

---
Written by an agent (Claude Code, claude-opus-4-7).
zkochan added a commit that referenced this pull request May 27, 2026
…istry at load time

Each .npmrc / auth.ini / CLI source's unscoped per-registry settings
(_authToken, _auth, username/_password, tokenHelper, inline cert/key)
are rewritten to their URL-scoped equivalent during load, using the
same source's registry= value (or the npmjs default if none). After
this rewrite the merged config contains only URL-scoped settings, so a
later layer overriding registry= (workspace .npmrc, pnpm-workspace.yaml,
CLI --registry) cannot rebind a credential or client certificate to a
different host.

Each rescope emits a deprecation warning naming the source and the URL
the setting was pinned to. ca/cafile are intentionally not rescoped —
they're trust anchors, not credentials, and corporate MITM-proxy setups
rely on them applying globally.

Ported from #11953. Reported by JUNYI LIU.

---
Written by an agent (Claude Code, claude-opus-4-7).
zkochan added a commit that referenced this pull request May 27, 2026
Removing the unscoped credential fallback in getAuthHeadersFromConfig
is a breaking change to the package's contract — callers passing
unscoped _authToken/_auth/username+_password/tokenHelper alongside a
default registry no longer get a header back. Matches the major bump
in #11953.

---
Written by an agent (Claude Code, claude-opus-4-7).
zkochan added a commit that referenced this pull request May 27, 2026
…istry at load time (backport #11953 to v10) (#11986)

* fix(config): pin unscoped per-registry settings to their source's registry at load time

Each .npmrc / auth.ini / CLI source's unscoped per-registry settings
(_authToken, _auth, username/_password, tokenHelper, inline cert/key)
are rewritten to their URL-scoped equivalent during load, using the
same source's registry= value (or the npmjs default if none). After
this rewrite the merged config contains only URL-scoped settings, so a
later layer overriding registry= (workspace .npmrc, pnpm-workspace.yaml,
CLI --registry) cannot rebind a credential or client certificate to a
different host.

Each rescope emits a deprecation warning naming the source and the URL
the setting was pinned to. ca/cafile are intentionally not rescoped —
they're trust anchors, not credentials, and corporate MITM-proxy setups
rely on them applying globally.

Ported from #11953. Reported by JUNYI LIU.

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

* chore(changeset): bump @pnpm/network.auth-header to major

Removing the unscoped credential fallback in getAuthHeadersFromConfig
is a breaking change to the package's contract — callers passing
unscoped _authToken/_auth/username+_password/tokenHelper alongside a
default registry no longer get a header back. Matches the major bump
in #11953.

---
Written by an agent (Claude Code, claude-opus-4-7).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant