Skip to content

ci: Route npm package restore through the Central Feed Service - #1045

Merged
wenytang-ms merged 1 commit into
mainfrom
wenyt/cfs-npm-feed
Jul 28, 2026
Merged

ci: Route npm package restore through the Central Feed Service#1045
wenytang-ms merged 1 commit into
mainfrom
wenyt/cfs-npm-feed

Conversation

@wenytang-ms

Copy link
Copy Markdown
Contributor

Why

SFI Network Isolation requires build pipelines to stop restoring packages directly from public feeds. These pipelines resolve everything from registry.npmjs.org, so they cannot run on a network isolated pool, and they are flagged by the MountainPass SR21 campaign under ICM 840100965 (ServiceTree a7511cd0-e340-456b-892c-88ce9d7e37ec, deadline 2026-08-21).

This points npm at the CFS feed mseng/VSJava/vscjava.

How

The redirect is carried by npm_config_registry, declared as a pipeline variable. npm resolves configuration in the order

cli > environment > project .npmrc > user .npmrc > global > builtin > default

so writing the registry only into a generated user config leaves it at the second lowest precedence, outranked by anything the agent image already configures. Microsoft hosted images do ship a user level .npmrc pointing at an internal proxy. An environment variable sits above every .npmrc file, so it is the one lever that reaches all of these pipelines.

npm matches npm_config_* case insensitively, which matters because restore is not driven by a single task here — the Npm@1 tasks, npx json, npx @vscode/vsce and the vsce invocation inside AzureCLI@2 all inherit the agent environment rather than reading a task input.

A .npmrc is still generated into the agent temp directory, because NpmAuthenticate@0 discovers the registries to authenticate by reading it. npm now takes the URL from the environment and the matching credential from that file. Nothing is committed, so outside contributors and the GitHub Actions workflows keep restoring from the public registry, and the credential never lands inside the workspace.

The outcome is asserted, not assumed. A silent fallback to the public registry is the failure this change exists to prevent, and it leaves no trace in the build log — npm never reports which registry it used and the package counts look identical either way. In vscode-gradle this exact configuration produced a build that restored 718 packages against a CFS feed that had cached nothing. A Verify CFS npm registry step now fails the build if npm is not pointed at the feed by the time restore begins.

Template references are absolute and anchored to @self. A relative path is resolved against the file doing the including, which for these pipelines is the 1ES extends template in another repository, so the unqualified form is looked up in 1ESPipelineTemplates and fails YAML compilation.

Validation

Full template expansion through the pipelines preview API on this branch. No builds were queued.

Definition Id In SR21 scope Preview
1ES-CI 16478 no not previewable, definition disabled
VSCode-Java-Dependency-Viewer-Nightly 16479 yes compiles
VSCode-Java-Dependency-Viewer-RC 16480 yes compiles
JavaDep_Release_Nightly 21458 no compiles
JavaDep_Release 21459 no compiles

Each passing pipeline expands to exactly three CFS steps — Configure CFS npm registry, Authenticate to CFS feed, Verify CFS npm registry — placed after the Node install task and before the first step that restores packages.

Feed authentication uses Project Collection Build Service (mseng), which holds contributor on the vscjava feed; all of these definitions run with jobAuthScope=projectCollection.

The verify script was also run directly under cmd.exe against both outcomes: with a preconfigured user .npmrc in effect it reports https://packagefeedproxy.microsoft.io/npm/ and exits 1, and with npm_config_registry set it reports the CFS feed and exits 0 — demonstrating the environment variable overriding a preconfigured user config, which is the case this change exists for.

Not covered

  • NodeTool@0 / UseNode@1, JavaToolInstaller@0 and any APIScan tool download are separate outbound fetches and are not addressed here.
  • The SR21 seven day lock-in cannot be satisfied by waiting on pipelines that are trigger: none or disabled.

SFI Network Isolation requires build pipelines to stop restoring packages
directly from public feeds. These pipelines resolve everything from
registry.npmjs.org today, so they cannot run on a network isolated pool
and are flagged by the MountainPass SR21 campaign.

Point npm at the CFS feed mseng/VSJava/vscjava instead.

The redirect is carried by npm_config_registry, declared as a pipeline
variable. npm resolves configuration in the order cli > environment >
project .npmrc > user .npmrc, so writing the registry only into a
generated user config would leave it outranked by anything the agent
image already configures -- Microsoft hosted images do ship a user level
.npmrc pointing at an internal proxy. An environment variable sits above
every .npmrc file, so it is the one lever that reaches all of these
pipelines. npm matches npm_config_* case insensitively, which matters
because restore is not driven by a single task here: the Npm tasks,
npx json, npx @vscode/vsce and the vsce invocation inside AzureCLI@2 all
inherit the agent environment rather than reading a task input.

A .npmrc is still generated into the agent temp directory, because
NpmAuthenticate discovers the registries to authenticate by reading it.
npm now takes the URL from the environment and the matching credential
from that file. Nothing is committed, so open source contributors and the
GitHub Actions workflows keep restoring from the public registry, and the
credential never lands inside the workspace.

Assert the result rather than assuming it. A silent fallback to the
public registry is the failure this change exists to prevent and it
leaves no trace in the build log -- npm never reports which registry it
used, and the package counts look identical either way. The pipelines now
fail if npm is not pointed at the CFS feed by the time restore begins.

The steps template is referenced as an absolute path anchored to @self.
A relative path is resolved against the file doing the including, which
for these pipelines is the 1ES extends template in another repository, so
the unqualified form is looked up in 1ESPipelineTemplates and fails YAML
compilation.
@wenytang-ms wenytang-ms changed the title Route npm package restore through the Central Feed Service ci: Route npm package restore through the Central Feed Service Jul 28, 2026
@wenytang-ms
wenytang-ms merged commit 8d6e46f into main Jul 28, 2026
32 of 33 checks passed
@wenytang-ms
wenytang-ms deleted the wenyt/cfs-npm-feed branch July 28, 2026 06:09
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.

2 participants