Skip to content

fix(arborist): allow-remote exemption for proxy/mirror-fronted registry tarballs#9550

Merged
owlstronaut merged 1 commit into
npm:latestfrom
manzoorwanijk:fix/allow-remote-registry-proxy-origin-9548
Jun 18, 2026
Merged

fix(arborist): allow-remote exemption for proxy/mirror-fronted registry tarballs#9550
owlstronaut merged 1 commit into
npm:latestfrom
manzoorwanijk:fix/allow-remote-registry-proxy-origin-9548

Conversation

@manzoorwanijk

Copy link
Copy Markdown
Contributor

With allow-remote=none (the default on npm 12) or allow-remote=root, a npm install fails with EALLOWREMOTE on ordinary registry dependencies when the configured registry origin differs from the lockfile resolved origin.

npm error code EALLOWREMOTE
npm error Fetching packages of type "remote" have been disabled

This is the common proxy/mirror case: a committed package-lock.json whose resolved URLs point to https://registry.npmjs.org/..., while the machine (or CI) is configured to use a private registry proxy/mirror with a different origin.
It affects both the hoisted and the linked install strategy.

Why

When extracting a registry-resolved package, reify hands pacote a name@URL spec, which pacote re-parses as type=remote and gates with allow-remote.
To avoid mis-firing on registry tarballs, #isRegistryResolvedTarball exempts them — but it compared the raw lockfile resolved URL against the configured registry origin.
With a proxy/mirror configured, resolved is the canonical registry.npmjs.org URL while the configured registry is the proxy, so the origins never matched, the exemption returned false, and the registry tarball was rejected as remote.

Crucially, reify already fetches a different URL than the raw resolved: #registryResolved applies replace-registry-host (default npmjs), rewriting the registry.npmjs.org host to the configured registry while preserving the path.
So npm fetches the tarball from the proxy correctly; only the allow-remote check was evaluating the wrong (pre-rewrite) URL.

How

Evaluate the effective URL npm actually fetches, not the raw lockfile value.

#isRegistryResolvedTarball now parses this.#registryResolved(node.resolved) — the host-rewritten URL — before the same origin + registry-path-prefix comparison.
After rewriting, a public-registry-pinned tarball resolves to the configured registry and is correctly recognized as registry-mediated.

The existing security boundary is preserved: under the default replace-registry-host, a same-origin tarball pointing outside the registry path is not rewritten and is still rejected, and a genuinely URL-declared dependency still fails the node.isRegistryDependency guard.
Under replace-registry-host=always, every tarball is routed through the configured registry, so registry dependencies are no longer treated as remote — consistent with what always means.

References

Fixes #9548

@manzoorwanijk manzoorwanijk marked this pull request as ready for review June 13, 2026 09:22
@manzoorwanijk manzoorwanijk requested review from a team as code owners June 13, 2026 09:22
@manzoorwanijk

Copy link
Copy Markdown
Contributor Author

@owlstronaut @JamieMagee this change might require some careful review.

@manzoorwanijk

Copy link
Copy Markdown
Contributor Author

Related issue #8875

@manzoorwanijk manzoorwanijk force-pushed the fix/allow-remote-registry-proxy-origin-9548 branch from b09d318 to b9177ae Compare June 18, 2026 14:28
owlstronaut pushed a commit to npm/npm-profile that referenced this pull request Jun 18, 2026
`npm login --auth-type=web` (the default) silently fails behind a
proxy/mirror registry, then falls back to legacy couch auth, which also
fails:

```
npm error code E401 - Incorrect or missing password
# or, depending on the proxy:
npm error Exit handler never called!
npm error code E405 - 405 Not Allowed - PUT .../-/user/org.couchdb.user:<name>
```

This is the common proxy/mirror case: the machine (or CI) is configured
to use a private registry proxy/mirror with a different origin than
`registry.npmjs.org`.

## Why

Web login does two requests against the configured registry: a `POST
/-/v1/login` that returns a `loginUrl` (opened in the browser) and a
`doneUrl` (polled by npm until the token is issued).

A proxy/mirror forwards npm's response verbatim, so the `doneUrl` it
returns points at the canonical
`https://registry.npmjs.org/-/v1/done?...`, not at the proxy that
actually created the session.
`webAuth` used that `doneUrl` as-is, so npm polled `registry.npmjs.org`
directly and got a `403` (the session lives on the proxy, not on the
public registry).
`webAuth` treats any `4xx`/`500` as "web login not supported" and falls
back to couch (`PUT /-/user/...`), which proxies commonly reject
(`405`/`401`) — the dead end users actually see.

This is the login analog of npm/cli#9550: a canonical
`registry.npmjs.org` URL handed back by the server must be
host-rewritten to the configured registry origin before npm fetches it.

## How

`replaceDoneUrlOrigin(doneUrl, opts.registry)` rewrites the `doneUrl` to
the configured registry's origin before polling, preserving any registry
path prefix and the query string.
The session is created by the `POST /-/v1/login` to `opts.registry`, so
the done-check must poll that same registry.

The rewrite is gated on the canonical npmjs host (`done.hostname ===
'registry.npmjs.org'`), mirroring the conservative default of
npm/cli#9550:

- A proxy/mirror that leaks the canonical `registry.npmjs.org` `doneUrl`
is corrected to poll the proxy.
- A registry that intentionally serves the done endpoint on its
own/other host is left untouched (a non-npmjs canonical host cannot be
inferred here).
- A non-proxied npmjs login is a no-op (same origin, no path prefix).

The browser-facing `loginUrl` is never rewritten.

## References

Fixes npm/cli#8875
Related: npm/cli#9550
@owlstronaut owlstronaut merged commit 8bbd70d into npm:latest Jun 18, 2026
24 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🎉 Backport to release/v11 created: #9577

owlstronaut pushed a commit that referenced this pull request Jun 18, 2026
…ry tarballs (#9577)

Backport of #9550 to `release/v11`.

Co-authored-by: Manzoor Wani <manzoorwani.jk@gmail.com>
@manzoorwanijk manzoorwanijk deleted the fix/allow-remote-registry-proxy-origin-9548 branch June 19, 2026 04:52
owlstronaut pushed a commit that referenced this pull request Jun 19, 2026
)

Adds a regression test for #8875. The fix is in npm-profile
(npm/npm-profile#191).

This test is expected to be red until bundled `npm-profile` is bumped to
the release with the fix, and turns green after that.

## Why

`npm login --auth-type=web` silently fails behind a proxy/mirror: the
returned `doneUrl` points at the canonical `registry.npmjs.org` instead
of the proxy, so npm polls the wrong host, gets a `403`, and falls back
to couch auth (which also fails). Fixed in npm-profile by rewriting
`doneUrl` to the configured registry origin.

## How

- `@npmcli/mock-registry`: `weblogin()` gains an optional `doneRegistry`
to emulate a proxy advertising a `doneUrl` on another origin.
- `test/lib/commands/login.js`: proxy registry whose `doneUrl` is on
`registry.npmjs.org`; asserts the token is saved with no couch fallback.
Fails with the current bundled npm-profile, passes once it is bumped.

## References

Fixes #8875
Depends on: npm/npm-profile#191
Related: #9550
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Registry-tarball allow-remote exemption fails when the configured registry origin differs from the lockfile resolved origin (proxy/mirror)

2 participants