Skip to content

[BUG] --dry-run fails with EALLOWREMOTE on registry tarballs for packages that declare bundleDependencies #9800

Open

Description

@kelnei

Is there an existing issue for this?

  • I have searched the existing issues

The closest reports have a different cause: #9796 (registry serving tarballs from a path outside the configured registry prefix) and the closed exemption fixes #9494 / #9548 / #9509. This one happens against the default registry.npmjs.org and only under --dry-run. #9187 is a separate --dry-run bug (side effects / output).

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

With npm 12's default allow-remote=none, npm install --dry-run refuses a registry-hosted tarball when the target package — or a package in its graph — declares bundleDependencies:

$ npm i --dry-run npm@11.10.0
npm error code EALLOWREMOTE
npm error Fetching packages of type "remote" have been disabled
npm error Refusing to fetch "https://registry.npmjs.org/npm/-/npm-11.10.0.tgz"

The identical command without --dry-run succeeds. The refused URL is a registry.npmjs.org tarball and registry is the default, so the registry-tarball exemption should apply.

command result
npm i --dry-run npm@11.10.0 EALLOWREMOTE
npm i npm@11.10.0 added 1 package
npm i --dry-run @tailwindcss/oxide@4.3.3 EALLOWREMOTE
npm i @tailwindcss/oxide@4.3.3 added 2 packages
npm i --dry-run lodash@4.17.21 (control, no bundleDependencies) added 1 package

@tailwindcss/oxide is the realistic case: it bundles nothing itself, but its optional @tailwindcss/oxide-wasm32-wasi dep declares bundleDependencies — and since that package is cpu: ["wasm32"], a real install skips it entirely, which is presumably why only the dry-run path trips.

Practical impact: npm update --dry-run to preview a bump fails in any project whose tree contains such a package (anything on Tailwind 4, for instance), while the real npm update completes fine. In a monorepo that makes the preview unusable without --allow-remote=all, which is exactly the guard one doesn't want to relax to inspect a change.

Not caused by min-release-age: reproduces with --min-release-age=0 explicitly passed.

Expected Behavior

--dry-run should apply the same registry-tarball allow-remote exemption a real install does. Declaring bundleDependencies shouldn't cause a registry spec to be classified as type remote.

Steps To Reproduce

$ mkdir /tmp/eallowremote && cd /tmp/eallowremote && npm init -y

# both fail with EALLOWREMOTE
$ npm i --dry-run npm@11.10.0
$ npm i --dry-run @tailwindcss/oxide@4.3.3

# both succeed
$ npm i npm@11.10.0
$ npm i @tailwindcss/oxide@4.3.3

# control: no bundleDependencies, dry-run is fine
$ npm i --dry-run lodash@4.17.21

Environment

  • npm: 12.0.1
  • Node.js: 24.18.0
  • OS Name: Linux 7.0.11 (x86_64)
  • System Model Name: n/a
  • npm config:
; user config (registry auth lines omitted)
min-release-age = 7
min-release-age-exclude = ["@fullbeaker/*"]
registry = "https://registry.npmjs.org/"

; relevant npm 12 defaults, not overridden
allow-remote = "none"
allow-git = "none"

