Repository navigation
[BUG] npm install fails with EALLOWREMOTE on certain private registry configurations #9796
Description
Is there an existing issue for this?
- I have searched the existing issues
This issue exists in the latest npm version
- I am using the latest npm
This is not just a request to bump a dependency for a CVE
- This is not solely a request to bump a dependency for a CVE
Current Behavior
If a registry is serving tarballs from a different path than the actual registry metadata (such as https://registry.example.com/blob/package.tgz, while the registry is https://registry.example.com/npm) this results in EALLOWREMOTE attempting to install anything from it.
Despite these ostensibly being the same origin to most browsers, as both the hostname and protocol match, npm apparently wants registry= to be an exact prefix for the purposes of allow-remote=none, yet I can't find anywhere that expectation is documented.
Expected Behavior
One of the following:
NPM follows browser rules, and understands that if the hostname and protocol match, the URL is indeed from the configured registry.
The expectation that tarballs are hosted/proxied from a path under the root of the registry, if the registry is not already the root of the site, is documented somewhere easily accessible.
Steps To Reproduce
- Set up a registry that serves tarballs from a different directory on the same hostname (such as aliasing two different paths to the same registry instance with a reverse proxy? the actual registry in question has something similar, though I'm not in a position to know the exact config behind it)
- Point NPM at the "non-canonical" registry path (so that the returned
tarballpath isn't underneath it) - Run
npm install, get EALLOWREMOTE.
Environment
- npm: 12.0.1
- Node.js: 24.18.0
- OS Name: MacOS
- System Model Name: Macbook Pro
- npm config: (Actual registry URL redacted for privacy, but the paths are accurate)
; "user" config from /Users/<redacted>/.npmrc
<redacted auth tokens>
; registry = "https://registry.example.com/api/npm/npm" ; overridden by project
; "project" config from /Users/<redacted>/<redacted>/.npmrc
registry = "https://registry.example.com/api/npm/npm" ; The same one as aboveThe registry itself is a JFrog Artifactory instance I'm not in a position to know any precise details about, with the actual paths involved being /api/npm/npm (The one in all our documentation and configs) and /artifactory/api/npm/npm (the one that the tarballs resolve under). Changing our config to use the more cumbersome path 'fixes' the issue, but is somewhat annoying if we have to rewrite everything just because someone decided that folders should be trust boundaries now.
Activity
simonmysun commentedon Aug 12, 2026
We hit
EALLOWREMOTEon npm 12.0.2 as well, and while digging into it I found what looks like a second, related trigger for the same underlying bug. Sharing the analysis in case it helps triage.TL;DR
npm 12 has a compensating patch in Arborist (
#isRegistryResolvedTarball) that is supposed to preventallow-remotefrom mis-firing on registry-hosted tarballs. That patch has (at least) two blind spots:registry=URL (reverse-proxied / multi-path registries).npm:aliases pointing at a scoped package on a custom registry, where the alias name loses the scope and the registry lookup falls back to the default registry.Both end up wrongly classified as
type: 'remote'even though the tarball is served by a configured registry.The mechanism (npm 12.0.2,
@npmcli/arborist)During reify, the node is turned into a spec of the form
`${node.name}@${resolvedURL}`:pacote's
npathen re-parsesname@URLastype: 'remote'(there's even a comment acknowledging this):That routes it to the
remotebranch instead of thealias/version→allowRegistrybranch:So the
allow-remotegate only stays correct if#isRegistryResolvedTarball(node)returnstrue. Here's that function:The reporter's case fails on the
pathname.startsWith(registryPath)check (tarball path isn't under theregistry=path).The alias variant
pickRegistry(npa(node.name), ...)only consultsspec.scope. For anpm:alias,node.nameis the alias name, not the target package name — so the scope is lost.Concrete repro. Two dependencies on the same private registry (a GitLab package registry):
Both resolve to bare tarball URLs under the same registry origin+path. In clean-cache isolated installs,
@<some-org>/<some-package>installs fine, while the alias throws:Simulating
#isRegistryResolvedTarballwith the real config shows exactly why:node.namenpa(name).scopepickRegistryresultisRegistryResolvedTarball@<some-org>/<some-package>@<some-org>@<some-org>:registrytrue→ allowed<some-alias-package>undefinedregistry.npmjs.org(default)false→ EALLOWREMOTEFor the alias,
npa("<some-alias-package>").scope === undefined, sopickRegistryfalls back to the default registry, the origin no longer matches our own package registry, and the patch declines to grant the override.Suggested fix
#isRegistryResolvedTarballshould resolve the registry from the target package name, not the alias name. Using the real name makes the check pass:More broadly, the underlying issue for both cases is that
allow-remotedecisions are made after a registry-resolved dependency has been flattened into aname@tarballURLspec, so the "is this actually a configured registry?" reconstruction in#isRegistryResolvedTarballhas to be watertight. It currently misses (1) tarball paths outside theregistry=prefix and (2) aliases whose scope is dropped.Environment
arseniyskvortsov-tempo commentedon Aug 12, 2026
The "alias" problem also happens with GitHub Packages (the registry is under
/npm, the blobs are under/download).