[rush] Add provideNpmrcCredentialsViaEnvironment experiment to restore ${VAR} credentials with PNPM >= 10.34.2 - #5995
[rush] Add provideNpmrcCredentialsViaEnvironment experiment to restore ${VAR} credentials with PNPM >= 10.34.2#5995Ian Clanton-Thuon (iclanton) with Copilot wants to merge 3 commits into
provideNpmrcCredentialsViaEnvironment experiment to restore ${VAR} credentials with PNPM >= 10.34.2#5995Conversation
…npm 10.34.2+ ignoring ${VAR} in project .npmrc credentials
Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com>
|
Copilot - I've left comments. |
…ronment Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com>
provideNpmrcCredentialsViaEnvironment experiment to restore ${VAR} credentials with PNPM >= 10.34.2provideNpmrcCredentialsViaEnvironment experiment to restore ${VAR} credentials with PNPM >= 10.34.2
|
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 :) |
There was a problem hiding this comment.
Pull request overview
Adds an opt-in PNPM workaround that moves environment-expanded .npmrc credentials into npm_config_* variables.
Changes:
- Classifies and transforms sensitive
.npmrcsettings. - 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.envby reference because_mergeEnvironmentVariables()returns itsbaseEnvargument directly. The subsequentObject.assigntherefore permanently installs each subspace's credentials into the Rush process; later subspace installs inherit stale registry credentials that are absent from their own.npmrc. Cloneprocess.envbefore 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.
| const packageManagerEnv: NodeJS.ProcessEnv = InstallHelpers.getPackageManagerEnvironment( | ||
| this.rushConfiguration, | ||
| this.options | ||
| { ...this.options, npmrcFolder: subspace.getSubspaceTempFolderPath() } | ||
| ); |
| rushConfiguration: RushConfiguration, | ||
| options: { | ||
| debug?: boolean; | ||
| npmrcFolder?: string; |
| 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( | |||
There was a problem hiding this comment.
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.
| // 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. |
There was a problem hiding this comment.
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.
|
Copilot - Can you take a look at the comments? |
Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com>
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 generatescommon/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 ofWARN Ignored project-level auth setting ...messages per credential and unauthenticated requests.Only the
cli,env(npm_config_*), anduserconfig sources are still trusted by PNPM, so this change has Rush do the expansion itself and hand credentials to PNPM throughnpm_config_*variables.Behavior (PNPM only, when the experiment is enabled)
syncNpmrcclassifies each line containing a${VAR}token the same way PNPM does:_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 vianpm_config_*. The secret never touches disk, and rotating a token doesn't change the file (socleanInstallAfterNpmrcChangesdoesn't spuriously trigger).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.Given
common/config/rush/.npmrc:the generated
common/temp/.npmrcbecomes:and PNPM is invoked with
npm_config_//pkgs.dev.azure.com/.../npm/registry/:_authTokenset to the resolved token.Changes
npmrcUtilities.ts— sensitive-setting classification mirroring PNPM's own lists; consolidated${VAR}expansion into_expandEnvironmentVariables(); newmoveSensitiveSettingsToEnvironmentoption onsyncNpmrc/trimNpmrcFileLines; newgetNpmrcEnvironmentVariables()that reconstructs thenpm_config_*map by re-reading the marker lines.provideNpmrcCredentialsViaEnvironmentinIExperimentsJson,experiments.schema.json, therush inittemplate, and the API report.InstallHelpers.shouldProvideNpmrcCredentialsViaEnvironment()is the single read point and also gates onisPnpm.BaseInstallManagerpasses the new sync option;WorkspaceInstallManagerandAutoinstallerinject the variables into the package manager's environment;rush-pnpmrecomputes them from the already-generated file so it stays authenticated without re-syncing.Notes for reviewers
syncNpmrcruns duringprepareAsyncwhile the install happens later, andrush-pnpmnever syncs at all. Recomputing from the marker comments rather than threading state through keeps all consumers uniform.envKeyToSetting()splits annpm_config_*name at its first colon and normalizes the remainder, so//registry.example.com:8080/:_authTokenwould be mangled into:-authtoken. Such settings fall back to literal expansion in the generated file rather than the environment.npm_config_//...name could in principle be altered. Unverified, and part of why this is behind an experiment.SetupPackageRegistry,rush publish(.npmrc-publish), andinstall-run.jsare unchanged — the option defaults tofalse.Verified against pnpm 10.34.5 (warning gone,
Authorizationheader sent) and pnpm 9.15.9 (unchanged behavior) using a local registry; the sandbox's network restrictions prevented a fullrush install.