[BUG] `npm link` warns about install scripts that no allowScripts mechanism can cover
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 10.1k
- Forks
- 4.7k
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 19
Description
Is there an existing issue for this?
I searched open and closed issues for link + allowScripts. The closest is #9681 (denying scripts drops .bin links), which is a different code path.
This issue exists in the latest npm version
Reproduced on npm 11.17.0.
Current Behavior
npm link (no arguments — the "publish this folder to the global prefix" form) warns that the linked package's install scripts are not covered by allowScripts:
npm warn allow-scripts 1 package has install scripts not yet covered by allowScripts:
npm warn allow-scripts @fixture/link-me@1.0.0 (prepare: node -e "...")
npm warn allow-scripts
npm warn allow-scripts Run `npm approve-scripts --allow-scripts-pending` to review, or `npm approve-scripts <pkg>` to allow.
The warning is unactionable. The script does run, but no allowScripts mechanism can cover it, and the remediation the warning prints does not work:
| attempt | warning | prepare ran |
|---|---|---|
| baseline | yes | yes |
allowScripts in package.json, bare name |
yes | yes |
allowScripts in package.json, name@version |
yes | yes |
allow-scripts=<name> in .npmrc |
yes | yes |
npm link --allow-scripts=<name> |
yes | yes |
npm link --ignore-scripts |
no | no |
$ npm approve-scripts --allow-scripts-pending
No packages with unreviewed install scripts.
So a project that has adopted allowScripts and wants a clean, warning-free CI log has exactly one lever, --ignore-scripts, which also skips the script rather than approving it.
Expected Behavior
Either of:
npm linkresolves anallowScriptspolicy like every other reify path, soallowScripts/--allow-scripts/.npmrccan approve the linked package andnpm approve-scriptscan see it; or- the linked package is treated as not-reviewable and excluded from the advisory warning entirely (it is the user's own project, the same package
npm approve-scriptsrefuses to list).
Whichever is chosen, the warning and the remediation should agree with each other.
Steps To Reproduce
work=$(mktemp -d); mkdir -p "$work/pkg" "$work/global"
cat > "$work/pkg/package.json" <<'JSON'
{
"name": "@fixture/link-me",
"version": "1.0.0",
"bin": { "fixture": "./bin.mjs" },
"scripts": { "prepare": "node -e \"console.error('PREPARE RAN')\"" },
"allowScripts": { "@fixture/link-me": true }
}
JSON
printf '#!/usr/bin/env node\nconsole.log("ok");\n' > "$work/pkg/bin.mjs"
cd "$work/pkg" && npm_config_prefix="$work/global" npm link
# -> warns, despite the allowScripts entry above
npm_config_prefix="$work/global" npm approve-scripts --allow-scripts-pending
# -> "No packages with unreviewed install scripts."
A script that walks the whole matrix in the table above is here: https://github.com/link-assistant/hive-mind/blob/main/experiments/npm-link-allow-scripts.sh
Root cause
Two independent causes, both in the no-argument path.
1. linkPkg() never resolves a policy. lib/commands/link.js — linkInstall() (the npm link <pkg> form) resolves the policy and threads it into both the Arborist constructor and reify():
const { policy: allowScriptsPolicy } = await resolveAllowScripts(this.npm)
const localArb = new Arborist({ ..., allowScripts: allowScriptsPolicy })
await localArb.reify({ ..., allowScripts: allowScriptsPolicy })
linkPkg() (the no-argument form) does not, so Arborist is constructed with no policy and every candidate is "unreviewed" no matter what the user configures:
async linkPkg () {
const wsp = this.workspacePaths
const paths = wsp && wsp.length ? wsp : [this.npm.prefix]
const add = paths.map(path => `file:${path}`)
const globalTop = resolve(this.npm.globalDir, '..')
const Arborist = require('@npmcli/arborist')
const arb = new Arborist({
...this.npm.flatOptions,
Arborist,
path: globalTop,
global: true,
})
await arb.reify({ add })
await reifyFinish(this.npm, arb)
}
2. Even with a policy, the entry could not match. lib/script-allowed.js matches a file: node exactly on node.resolved, which for this node is a relative path from the global prefix (e.g. file:../../../../pkg) — it varies with the prefix and the source directory, so there is no stable string a user could put in allowScripts. This is why --allow-scripts=@fixture/link-me also fails: the name never enters the comparison.
Relatedly, collectUnreviewedScripts in @npmcli/arborist/lib/unreviewed-scripts.js skips isProjectRoot, isWorkspace, isLink and inBundle. In the global tree the Link node is skipped, but its target — the user's own project directory — is none of those four, so it is reported. That target is exactly the package npm approve-scripts declines to list, which is the source of the contradiction.
Suggested fix
Minimal, keeping the two halves separable:
-
In
linkPkg(), mirrorlinkInstall():const { policy: allowScriptsPolicy } = await resolveAllowScripts(this.npm) const arb = new Arborist({ ...this.npm.flatOptions, Arborist, path: globalTop, global: true, allowScripts: allowScriptsPolicy }) await arb.reify({ add, allowScripts: allowScriptsPolicy })Note
this.npm.globalis false duringnpm link(it throwsELINKGLOBALif set), soresolveAllowScriptsreads the projectpackage.json— which is the right layer here, since the package being linked is the project. -
In
collectUnreviewedScripts, also skip a node that is the target of a link added from the local prefix — or, equivalently, havelinkmark it. It is the user's own source tree, not a third-party dependency, and it is already exempt in the sibling checks:isProjectRootis skipped for exactly this reason, and it only stops applying because the tree being reified is the global one.
Fix 2 alone removes the contradiction. Fix 1 alone makes the warning reviewable but only if the resolved matching in cause 2 is also addressed. If only one lands, fix 2 is the one that makes the current output self-consistent.
Workaround
npm link --ignore-scripts, accepting that the package's own prepare no longer runs during the link.
Environment
- npm: 11.17.0
- Node.js: v20.20.2 (also seen on 24.x)
- OS: Ubuntu (Linux 6.8)
- platform: linux
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the no-argument linkPkg() and linkInstall() paths in lib/commands/link.js, then read lib/script-allowed.js and @npmcli/arborist/lib/unreviewed-scripts.js. Run the reproduction matrix to compare policy resolution, link targets, and warning behavior. Done means npm link either honors the configured policy or omits the linked project from the advisory warning, with npm approve-scripts reporting consistently.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100