Skip to content

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

@manzoorwanijk

Description

@manzoorwanijk

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

Current Behavior

With allow-remote set to none or root (the none default on npm 12), a npm install fails with EALLOWREMOTE on ordinary registry dependencies whenever the configured registry origin differs from the resolved URL origin recorded in the lockfile:

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. Every registry tarball is then misclassified as a genuine remote tarball and blocked.

It affects both the hoisted and the linked install strategy — it is not strategy specific. The same install succeeds when allow-remote=all, or when the configured registry origin matches the resolved origin.

Expected Behavior

A tarball whose resolved URL is a registry-mediated URL (e.g. https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz) should be exempt from the allow-remote gate, identically to when the configured registry happens to share that origin. Configuring a private proxy/mirror should not turn every committed-lockfile registry dependency into a blocked remote tarball.

Steps To Reproduce

REPRO=/tmp/allowremote-proxy-repro
rm -rf "$REPRO" && mkdir -p "$REPRO" && cd "$REPRO"

cat > package.json << 'EOF'
{ "name": "x", "version": "1.0.0", "dependencies": { "minimatch": "3.1.5" } }
EOF

# 1) Generate the lockfile against the public registry, so resolved -> registry.npmjs.org
npm install --registry=https://registry.npmjs.org/ --allow-remote=none --no-audit --no-fund
# -> added 4 packages (configured origin == resolved origin, so the exemption fires)

# 2) Keep the lockfile, drop only node_modules, reinstall against a different registry origin.
#    The committed resolved URLs still point at registry.npmjs.org while the configured
#    registry origin is the mirror -> EALLOWREMOTE.
rm -rf node_modules
npm install --registry=https://registry.npmmirror.com/ --allow-remote=none --no-audit --no-fund
# -> npm error code EALLOWREMOTE

# 3) allow-remote=all bypasses it (confirms the cause):
rm -rf node_modules
npm install --registry=https://registry.npmmirror.com/ --allow-remote=all --no-audit --no-fund
# -> added 4 packages

Important: do not delete package-lock.json before step 2 — regenerating it against the mirror rewrites resolved to the mirror origin, which removes the mismatch and hides the bug. The mismatch requires a lockfile whose resolved origin differs from the configured registry origin (the normal committed-lockfile + proxy/mirror case). The mirror does not need to actually serve the tarball — the gate fires at extract time before/at fetch, purely from the origin comparison.

Root Cause

The registry-tarball exemption at reify.js:716 only overrides allowRemote: 'all' when #isRegistryResolvedTarball(node) returns true. That check at reify.js:849 compares the resolved URL origin against the configured registry origin:

#isRegistryResolvedTarball (node) {
  if (!node.resolved || !node.isRegistryDependency) {
    return false
  }
  try {
    const resolved = new URL(node.resolved)
    const registry = new URL(pickRegistry(npa(node.name), this.options))
    const registryPath = registry.pathname.replace(/\/?$/, '/')
    return resolved.origin === registry.origin &&
      (registryPath === '/' || resolved.pathname.startsWith(registryPath))
  } catch {
    return false
  }
}

When a proxy/mirror is configured, pickRegistry() returns the proxy origin while node.resolved is the canonical registry.npmjs.org URL from the lockfile, so resolved.origin === registry.origin is false. The override is skipped, pacote re-parses the name@URL spec as type=remote, and canUse() (pacote/lib/fetcher.js) throws EALLOWREMOTE.

Verified with a temporary probe in #isRegistryResolvedTarball (since reverted): for a hoisted minimatch install with a proxy registry configured, isRegistryDependency=true, resolvedOrigin=https://registry.npmjs.org, registryOrigin=<proxy origin> -> returns false. Switching the configured registry to https://registry.npmjs.org/ flips it to true and the install succeeds.

This is distinct from #9494/#9495 (store nodes lacking isRegistryDependency) and #9509 (genuine remote root tarballs). Here isRegistryDependency is correctly true; the failure is purely the configured-vs-resolved origin comparison.

Environment

  • npm: 12.0.0-pre.0 (latest); also present on 11.x when allow-remote is set to none/root (default all masks it)
  • Node.js: v24.15.0
  • OS Name: Darwin 25.5.0 (macOS, arm64)
  • System Model Name: Mac17,6
  • Relevant config:
registry = "https://<private-proxy-host>/<path>/"   ; origin differs from lockfile resolved URLs
allow-remote = none                                 ; default on npm 12

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions