Skip to content

[rush] Add provideNpmrcCredentialsViaEnvironment experiment to restore ${VAR} credentials with PNPM >= 10.34.2 - #5995

Open
Ian Clanton-Thuon (iclanton) with Copilot wants to merge 3 commits into
mainfrom
copilot/fix-npm-auth-token-issue
Open

[rush] Add provideNpmrcCredentialsViaEnvironment experiment to restore ${VAR} credentials with PNPM >= 10.34.2#5995
Ian Clanton-Thuon (iclanton) with Copilot wants to merge 3 commits into
mainfrom
copilot/fix-npm-auth-token-issue

Conversation

Copilot AI commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

PNPM 10.34.2 added dropUntrustedEnvExpansions, which silently discards any auth setting or request destination containing a ${VAR} token when it comes from a project or workspace .npmrc. Rush generates common/temp/.npmrc, which PNPM loads as both layers, so the documented practice of supplying private-feed credentials via environment variables in CI now fails with a pair of WARN Ignored project-level auth setting ... messages per credential and unauthenticated requests.

Only the cli, env (npm_config_*), and user config sources are still trusted by PNPM, so this change has Rush do the expansion itself and hand credentials to PNPM through npm_config_* variables.

Behavior (PNPM only, when the experiment is enabled)

syncNpmrc classifies each line containing a ${VAR} token the same way PNPM does:

  • Credentials (_authToken, _auth, _password, username, tokenHelper, cert, key — bare or registry-scoped) are replaced with a ; PROVIDED VIA ENVIRONMENT: marker that retains the unexpanded original line, and the value is passed via npm_config_*. The secret never touches disk, and rotating a token doesn't change the file (so cleanInstallAfterNpmrcChanges doesn't spuriously trigger).
  • Request destinations (registry, @scope:registry, proxy, http-proxy, https-proxy, and //... key names) are expanded literally into the generated file — they aren't secret, and the generated file isn't committed.
  • Everything else is left untouched for PNPM to expand as before.

Given common/config/rush/.npmrc:

registry=https://pkgs.dev.azure.com/azdo-org/_packaging/package-feed/npm/registry
//pkgs.dev.azure.com/azdo-org/_packaging/package-feed/npm/registry/:_authToken=${NPM_AUTH_TOKEN}

the generated common/temp/.npmrc becomes:

registry=https://pkgs.dev.azure.com/azdo-org/_packaging/package-feed/npm/registry
; PROVIDED VIA ENVIRONMENT: //pkgs.dev.azure.com/azdo-org/_packaging/package-feed/npm/registry/:_authToken=${NPM_AUTH_TOKEN}

and PNPM is invoked with npm_config_//pkgs.dev.azure.com/.../npm/registry/:_authToken set to the resolved token.

Changes

  • npmrcUtilities.ts — sensitive-setting classification mirroring PNPM's own lists; consolidated ${VAR} expansion into _expandEnvironmentVariables(); new moveSensitiveSettingsToEnvironment option on syncNpmrc/trimNpmrcFileLines; new getNpmrcEnvironmentVariables() that reconstructs the npm_config_* map by re-reading the marker lines.
  • Experiment plumbingprovideNpmrcCredentialsViaEnvironment in IExperimentsJson, experiments.schema.json, the rush init template, and the API report. InstallHelpers.shouldProvideNpmrcCredentialsViaEnvironment() is the single read point and also gates on isPnpm.
  • Call sitesBaseInstallManager passes the new sync option; WorkspaceInstallManager and Autoinstaller inject the variables into the package manager's environment; rush-pnpm recomputes them from the already-generated file so it stays authenticated without re-syncing.

Notes for reviewers

  • Stateless by design. syncNpmrc runs during prepareAsync while the install happens later, and rush-pnpm never syncs at all. Recomputing from the marker comments rather than threading state through keeps all consumers uniform.
  • Name round-tripping. PNPM's envKeyToSetting() splits an npm_config_* name at its first colon and normalizes the remainder, so //registry.example.com:8080/:_authToken would be mangled into :-authtoken. Such settings fall back to literal expansion in the generated file rather than the environment.
  • Windows. Environment variable names are case-insensitive there; URI casing in an npm_config_//... name could in principle be altered. Unverified, and part of why this is behind an experiment.
  • SetupPackageRegistry, rush publish (.npmrc-publish), and install-run.js are unchanged — the option defaults to false.

Verified against pnpm 10.34.5 (warning gone, Authorization header sent) and pnpm 9.15.9 (unchanged behavior) using a local registry; the sandbox's network restrictions prevented a full rush install.

…npm 10.34.2+ ignoring ${VAR} in project .npmrc credentials

Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com>
Comment thread libraries/rush-lib/src/logic/installManager/InstallHelpers.ts Outdated
Comment thread libraries/rush-lib/src/utilities/npmrcUtilities.ts
@iclanton

Copy link
Copy Markdown
Member

Copilot - I've left comments.

…ronment

Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com>
@iclanton
Ian Clanton-Thuon (iclanton) marked this pull request as ready for review August 28, 2026 07:57
@iclanton Ian Clanton-Thuon (iclanton) changed the title Add provideNpmrcCredentialsViaEnvironment experiment to restore ${VAR} credentials with PNPM >= 10.34.2 [rush] Add provideNpmrcCredentialsViaEnvironment experiment to restore ${VAR} credentials with PNPM >= 10.34.2 Aug 28, 2026
@mojaza
Mo Jazayeri (mojaza) requested a balanced review from Copilot August 28, 2026 16:49
@mojaza

Copy link
Copy Markdown
Contributor

I asked Copilot to review your PR. I will take a look when it's done. I do recommend you to do the same on your other PRs as its reviews were mostly legit to me :)

Copilot AI 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.

Pull request overview

Adds an opt-in PNPM workaround that moves environment-expanded .npmrc credentials into npm_config_* variables.

Changes:

  • Classifies and transforms sensitive .npmrc settings.
  • Injects reconstructed credentials into PNPM invocation environments.
  • Adds experiment configuration, documentation, tests, and change metadata.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts Tests .npmrc transformations.
libraries/rush-lib/src/utilities/npmrcUtilities.ts Implements classification, expansion, markers, and reconstruction.
libraries/rush-lib/src/schemas/experiments.schema.json Defines the experiment schema.
libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts Supplies the subspace .npmrc location.
libraries/rush-lib/src/logic/installManager/InstallHelpers.ts Gates the experiment and prepares PNPM environments.
libraries/rush-lib/src/logic/base/BaseInstallManager.ts Enables credential extraction during .npmrc synchronization.
libraries/rush-lib/src/logic/Autoinstaller.ts Applies credentials to autoinstaller commands.
libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts Applies credentials to rush-pnpm.
libraries/rush-lib/src/api/ExperimentsConfiguration.ts Exposes the experiment API.
libraries/rush-lib/assets/rush-init/common/config/rush/experiments.json Documents the experiment in generated repositories.
common/reviews/api/rush-lib.api.md Updates the API report.
common/changes/@microsoft/rush/copilot-npmrc-credentials-experiment_2026-08-28-05-19.json Records the Rush minor change.
Suppressed comments (1)

libraries/rush-lib/src/logic/installManager/InstallHelpers.ts:423

  • This object is still process.env by reference because _mergeEnvironmentVariables() returns its baseEnv argument directly. The subsequent Object.assign therefore permanently installs each subspace's credentials into the Rush process; later subspace installs inherit stale registry credentials that are absent from their own .npmrc. Clone process.env before merging so credentials remain scoped to the child invocation.
    const packageManagerEnvironment: NodeJS.ProcessEnv = _mergeEnvironmentVariables(
      process.env,
      configurationEnvironment,
      options
    );

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 495 to 498
const packageManagerEnv: NodeJS.ProcessEnv = InstallHelpers.getPackageManagerEnvironment(
this.rushConfiguration,
this.options
{ ...this.options, npmrcFolder: subspace.getSubspaceTempFolderPath() }
);
rushConfiguration: RushConfiguration,
options: {
debug?: boolean;
npmrcFolder?: string;
Comment on lines +680 to +682
export function getNpmrcEnvironmentVariables(
options: IGetNpmrcEnvironmentVariablesOptions
): Record<string, string> | undefined {
@@ -559,7 +559,10 @@ export abstract class BaseInstallManager {
targetNpmrcFolder: subspace.getSubspaceTempFolderPath(),
linesToPrepend: extraNpmrcLines,
createIfMissing: this.rushConfiguration.subspacesFeatureEnabled,
supportEnvVarFallbackSyntax: this.rushConfiguration.isPnpm
supportEnvVarFallbackSyntax: this.rushConfiguration.isPnpm,
moveSensitiveSettingsToEnvironment: InstallHelpers.shouldProvideNpmrcCredentialsViaEnvironment(

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.

Credentials are removed from .npmrc for all PNPM installs, but legacy RushInstallManager never supplies npmrcFolder to getPackageManagerEnvironment(). Private-registry installs fail when useWorkspaces: false. Pass the subspace temp folder as done by WorkspaceInstallManager.

Comment on lines +369 to +371
// The setting name cannot survive PNPM's environment variable name normalization, so fall back
// to writing the expanded value into the generated .npmrc file. This is less desirable, but the
// generated file is not committed to Git.

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.

Registry auth keys containing explicit ports cannot round-trip through PNPM environment variables, so the resolved secret is written to common/temp/.npmrc. This violates the feature’s “secret never touches disk” guarantee. Avoid plaintext fallback or reject unsupported keys clearly.

@iclanton

Copy link
Copy Markdown
Member

Copilot - Can you take a look at the comments?

Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com>

Copilot AI commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Copilot - Can you take a look at the comments?

Addressed in a34bd29: preserved credential variables on Windows, wired legacy PNPM installs, rejected unsafe explicit-port auth keys, and added round-trip/missing-file coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

4 participants