Skip to content

[BUG] A user/local .npmrc allow-scripts setting is forwarded to an inner npm install spawned by npm run-script and fails with EALLOWSCRIPTS #9912

Description

@VariableVince

Summary

Setting allow-scripts in a user or local (non-project) .npmrc makes a project's own package.json script fail with:

npm error code EALLOWSCRIPTS
npm error --allow-scripts is not allowed in project-scoped installs.
Add the entries to the "allowScripts" field in package.json, or to .npmrc, instead.

This happens even though the user never passed --allow-scripts — the value lives in a persistent .npmrc exactly as npm config set allow-scripts=<pkg> --location=user writes it — and even when the script explicitly passes --ignore-scripts.

The project package.json in question contains scripts like:

"scripts": {
  "inst": "npm ci --ignore-scripts",
  "perf": "node tests/ensure-playwright.mjs"
}
  • Running npm ci --ignore-scripts directly in the workspace: ✅ succeeds (no error).
  • Running npm run inst (which runs that exact same command): ❌ fails with EALLOWSCRIPTS.
  • Running node tests/ensure-playwright.mjs directly: ✅ succeeds.
  • Running npm run perf (which runs that exact same node … command, and that script internally spawns an npm install): ❌ fails with EALLOWSCRIPTS.

The only difference between the working and failing invocations is whether the npm install/npm ci process is reached through npm run-script. npm run exports the resolved npm config as npm_config_* environment variables for the lifecycle script's child process. Because the outer process read allow-scripts from a user/local .npmrc, the inner npm install/npm ci inherits npm_config_allow_scripts and reads it via its env source. resolveAllowScripts groups the cli and env sources together and rejects any policy from them in a project-scoped install, so the inner install aborts — even though the value originated in a .npmrc, not a command-line flag, and even though the inner install was told --ignore-scripts.

This is the same root cause as #9783 (git-dep preparation), just reached through a different "shell-out to an inner npm install" path: an ambient npm_config_allow_scripts from a persistent .npmrc is misclassified as a forbidden CLI/env policy.

Environment

  • npm: 12.0.2
  • node: v26.6.0
  • OS: Windows 11
  • allow-scripts entry in user .npmrc: ["opencode-ai"]
  • node executable: C:\nvm4w\nodejs\node.exe
  • npm local prefix = C:\Users\xxx\xxx\xxx
  • HOME = C:\Users\xxx
  • cwd = C:\Users\xxx\xxx\xxx (a real project workspace, not global context)

How the offending setting gets there

This is the documented, recommended workflow. Installing a global package with an install script prints:

npm warn allow-scripts <pkg>@x.y.z (postinstall: node install.cjs)
npm warn allow-scripts Run `npm install -g --allow-scripts=<pkg>` to allow
  these scripts once, or `npm config set allow-scripts=<pkg> --location=user`
  to allow them for all global installs.

Following the second suggestion writes allow-scripts=<pkg> to the user .npmrc. From then on, npm run in any project on the machine that triggers an inner npm install fails — even ones that do not run any scripts and even ones that explicitly request --ignore-scripts.

Minimal, self-contained reproduction (no network / external repo required)

R=$(mktemp -d); cd "$R"
# A user/local .npmrc with the entry exactly as
# `npm config set allow-scripts=... --location=user` would write it.
echo "allow-scripts=whatever" > "$R/userrc"

mkdir app && cd app
npm init -y >/dev/null
# A script that spawns an inner `npm install` (mirrors `npm ci` / ensure-playwright.mjs).
mkdir inner && (cd inner && npm init -y >/dev/null)
cat > package.json <<'EOF'
{ "name": "app", "version": "1.0.0",
  "scripts": { "inst": "cd inner && npm install" } }
EOF

# (A) Direct inner install — works
(cd inner && npm install --userconfig "$R/userrc"); echo "direct exit: $?"

# (B) Same inner install, but reached through `npm run-script` — fails
npm run inst --userconfig "$R/userrc"; echo "npm-run exit: $?"

Actual result (B)

