Skip to content

fix(install): honour the machine npm registry in the portable Node.js runtime - #57

Merged
Giorgio Ughini (GiorgioUghini) merged 1 commit into
mainfrom
giorgioughini-fix-portable-npm-registry-config
Aug 7, 2026
Merged

fix(install): honour the machine npm registry in the portable Node.js runtime#57
Giorgio Ughini (GiorgioUghini) merged 1 commit into
mainfrom
giorgioughini-fix-portable-npm-registry-config

Conversation

@GiorgioUghini

Copy link
Copy Markdown
Contributor

Problem

Internal-Microsoft users cannot install the latest release. npm ci fails with:

npm error code ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE
npm error request to https://registry.npmjs.org/zod/-/zod-4.4.3.tgz failed,
  reason: ...ssl3_read_bytes:ssl/tls alert handshake failure...

accompanied by a Windows Security toast: "This content is blocked by your IT admin ... [te] npm url block".

Root cause

The installers download a portable Node.js archive and run its bundled npm.

Node.js ships the builtin npmrc containing prefix=${APPDATA}\npm only in the Windows MSI — the .zip/.tar.gz archives do not have it. Verified against a runtime the installer had already unpacked:

runtime\node-v24.19.0-win-arm64\node_modules\npm\npmrc : missing
runtime\node-v24.19.0-win-arm64\etc\npmrc              : missing

So the portable npm resolves prefix to its own throwaway runtime directory and globalconfig to <runtime>\etc\npmrc, which never exists. On Microsoft-managed machines the corporate registry lives exactly in the global npmrc, and there is no user-level ~/.npmrc:

%APPDATA%\npm\etc\npmrc  ->  registry=https://packagefeedproxy.microsoft.io/npm/
%USERPROFILE%\.npmrc     ->  does not exist

Reproduced by simulating the portable prefix:

prefix:        ...\portable-node-sim
globalconfig:  ...\portable-node-sim\etc\npmrc
registry:      https://registry.npmjs.org/    <-- silent fallback

With no configured registry, npm's replace-registry-host=npmjs has nothing to remap to, so every tarball is fetched from the lockfile's canonical registry.npmjs.org URLs — which network policy blocks.

This surfaced in 0.4.0: 95b56c7 correctly stopped committing internal ms-feed-*.pkgs.visualstudio.com URLs in the lockfile (which broke external users) and removed the NPM_CONFIG_REGISTRY / NPM_CONFIG_REPLACE_REGISTRY_HOST=never overrides. INSTALL.md now relies on "npm may map those URLs to a configured compatible corporate registry" — but the portable runtime never sees that configuration.

Fix

Locate the machine's real global npmrc before the portable runtime is prepended to PATH (probe npm config get globalconfig, falling back to %APPDATA%\npm\etc\npmrc), and pass it to the portable npm via NPM_CONFIG_GLOBALCONFIG. replace-registry-host=npmjs then maps the canonical lockfile URLs onto the configured mirror.

Also included:

  • SKILL_RECORDER_NPM_REGISTRY escape hatch (HTTPS-validated) for a one-off registry.
  • The installer now prints the registry it will actually use.
  • npm ci failures explain the mirror options instead of just surfacing a TLS error.
  • Same treatment in install.sh.

No impact on external users

The override is applied only when an npm config file actually exists. On a machine with no npm configuration, nothing is set and npm keeps using registry.npmjs.org. Verified:

external-user result: []
OK: external users keep the default registry.

Security posture is unchanged: the lockfile's integrity hashes are verified whichever registry serves the packages, and --ignore-scripts=false --dangerously-allow-all-scripts=false --strict-allow-scripts are still passed on the command line, which outranks any config file.

Verification

End-to-end npm ci against a lockfile pinning https://registry.npmjs.org/zod/-/zod-4.4.3.tgz:

Before (portable prefix, no globalconfig):

registry: https://registry.npmjs.org/

After (portable prefix + machine globalconfig):

registry: https://packagefeedproxy.microsoft.io/npm/
npm http cache zod@https://packagefeedproxy.microsoft.io/npm/zod/-/zod-4.4.3.tgz
added 1 package in 4s

Also run:

  • scripts\install-windows.test.ps1 — passes, extended with coverage for the new Resolve-MachineNpmConfigPath helper (existing npmrc, undefined/null placeholders, missing file, quoted path).
  • install.ps1 parses cleanly under Windows PowerShell 5.1 and PowerShell 7.
  • bash -n install.sh clean; detect_machine_npm_config exercised with and without npm on PATH.
  • node scripts/check-lockfile-portability.mjs still passes.

Note for a follow-up (not addressed here)

623 of 625 lockfile entries carry SHA-1 integrity (sha1-...) rather than sha512-..., because the feed that generated the lockfile returns only the legacy dist.shasum. That is a weaker integrity guarantee than intended and is worth regenerating separately.

The installers unpack a portable Node.js archive and run its bundled npm.
Node.js ships the builtin npmrc that sets `prefix=${APPDATA}\npm` only in the
Windows MSI, so the portable archive leaves npm resolving `globalconfig` inside
the throwaway runtime directory. On machines whose npm registry is configured in
the global npmrc rather than a user `~/.npmrc`, npm never saw that file, fell
back to registry.npmjs.org, and `npm ci` failed with
ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE wherever network policy blocks the
public registry.

Locate the machine's real global npmrc before the portable runtime is prepended
to PATH and pass it to npm as NPM_CONFIG_GLOBALCONFIG. npm's default
`replace-registry-host=npmjs` then maps the lockfile's canonical npmjs URLs onto
the configured mirror, and the lockfile integrity hashes are still verified.
Machines without any npm configuration resolve no override and keep using
registry.npmjs.org exactly as before.

Also add a SKILL_RECORDER_NPM_REGISTRY escape hatch, report the registry that
will actually be used, and explain the mirror options when `npm ci` fails.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@GiorgioUghini
Giorgio Ughini (GiorgioUghini) merged commit 1a1f5e9 into main Aug 7, 2026
1 of 5 checks passed
@GiorgioUghini Giorgio Ughini (GiorgioUghini) mentioned this pull request Aug 7, 2026
10 tasks
pull Bot pushed a commit to Stars1233/skill-recorder that referenced this pull request Aug 7, 2026
PR microsoft#57 added a SKILL_RECORDER_NPM_REGISTRY escape hatch that made both
installers reference the npm registry environment variable directly. That
trips the deliberate compliance guard in scripts/compliance.test.mjs, which
asserts the installers never pin an npm registry -- the exact regression
that broke installs for users behind a corporate proxy in the first place.

The escape hatch was redundant anyway: npm already honours a caller-set
registry environment variable, so users keep the same capability without
the installer sources dictating a registry.

The actual fix stays: the installers still point the portable runtime at
the machine's existing npm configuration via NPM_CONFIG_GLOBALCONFIG,
which discovers rather than overrides the configured registry.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 02a84c32-5401-470c-a8f1-b5f7a3f1b05b
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