The lockfile had not changed in six weeks. The Node base image had. That is the shape of most npm error code EALLOWREMOTE reports landing this month: a green pipeline goes red on npm ci, for packages that live on the same registry they always lived on.
Short version: Fetching packages of type "remote" have been disabled has at least four distinct causes, and only one of them involves a dependency that genuinely points at a remote URL. The line under the error, Refusing to fetch "<spec>", is the diagnostic. If the URL in it points at your own registry host, you are looking at npm's path-prefix check rejecting a legitimate registry tarball, and --allow-remote=all would disable a supply-chain control across your entire dependency tree to work around a string comparison.
What changed in npm 12.0.0
npm 12.0.0 shipped on 8 July 2026 and flipped allow-git and allow-remote from all to none by default, pre-announced in the GitHub changelog on 9 June. The config reference describes allow-remote as limiting "dependencies that point to a tarball url instead of a version or semver range," with accepted values all, none, and root.
The goal is narrow and worth having: stop a lockfile from pulling a tarball off some random host at install time. This is the same release that turned install scripts off by default, so if you are still working through the allowScripts warning and the npm 12 install-script gate, EALLOWREMOTE is the sibling break you will hit next. Like the edgesOut crash, one error string covers several different trees, and the triage matters more than the fix.
The implementation is broader than the goal. The exemption that lets ordinary registry tarballs through is a URL comparison inside Arborist, it has been patched repeatedly since May, and two failure modes are open against 12.0.x right now.
Why does my own registry count as "remote"?
The gate lives in reify.js. Commit 95cf2e9, "fix: validate registry path for allow-remote tarballs", gives #isRegistryResolvedTarball() its current body: build new URL(node.resolved), build new URL(pickRegistry(npa(node.name), this.options)), normalise the registry pathname with a trailing slash, then return
resolved.origin === registry.origin &&
(registryPath === '/' || resolved.pathname.startsWith(registryPath))
Same host stopped being sufficient. The path of the tarball has to begin with the path of the registry.
That rule breaks real Artifactory layouts. npm/cli issue #9796, open against 12.0.1 and marked Priority 1, reports a registry configured as https://registry.example.com/api/npm/npm serving tarballs from https://registry.example.com/artifactory/api/npm/npm/package.tgz. Identical protocol, identical host, different path prefix, so startsWith returns false and every package in the install is reclassified as type=remote. Nothing in the config reference tells you the registry URL must be an exact prefix of the tarball URL.
Anyone on Artifactory, Nexus, Verdaccio, GitHub Packages or a corporate proxy is disproportionately exposed here, and the blast radius is CI, because CI is where the Node image moves without the lockfile moving.
Which bug do I actually have?
The wording separates two of these before you read any URL. Fetching packages of type "remote" have been disabled comes from allow-remote=none. Fetching non-root packages of type "remote" have been disabled comes from allow-remote=root, and issue #9509 territory aside, that branch had its own break: install-strategy=linked rejected root-level tarballs that the hoisted strategy allowed under identical config, closed by PR #9510.
Why does it fail only under --dry-run?
Issue #9800 (open, PR #9818 pending) reproduces with npm i --dry-run [email protected], which fails on Refusing to fetch "https://registry.npmjs.org/npm/-/npm-11.10.0.tgz" while the same install without --dry-run succeeds. The trigger is a package in the graph declaring bundleDependencies: the real install applies the registry exemption, the preview path does not.
The symptom this produces in a real org is specific and easy to misread. Renovate and Dependabot preview jobs fail, PR-time plan steps fail, and every developer installs fine on their laptop. Three people will spend an afternoon on the bot's credentials before anyone reads the flag in the job definition. If your only red job is the dependency bot, start there.
The fix for a private registry is replace-registry-host
Issue #9548 traced EALLOWREMOTE on a committed public-registry lockfile installed through a private mirror to the comparison running against the raw lockfile value. PR #9550 changed it to const resolvedURL = new URL(this.#registryResolved(node.resolved)), so the check matches the effective fetch URL after replace-registry-host rewriting. That fix landed before GA, and it depends on a second config: replace-registry-host defaults to "npmjs", which rewrites only registry.npmjs.org URLs. A lockfile whose resolved field points at a previous mirror host still fails on 12.0.2.
npm 12 also grew the feature that makes this the right lever. The docs now accept a full URL including a path: "resolved URLs whose host and path begin with that prefix will have the entire prefix replaced with the configured registry URL (host and path), without duplicating path segments" (shipped as #9672/#6110 in 12.0.0-pre.2). Point replace-registry-host at the tarball prefix and the URL is rewritten before the origin-and-path check runs, so the exemption applies with allow-remote still at none.
The workaround suggested in #9796 is to repoint your registry at the longer path instead. I would not. That value is referenced by every .npmrc in the org, every scoped-registry auth line, and every CI secret that was scoped to the old URL, so you are changing an org-wide identifier to satisfy a startsWith call in one npm subroutine. replace-registry-host is scoped to URL rewriting and leaves the gate armed. Fix the string comparison where the string comparison happens.
When EALLOWREMOTE is npm working correctly
Genuinely remote dependencies look nothing alike in the log. The homebridge-ecovacs report prints Refusing to fetch "reconnect-core@https://github.com/dodo/reconnect-core/tarball/merged", a transitive dependency of node-xmpp-core four levels down that nobody in the project chose. That is the case npm 12 was built to stop, and the fix belongs upstream or in a vendored publish to your own registry.
Which is why the advice circulating for this error is worse than the error. npm config set allow-remote all shows up in the homebridge-ecovacs thread, in the n8n community forum, and in essentially every AI answer, because any model trained before July describes allow-remote as an opt-in flag. It is a machine-wide setting that re-permits arbitrary tarball URLs for every transitive dependency in every project on that host. A team that hits the #9796 path false positive and bakes allow-remote=all into a Dockerfile has traded a working install for the exposure the release closed, on behalf of a dependency that was never remote.
What to run before your next Node image bump
Do this during the upgrade, not after CI goes red.
- Audit the lockfile first. One command lists every dependency the gate will reject:
jq -r --arg reg "$(npm config get registry)" \
'.packages | to_entries[] | select(.value.resolved != null)
| select(.value.resolved | startswith($reg) | not)
| "\(.key)\t\(.value.resolved)"' package-lock.json
Empty output means the upgrade is uneventful. Hits on a public-registry host are real remote or git dependencies. Hits that are all your own registry with a different path are #9796.
- If the hits are your registry, set the prefix, not the gate.
npm config set replace-registry-host=https://registry.example.com/artifactory/api/npm/npmin the CI image or project.npmrc, withallow-remoteleft atnone. Re-run the jq check to confirm the rewrite covers every hit. - If only the dependency bot fails, look for
--dry-run. #9800 is open. Remove the flag from the Renovate or Dependabot job, or pin that runner to npm 12.0.0 until PR #9818 ships. - If the message says "non-root", you set
allow-remote=rootsomewhere. Check forinstall-strategy=linkedin the same config, which had its own root-tarball break in #9509. - Treat
allow-remote=allas an incident-only, single-command escape hatch. Type it at a prompt to unblock a release, then remove it. Nevernpm config setit on a build image, never commit it. - Re-check in September. #9796 and #9800 are both open against 12.0.x, so anything written about this before then, including this piece, is describing a moving target. The same caution applies to the other 12.x fallout, like the "Cannot find module build/Release" native-module break and the npm audit 410 responses.
FAQ
What does npm error code EALLOWREMOTE mean? npm classified a dependency as type=remote while allow-remote is none, the default since 12.0.0. The classification comes from comparing the resolved tarball URL against your configured registry URL, so a private registry whose tarball path differs from its API path gets misclassified.
Is npm config set allow-remote all safe? No. It re-permits arbitrary tarball URLs for every transitive dependency in every project on that host. Use it once at a prompt during an incident if you have to, never in an image or a committed .npmrc.
Why does EALLOWREMOTE only happen with --dry-run? Issue #9800: when a package in the graph declares bundleDependencies, the real install applies the registry exemption and the preview path does not. Drop the flag or pin to 12.0.0 until PR #9818 lands.
How do I fix this on Artifactory or Nexus? Set replace-registry-host to the tarball URL prefix. npm rewrites the resolved URL before the origin-and-path check, so the exemption applies with the gate still armed.
Can I tell in advance which packages will break? Yes, with the jq pass over package-lock.json above. Run it before the Node image bump.
Comments
Be the first to comment.