npm error code EALLOWSCRIPTS
npm error --allow-scripts is not allowed in project-scoped installs.
Add the entries to the "allowScripts" field in package.json, or to .npmrc, instead.

Expected result

Both (A) and (B) complete. An allow-scripts value that lives in a persistent user/local .npmrc is, by definition, not a command-line flag. When a script is run inside a project (notably a script defined in that project's own package.json), npm should ignore non-project (user/local) .npmrc allow-scripts rather than forwarding it into the child npm install as a forbidden env-layer policy — and it must never abort a command that was explicitly told --ignore-scripts.

Notes on the trigger

  • The failure requires the inner process to be an npm install/npm ci reached through npm run-script (or any lifecycle script that shells out to npm). The reproduction above uses a trivial inner install with no scripts at all, proving the policy check itself — not any real script execution — is what trips.
  • It is independent of where the setting lives: reproduced identically with the entry in the user .npmrc and the global (etc/npmrc) file. There is no persistent-config location that both silences the global-install script warning and leaves npm run working.
  • It reproduces even when the outer/script command passes --ignore-scripts, because the EALLOWSCRIPTS throw at resolveAllowScripts happens purely from the presence of the env-layer policy, before --ignore-scripts is ever consulted.

Root cause (source pointers)

  • lib/utils/resolve-allow-scripts.js:107 throws EALLOWSCRIPTS when a policy is found in the cli/env sources and the install is not global and not skipProjectConfig. The grouping:

    const cliPolicy = policyFromSources(npm, ['cli', 'env'])
    ...
    if (cliPolicy && !npm.global && !skipProjectConfig) {
      throw Object.assign(new Error(
        '--allow-scripts is not allowed in project-scoped installs. ' +
        'Add the entries to the "allowScripts" field in package.json, ' +
        'or to .npmrc, instead.'), { code: 'EALLOWSCRIPTS' })
    }
  • npm run-script exports the resolved config into the child process's environment as npm_config_* variables (so lifecycle scripts can read e.g. process.env.npm_config_registry). A allow-scripts value sourced from a user/local .npmrc therefore arrives in the inner npm install as an env-layer value, which policyFromSources(npm, ['cli', 'env']) reads and resolveAllowScripts treats as a forbidden CLI flag.

This is the same defect described in #9783, where the ambient npm_config_allow_scripts instead arrives via the git-dep preparation inner install. The fix space is identical; npm run-script is simply an additional path that injects the env-layer value.

Suggested fixes

  1. (Preferred) When npm run-script (and the git-dep preparation path from [BUG] A user/global .npmrc allow-scripts setting is forwarded to git-dependency preparation as an env-layer policy and fails the install with EALLOWSCRIPTS #9783) spawns an inner npm install, do not forward npm_config_allow_scripts from the ambient environment into the child. The inner install already receives a deliberately curated flag set, so script policy for the inner build should be passed explicitly (or omitted) rather than inherited ambiently. This leaves the cli/env grouping in resolveAllowScripts intact per RFC 868, while fixing the ambient-env inheritance that misclassifies a .npmrc value as a CLI policy.
  2. Alternatively, in npm run-script, strip allow-scripts from the exported npm_config_* env when the script is project-scoped, so a user/local .npmrc policy is not re-applied to the project's own inner install.
  3. At minimum, fix the error message: when the value came from a persistent .npmrc (directly or via npm_config_* env inheritance), do not tell the user to "add the entries to .npmrc" — that is where it already is, and the advice sends them in a circle. The message should also not fire when the offending command was invoked with --ignore-scripts.

Related

Workaround (for anyone hitting this)

Do not persist allow-scripts in a user/global .npmrc. Instead pass it only at the moment of the global install that needs it:

npm install -g <pkg> --allow-scripts=<pkg>

This approves the global install's script without leaving a persistent setting that later breaks npm run in unrelated projects. A project-scoped workaround is to move the policy into the project's own package.json allowScripts field (or project .npmrc) — but note that, per the linked #9783, the project-own-.npmrc path can still be hit via the env inheritance, so the robust fix is to avoid the ambient user/local entry entirely.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions