Skip to content

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

Merged
zkochan merged 2 commits into
release/10from
release/10-auth-fix
May 27, 2026
Merged

fix(config): pin unscoped per-registry settings to their source's registry at load time (backport #11953 to v10)#11986
zkochan merged 2 commits into
release/10from
release/10-auth-fix

Conversation

@zkochan

@zkochan zkochan commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

Backport of #11953 to the release/10 branch.

Each .npmrc, auth.ini, and 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.

Reported by JUNYI LIU.

Threat closed

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. After this change 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.

Differences from #11953

v10's config-loading layout differs from main, so the port is not a straight cherry-pick:

  • v10 has no separate config/reader package — the layered loader runs inside config/config via @pnpm/npm-conf. The rescoper is attached to that existing flow instead of being a property of a new package.
  • v10's createGetAuthHeaderByURI never had main's defaultRegistry parameter, so that subset of the refactor (and the related call-site updates and publishPackedPkg.ts fallback removal) doesn't apply here.
  • v10's @pnpm/npm-conf is built on config-chain + proto-list, which chains each source's data object through prototypes (list[0].__proto__ === list[1], …). key in source and source[key] therefore walk into other sources, so the rescoper uses Object.hasOwn everywhere — otherwise it would pin an inherited token to the wrong registry. This concern doesn't exist in main.
  • The legacy-auth fallback in network/auth-header/src/getAuthHeadersFromConfig.ts (which rebound unscoped _auth/_authToken/username + _password/tokenHelper to the merged registry at request time) is removed, matching main's intent — credentials always reach the merged config URL-scoped after load-time rescoping. The corresponding "legacy way" test in pkg-manager/core/test/install/auth.ts and the unscoped-fallback tests in network/auth-header/test/getAuthHeadersFromConfig.test.ts are dropped.

Test plan

  • 8 new tests in config/config/test/index.ts cover: workspace registry override (token stays on user's registry), no source-level registry (pins to npmjs default), CLI --registry (pins to npmjs default, doesn't pull token along), URL-scoped passthrough (no warning), deprecation warning shape, explicit URL-scoped key wins, inline cert/key rescoping, ca/cafile left alone.
  • All 17 network/auth-header tests pass.
  • pnpm run lint clean on changed packages.
  • cspell clean on changed files.
  • Six other tests in config/config/test/index.ts fail identically on the baseline without this PR — they leak the maintainer's real ~/.config/pnpm/{rc,auth.ini} into URL-scoped-token assertions; not addressed here.

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

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

Copy link
Copy Markdown

Review Summary by Qodo

Pin unscoped per-registry credentials to their source registry at load time

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Pin unscoped per-registry credentials to source registry at load time
• Prevents workspace registry override from redirecting user credentials
• Emits deprecation warnings for unscoped auth settings
• Removes legacy unscoped credential fallback in auth-header
Diagram
flowchart LR
  A["Config sources<br/>CLI, env, project,<br/>workspace, user, global"] -->|"Load each source"| B["rescopeUnscopedCreds<br/>function"]
  B -->|"Pin unscoped keys<br/>to source registry"| C["URL-scoped settings<br/>in merged config"]
  C -->|"Later registry override<br/>cannot rebind"| D["Credentials stay<br/>on original host"]
  B -->|"Emit deprecation<br/>warning"| E["User migration path"]

Loading

Grey Divider

File Changes

1. config/config/src/rescopeUnscopedCreds.ts ✨ Enhancement +102/-0

New rescoping function for unscoped credentials

config/config/src/rescopeUnscopedCreds.ts


2. config/config/src/index.ts ✨ Enhancement +32/-1

Integrate rescoping into config load flow

config/config/src/index.ts


3. config/config/test/index.ts 🧪 Tests +175/-0

Add comprehensive tests for credential rescoping

config/config/test/index.ts


View more (7)
4. network/auth-header/src/getAuthHeadersFromConfig.ts 🐞 Bug fix +0/-11

Remove legacy unscoped credential fallback

network/auth-header/src/getAuthHeadersFromConfig.ts


5. network/auth-header/test/getAuthHeadersFromConfig.test.ts 🧪 Tests +0/-39

Remove legacy unscoped credential tests

network/auth-header/test/getAuthHeadersFromConfig.test.ts


6. pkg-manager/core/test/install/auth.ts 🧪 Tests +0/-22

Remove legacy auth test case

pkg-manager/core/test/install/auth.ts


7. config/config/package.json Dependencies +1/-0

Add nerf-dart dependency for URL scoping

config/config/package.json


8. .changeset/credential-rebind-defense.md 📝 Documentation +7/-0

Document credential rebind defense changes

.changeset/credential-rebind-defense.md


9. cspell.json ⚙️ Configuration changes +6/-0

Add new terms to spell checker

cspell.json


10. pnpm-lock.yaml Dependencies +138/-45

Update lock file with dependency changes

pnpm-lock.yaml


Grey Divider

Qodo Logo

zkochan added 2 commits May 27, 2026 14:29
…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).
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 zkochan force-pushed the release/10-auth-fix branch from 3beac78 to fb8dd31 Compare May 27, 2026 12:30
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 674e4bfe-a18e-4c24-b849-83ad09554761

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/10-auth-fix

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 zkochan merged commit 99cdedc into release/10 May 27, 2026
4 of 14 checks passed
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