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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions