Skip to content

fix(npm): download tarballs from the configured registry instead of registry.npmjs.org#36187

Open
nathanwhit wants to merge 3 commits into
denoland:mainfrom
nathanwhit:fix/npm-tarball-registry-host
Open

fix(npm): download tarballs from the configured registry instead of registry.npmjs.org#36187
nathanwhit wants to merge 3 commits into
denoland:mainfrom
nathanwhit:fix/npm-tarball-registry-host

Conversation

@nathanwhit

@nathanwhit nathanwhit commented Jul 20, 2026

Copy link
Copy Markdown
Member

Closes #36183

Problem

When a private registry is configured (via .npmrc or NPM_CONFIG_REGISTRY), Deno correctly fetches manifests from it but could still download tarballs from registry.npmjs.org, bypassing the configured registry. In regulated/enterprise setups that route npm traffic through a proxy for security scanning, this means the actual package bytes (the supply-chain risk) skip the filter entirely.

A tarball URL ends up pointing at registry.npmjs.org even with a private registry configured in two ways:

  1. Passthrough packuments — some proxies (e.g. Artifactory) don't rewrite dist.tarball in the packument, so Deno reads registry.npmjs.org URLs straight from the manifest.
  2. Lockfile reconstruction — the lockfile omits tarball URLs that match the npmjs.org default (for portability) and reconstructs them on read via a provider that hardcodes https://registry.npmjs.org/.... A lockfile generated against npmjs.org and then reused behind a proxy sends every tarball to npmjs.org.

Fix

Deno now mirrors npm's replace-registry-host default (npmjs): at the single tarball-download chokepoint, when dist.tarball's host is registry.npmjs.org and a different registry is configured for that package's scope, the download is relocated to the configured registry.

Unlike npm — whose implementation drops the registry's base path — this preserves the base path (e.g. an Artifactory repo under /api/npm/npm-remote/), which is where such proxies actually serve tarballs. It is a no-op when the configured registry is already npmjs.org or when the proxy already rewrote the tarball host. As a bonus, the relocated URL now matches the private registry's configured auth, so scoped credentials are applied to the tarball request.

Behavior change

This is an intentional behavior change worth calling out: a user who has a private registry configured but genuinely wants tarballs served from registry.npmjs.org (e.g. a manifest-only mirror that intentionally leaves dist.tarball pointing at npm) will now have those tarball downloads relocated to their configured registry. This matches npm's replace-registry-host=npmjs default, and is a no-op when the configured registry is already npmjs.org or when the proxy already rewrote the tarball host, but it may surprise a manifest-only-mirror setup.

Tests

  • Unit tests for the URL relocation (root and base-path registries, scoped packages, and both no-op cases).
  • A spec test (tests/specs/npm/tarball_relocate_to_registry) with a fixture package whose packument points dist.tarball at registry.npmjs.org; it asserts the tarball is downloaded from the configured registry and runs end-to-end. Verified that the spec test fails without the fix (tarball stays on registry.npmjs.org).

…ured registry

When a private registry is configured (via .npmrc or NPM_CONFIG_REGISTRY) but a
tarball URL still points at registry.npmjs.org -- because a proxy does not
rewrite tarball URLs in its packuments, or because the URL was reconstructed
from a lockfile that omitted it -- Deno downloaded the package bytes from
registry.npmjs.org, bypassing the configured registry entirely.

Deno now mirrors npm replace-registry-host=npmjs: tarballs served by the public
npm registry are relocated to the configured registry, preserving its base path
(e.g. an Artifactory repo under /api/npm/npm-remote/).

Closes denoland#36183
The tarball relocation fix routes registry.npmjs.org tarball downloads to the
configured registry. lock_file_lock_write uses a portable lockfile (tarball URLs
omitted, so they reconstruct to registry.npmjs.org) with real npm packages that
the test registry does not serve, so it relied on those downloads reaching the
public registry. Point NPM_CONFIG_REGISTRY at the real npm registry for this
test so the reconstructed tarballs are not relocated to the test registry.
@bartlomieju

Copy link
Copy Markdown
Member

Nice fix, and the no-op guards / base-path preservation / scoped-auth handling all check out. One thing worth addressing:

Failure messages report the pre-relocation dist.tarball URL, not the URL actually fetched. After maybe_relocate_npm_registry_tarball, the download targets the configured registry (tarball_uri), but all three error paths still interpolate dist.tarball (the original registry.npmjs.org URL):

  • libs/npm_cache/tarball.rs:234scoped_registry_auth_error(&dist.tarball, registry_url) on the 401 path
  • libs/npm_cache/tarball.rs:286 — same on the 404/None path
  • libs/npm_cache/tarball.rs:288format!("Could not find npm package tarball at: {}", dist.tarball)

So a user behind, say, an Artifactory proxy that 404s a given package gets Could not find npm package tarball at: https://registry.npmjs.org/foo/-/foo-1.0.0.tgz even though Deno actually requested it from their proxy and never touched npmjs.org. That points exactly this PR's audience (enterprise/proxy debuggers) at the wrong host. Suggest reporting the relocated tarball_uri in these messages (or both the original and the relocated URL).

Two smaller non-blocking notes for the description:

  1. This is an intentional behavior change worth calling out: a user with a private registry configured who genuinely wants tarballs from npmjs.org (a manifest-only mirror) will now have those relocated. It matches npm's replace-registry-host=npmjs default, but it may surprise some setups.
  2. The lock_file_lock_write guard hints that other tests/lockfiles reconstructing npmjs tarball URLs under a private test registry could be similarly affected — worth confirming the full npm suite is green.

After relocating a registry.npmjs.org tarball to the configured registry,
the 401/404/not-found error paths still interpolated the original
`dist.tarball` URL. A user behind a proxy that 404s a package would be told
the tarball couldn't be found at registry.npmjs.org, even though Deno
requested it from their configured registry. Report the post-relocation URL
that was actually contacted instead.

Addresses review feedback.
@nathanwhit

Copy link
Copy Markdown
Member Author

Thanks for the review!

Failure messages report the pre-relocation dist.tarball URL, not the URL actually fetched.

Good catch — fixed in d9387b5. All three error paths (the 401 and 404 scoped_registry_auth_error calls plus the "Could not find npm package tarball at" message) now report the post-relocation URL that Deno actually contacted, captured as fetched_tarball_url before the download consumes the Url. So a proxy user who 404s now sees their configured registry, not registry.npmjs.org.

This is an intentional behavior change worth calling out (manifest-only mirror)

Added a "Behavior change" section to the PR description spelling this out.

The lock_file_lock_write guard hints other tests/lockfiles could be similarly affected — worth confirming the full npm suite is green.

Confirmed — ran the full npm integration suite (cargo test -p integration_tests --test integration npm): 77 passed, 0 failed, including lock_file_lock_write. The relocation spec test (tarball_relocate_to_registry) and the crate unit tests pass as well.

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.

deno install fetches npm tarballs from registry.npmjs.org regardless of configured registry or dist.tarball value

2 participants