Skip to content

fix(arborist): honor allow-remote=root for root-direct remote tarballs#9510

Merged
owlstronaut merged 1 commit into
npm:latestfrom
manzoorwanijk:fix/linked-allowremote-root-remote-tarball
Jun 8, 2026
Merged

fix(arborist): honor allow-remote=root for root-direct remote tarballs#9510
owlstronaut merged 1 commit into
npm:latestfrom
manzoorwanijk:fix/linked-allowremote-root-remote-tarball

Conversation

@manzoorwanijk
Copy link
Copy Markdown
Contributor

In continuation of our exploration of using install-strategy=linked in the Gutenberg monorepo, which powers the WordPress Block Editor.

Under install-strategy=linked with allow-remote=root, a fresh install fails with EALLOWREMOTE on a genuine remote (non-registry) tarball that is a direct dependency of the project root or a workspace. The standard (hoisted) reifier installs the same dependency fine under allow-remote=root; only the linked strategy rejects it.

npm error code EALLOWREMOTE
npm error Fetching non-root packages of type "remote" have been disabled
npm error Refusing to fetch "@react-native-community/slider@https://raw.githubusercontent.com/wordpress-mobile/react-native-slider/v3.0.2-wp-5/react-native-community-slider-3.0.2-wp-5.tgz"

Why

The allow-remote=root gate is enforced at reify time by computing _isRoot and passing it to pacote.extract in reify.js. A node counts as "root" if it satisfies at least one valid dependency edge from the project root or a workspace, which is derived from node.edgesIn. In the linked strategy, store nodes are IsolatedNode instances with no edgesIn to recompute root-ness from, so _isRoot was always false, every remote tarball was treated as non-root, and pacote refused even a legitimate root/workspace direct dependency.

This is the sibling of the registry-tarball fix (#9495). That change carried isRegistryDependency onto store nodes so the registry-tarball exemption still applied; this change carries the analogous root-ness signal so the allow-remote=root gate resolves correctly for genuine remote tarballs, which are not registry-mediated and so do not qualify for the registry exemption.

This only widens allow-remote=root. allow-remote=none still rejects all remote specs (pacote refuses regardless of _isRoot), and a genuinely transitive remote dependency still fails the resolution-layer #checkAllow gate during ideal-tree construction. Hoisted is unaffected because its nodes retain real edgesIn.

How

Carry a root-ness flag from the source tree node onto the store node, rather than weakening the guard:

  1. IsolatedNode gains an isRootDependency field (default false), settable from constructor options.
  2. #externalProxy computes isRootDependency from the real tree node's edgesIn using the same predicate the reifier applies (a valid edge from the project root or a workspace).
  3. #generateChild passes it through to the store IsolatedNode.
  4. The _isRoot computation in reify.js falls back to node.isRootDependency. Hoisted nodes do not have the property, so they fall through to the existing edge-based check unchanged.

References

Fixes #9509
Follows-up #9495

@manzoorwanijk manzoorwanijk marked this pull request as ready for review June 8, 2026 17:05
@manzoorwanijk manzoorwanijk requested review from a team as code owners June 8, 2026 17:05
@manzoorwanijk
Copy link
Copy Markdown
Contributor Author

@owlstronaut, how do we ensure that every feature implementation in npm takes isolated mode into consideration?

@owlstronaut
Copy link
Copy Markdown
Contributor

@manzoorwanijk it's a good question. Maybe we can find a way to parameterize tests over install strategies? Open to suggestions, because you are right, this will keep happening

@owlstronaut owlstronaut merged commit d70e116 into npm:latest Jun 8, 2026
18 checks passed
@manzoorwanijk manzoorwanijk deleted the fix/linked-allowremote-root-remote-tarball branch June 8, 2026 17:31
@manzoorwanijk
Copy link
Copy Markdown
Contributor Author

Maybe we can find a way to parameterize tests over install strategies?

Thank you for the pointer. I will explore a few options to see what we can do about it.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

🎉 Backport to release/v11 created: #9511

manzoorwanijk added a commit to manzoorwanijk/npm-cli that referenced this pull request Jun 8, 2026
npm#9510)

In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.

Under `install-strategy=linked` with `allow-remote=root`, a fresh
install fails with `EALLOWREMOTE` on a genuine remote (non-registry)
tarball that is a direct dependency of the project root or a workspace.
The standard (hoisted) reifier installs the same dependency fine under
`allow-remote=root`; only the linked strategy rejects it.

```
npm error code EALLOWREMOTE
npm error Fetching non-root packages of type "remote" have been disabled
npm error Refusing to fetch "@react-native-community/slider@https://raw.githubusercontent.com/wordpress-mobile/react-native-slider/v3.0.2-wp-5/react-native-community-slider-3.0.2-wp-5.tgz"
```

## Why

The `allow-remote=root` gate is enforced at reify time by computing
`_isRoot` and passing it to `pacote.extract` in `reify.js`. A node
counts as "root" if it satisfies at least one valid dependency edge from
the project root or a workspace, which is derived from `node.edgesIn`.
In the linked strategy, store nodes are `IsolatedNode` instances with no
`edgesIn` to recompute root-ness from, so `_isRoot` was always `false`,
every remote tarball was treated as non-root, and pacote refused even a
legitimate root/workspace direct dependency.

This is the sibling of the registry-tarball fix (npm#9495). That change
carried `isRegistryDependency` onto store nodes so the registry-tarball
exemption still applied; this change carries the analogous root-ness
signal so the `allow-remote=root` gate resolves correctly for genuine
remote tarballs, which are not registry-mediated and so do not qualify
for the registry exemption.

This only widens `allow-remote=root`. `allow-remote=none` still rejects
all remote specs (pacote refuses regardless of `_isRoot`), and a
genuinely transitive remote dependency still fails the resolution-layer
`#checkAllow` gate during ideal-tree construction. Hoisted is unaffected
because its nodes retain real `edgesIn`.

## How

Carry a root-ness flag from the source tree node onto the store node,
rather than weakening the guard:

1. `IsolatedNode` gains an `isRootDependency` field (default `false`),
settable from constructor options.
2. `#externalProxy` computes `isRootDependency` from the real tree
node's `edgesIn` using the same predicate the reifier applies (a valid
edge from the project root or a workspace).
3. `#generateChild` passes it through to the store `IsolatedNode`.
4. The `_isRoot` computation in `reify.js` falls back to
`node.isRootDependency`. Hoisted nodes do not have the property, so they
fall through to the existing edge-based check unchanged.

## References

Fixes npm#9509
Follows-up npm#9495
owlstronaut pushed a commit that referenced this pull request Jun 8, 2026
#9511)

Backport of #9510 to `release/v11`.

Co-authored-by: Manzoor Wani <manzoorwani.jk@gmail.com>
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] install-strategy=linked rejects root/workspace remote tarball deps with EALLOWREMOTE under allow-remote=root

2 participants