You did the responsible thing. You turned on npm's new install-script allowlist so a freshly-published malicious version can't run postinstall code on your build box. Then you cloned a repo, ran npm install, and it died on a flag you never typed:
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.
Read that last line twice. It tells you to move a setting to where you already put it. That is not a helpful error, that is a bug talking. Here is what actually broke and how to get a green build back without gutting the control you just added.
What the error really is
The feature is real and it's good. As of npm 11.17.0 and the npm 12.x line, install scripts are gated behind an explicit allowlist: an allowScripts field in package.json, managed with npm approve-scripts. If you shipped through the earlier rollout and hit the "N packages have install scripts not yet covered by allowScripts" warning, that's the same feature. I wrote up that warning and its fix in the npm allowScripts install-scripts guide, and it's worth reading first because this is a different failure with a different cause. That one was a warning nudging you to approve. This one is a hard EALLOWSCRIPTS stop.
EALLOWSCRIPTS almost never comes from your command line. It fires while npm prepares a git dependency: a package pulled from git+https://… or a GitHub shorthand that carries a prepare build step. Per npm/cli issue #9783, npm prepares that dependency by spawning an inner npm install. The outer config gets forwarded to that child as npm_config_* environment variables. The inner process then reads an allow-scripts value that came from your persistent user-level .npmrc, decides it's a forbidden project-scoped CLI policy, and kills the whole install. The value it's complaining about is an environment variable it inherited from itself. Nobody passed --allow-scripts on that command.
So the message is wrong in a specific way. It treats an env-inherited setting as if you typed it as a flag, then tells you to relocate it to a place it's already living. npm's own maintainers list "correct the error message to avoid contradictory guidance" as a minimum fix in #9783. When the tool admits the text is contradictory, stop trusting the text.
Why this bites teams that did the right thing
The trap is that the security guidance builds the failure for you. npm's docs and its warnings point you toward persisting the allowlist so it survives across projects:
npm config set allow-scripts=<pkg> --location=user
Persist it that way, then clone any repo that pulls a package from git with a prepare step, and npm install dies with EALLOWSCRIPTS. You never scoped anything to that project. The config you set globally, on purpose, to be safer, is exactly what leaks into the git-dependency preparation and trips the check.
For a platform or DevSecOps team the shape of this is familiar. A pipeline goes red after a routine npm bump. The error blames a flag nobody in the repo passed. Someone greps the codebase for --allow-scripts, finds nothing, and burns an afternoon. Then the "obvious" fix surfaces: delete the .npmrc entry, watch the build go green, move on. That's the second-order bite. The hardening control does its job on the outer install and breaks the inner one, so the fastest path to green quietly removes a supply-chain defense you added deliberately. It's the same pattern I keep flagging: a control that works right up until it makes someone rip it out. The npm Trusted Publishing analysis is the cousin case, a defense that ships and still leaves the attack path open.
And the exposed population is growing, not shrinking, because the npm 12 install-script lockdown is reaching more teams every week.
The versions are current, not historical
This matters when you're deciding whether to wait it out. #9783 reproduces on npm 11.17.0, 12.0.1, and the main branch as of this writing. It is live. The reproduction is small: point at a git dependency that has a prepare script, set allow-scripts in a user-level .npmrc, then npm install a consumer of it. You don't need an exotic setup. A GitHub-shorthand dependency with a build step is enough.
The fix npm wants is upstream: strip npm_config_allow_scripts from the child environment during git-dependency preparation, and stop conflating env-inherited values with CLI flags. Until that release lands, the workaround is yours to run.
The sibling bug that makes approve-scripts untrustworthy
There's a related inconsistency worth knowing before you lean on npm approve-scripts as your source of truth. In npm/cli #9562 (npm 11.17.0, Node 24.16.0), npm ci --strict-allow-scripts rejected fsevents@2.3.3 with 1 package(s) have install scripts not covered by allowScripts. But npm approve-scripts --allow-scripts-pending never listed fsevents at all, because it's optional: true and os: ["darwin"]. The tool that builds your allowlist and the command that enforces it disagree.
Read what that does to a workflow. You run the approval tool on your Mac, it shows a clean list, you commit package.json, you feel done. Then CI on Linux (or a different OS matrix) rejects an optional dep the approval tool never surfaced. A "clean" approval run is not proof the enforcement pass will accept the same tree on another platform. For platform-specific optional deps, single-machine approval is a false positive.
What approve-scripts can and can't reach
One more boundary. Per the npm docs and issue #9463, npm approve-scripts maintains the allowScripts field inside a project's package.json. Run it against a global install and it errors with EGLOBAL. So for global CLIs and npx, you cannot pre-build a committed allowlist. You approve at install time with install-level flags instead. That's not a bug, it's a scope limit, but it catches people who assume one allowlist mechanism covers every install path. It doesn't. Project-scoped deps get allowScripts in package.json; global tools and npx get a flag on the install command.
If you're already treating fast-moving dependencies as a threat surface, this pairs with dependency cooldowns as a defense against fast supply-chain attacks: the allowlist controls what runs, the cooldown controls when a new version is even eligible to run.
How to get a green build back without losing the control
Work this in order. The first two steps fix the outage; the rest keep you from trading security for green.
When
EALLOWSCRIPTShits a normalnpm install, hunt for a git dependency first, not a stray flag. Openpackage.jsonand scan every dependency forgit+, a raw GitHub URL, or anowner/reposhorthand. The one with apreparescript is where the inner install fires. Grepping your source for--allow-scriptsis a dead end; the value is an inherited env var, not a flag in the repo.Stop persisting
allow-scriptsin a user or global.npmrc. That is the leak. Per #9783, remove the persistent setting so it can't be forwarded asnpm_config_allow_scriptsinto git-dependency preparation, and pass--allow-scriptsonly on the specific install that genuinely needs it. Do not delete your allowlist to make the build pass. Those are different things: one is a persisted global env policy, the other is your durable per-project control.Keep the project-scoped
allowScriptsfield as your real control. It lives inpackage.json, it's managed bynpm approve-scripts, it survives this bug, and it shows up in a pull request where a reviewer can actually see what you allowed. Global.npmrcpolicy gives you none of that and triggers the failure. Prefer the committed field.Never trust a single-platform approval for optional deps like
fsevents. Cross-checknpm approve-scripts --allow-scripts-pendingagainst whatnpm ci --strict-allow-scriptsactually rejects on each target OS, per #9562. If your CI matrix spans macOS and Linux, run the enforcement pass on both before you call the allowlist complete.For global tools and
npx, bake the flag into the install command.approve-scriptsreturnsEGLOBALthere (#9463), so don't expect a persisted allowlist to cover them. Approve at install time.Pin your npm version and watch #9783. The genuine fix is upstream env-scrubbing. Until a release ships it, treat the workaround above as standing policy and re-test after every npm bump, because a version change is exactly what turns this back on. The same lockdown discipline pays off elsewhere: killing static credentials in CI, for instance, follows the same pattern in GitHub Actions OIDC to AWS.
The thing to hold onto: this error is the control working in the wrong place, not proof the control is wrong. Move the setting off the global env, keep it in package.json, and you get both a green build and the supply-chain gate you turned on in the first place.
Sources
- https://github.com/npm/cli/issues/9783
- https://github.com/npm/cli/issues/9562
- https://github.com/npm/cli/issues/9463
- https://docs.npmjs.com/cli/v11/commands/npm-approve-scripts/
Originally published at indragustiprasetya.com
Top comments (0)