Activity

  1. shooley-apco commented on Jul 28, 2026

    @shooley-apco

    Independently reproduced on Windows 11 / npm 12.0.1 / Node v22.23.1, against the default registry.npmjs.org. I arrived at this from the other direction and can point at the call site.

    --dry-run isn't the only trigger — --package-lock-only is too. reify.js builds its ideal-tree options as:

    complete: this.options.packageLockOnly || this.options.dryRun,

    So anything that sets either flag is affected. In a Tailwind 4 monorepo that makes npm dedupe fail outright, not just previews:

    $ npm dedupe --package-lock-only
    npm error code EALLOWREMOTE
    npm error Fetching packages of type "remote" have been disabled
    npm error Refusing to fetch "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz"

    The failing call site is the ideal-tree "crack open", not the reify extract:

    Error: Fetching packages of type "remote" have been disabled
        at canUse (.../pacote/lib/fetcher.js:485:5)
        at FetcherBase.get (.../pacote/lib/fetcher.js:502:7)
        at Object.extract (.../pacote/lib/index.js:19:34)
        at .../@npmcli/arborist/lib/arborist/build-ideal-tree.js:1026:22
        at withTempDir (.../@npmcli/fs/lib/with-temp-dir.js:21:20)
        at async #buildDepStep (.../build-ideal-tree.js:1025:7)
        at async Arborist.buildIdealTree (.../build-ideal-tree.js:188:7)
        at async Arborist.reify (.../reify.js:118:5)
        at async Dedupe.exec (.../lib/commands/dedupe.js:54:5)
    

    That's #buildDepStep's crackOpen branch, whose own comment explains why it only happens here:

    // if we're loading a _complete_ ideal tree, for a --package-lock-only
    // installation for example, we have to crack open the tarball and
    // look inside if it has bundle deps.  note that this is
    // not necessary during a reification, because we just update the
    // ideal tree by reading bundles in place.
    const crackOpen = this.#complete && node !== this.idealTree &&
      node.resolved && hasBundle && !node.inert
    if (crackOpen) {
      await cacache.tmp.withTmp(this.cache, opt, async path => {
        await pacote.extract(node.resolved, path, {
          ...opt, Arborist, resolved: node.resolved, integrity: node.integrity,
        })

    node.resolved is a bare URL, so npa classifies the spec as type remote and pacote's gate fires. reify.js already handles exactly this hazard for its own extract:

    // pacote's npa re-parses our `name@URL` spec as type=remote, so allowRemote would mis-fire on registry tarballs.
    // Override only when we can prove the URL is registry-mediated; see #isRegistryResolvedTarball.
    ...(this.#isRegistryResolvedTarball(node) ? { allowRemote: 'all' } : {}),

    The crack-open call site has no equivalent. A code search for isRegistryResolvedTarball across this repo returns only workspaces/arborist/lib/arborist/reify.js, so the exemption added in #9348 / #9495 / #9510 / #9550 never reached build-ideal-tree.js — which would explain why this survived those four fixes.

    One correction to the issue body: the reason a real install is unaffected isn't that it skips the cpu: ["wasm32"] package — it's the #complete gate. A reification reads bundles in place (per the comment above) and never cracks the tarball open, so the platform mismatch is incidental. That also predicts the control case correctly: lodash passes because hasBundle is false, not because of anything platform-related.

    Two smaller notes:

    • --allow-remote=root is not a usable middle ground. This extract passes no _isRoot, so canUse sees isRoot: false and refuses with the non-root wording regardless. all is the only value that unblocks it, which sharpens your point about relaxing the guard just to inspect a change.
    • The failure is unnecessarily fatal for optional deps. #nodeFromSpec routes #checkAllow rejections through #failureNode so optional-dependency semantics apply; the crackOpen extract isn't wrapped, so a transitive, platform-mismatched optional dep hard-fails the command instead of being dropped.

    Line numbers are from the published 12.0.1 bundle and may be offset slightly from main.

  2. chgeo commented on Aug 6, 2026

    @chgeo

    Can this be fixed soon? Two patch candidates are available.

  3. coderbyheart commented on Aug 17, 2026

    @coderbyheart

    Thanks for the detailed report. I have encountered the same issue for projects that depend on @aws-cdk/cloud-assembly-schema.

  4. self-assigned this
    on Aug 31, 2026
  5. TiBata commented on Sep 17, 2026

    @TiBata

    Adding test results for the two candidate PRs, since neither has been reviewed yet.

    As noted above, --package-lock-only is also a trigger. That is the command Renovate runs for lock file maintenance, so Renovate-managed projects depending on Tailwind CSS 4 (@tailwindcss/oxide) get an "Artifact update problem" as soon as Renovate resolves npm 12.

    Minimal repro (Node 24.21.0):

    echo '{"name":"repro","version":"1.0.0","dependencies":{"@tailwindcss/oxide":"4.3.3"}}' > package.json
    npx npm@12.0.2 install --package-lock-only
    # npm error code EALLOWREMOTE
    # npm error Refusing to fetch "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz"
    

    Both PRs run from source against this repro:

    install --package-lock-only (no lockfile) install --dry-run install
    main @ 834408e EALLOWREMOTE EALLOWREMOTE ok
    #9818 @ e30c58b ok ok ok
    #9819 @ 6ddc878 ok ok ok
    npm 11.19.0 ok ok ok

    On a real project (Astro + Tailwind 4, 437 lockfile entries), the lockfile regenerated from scratch by either PR is identical to npm 11.19.0's: same entries, versions and integrity. Either PR would unblock these users. The current workarounds are pinning npm to 11 via engines.npm/packageManager, or setting allow-remote=all, which gives up the new default.

  6. MikeMcC399 commented on Sep 17, 2026

    @MikeMcC399
    Contributor

    @TiBata

    Thanks for tying this in to Renovate! We're also seeing this in a public repo - see cypress-io/github-action#1900 (comment)

    I came up with the same conclusion as you regarding the workarounds and I'm going for the allow-remote=all right now because it sets the default the same as npm <12.

  7. 10 remaining items

  8. added 5 commits that reference this issue on Oct 12, 2026
  9. added a commit that references this issue on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions