An npm dependency can execute lifecycle code while a developer or CI runner installs the project. That supports legitimate native compilation, binary selection, code generation, and setup work. It also means “download dependencies” can become “run dependency-controlled code” before the application starts.
The right policy is not automatically “block every script” or “trust the lockfile.” Identify which jobs require lifecycle execution, review the packages that receive that privilege, constrain the runner, and keep a reproducible no-script path for tasks that do not need builds. npm’s current lifecycle documentation lists preinstall, install, postinstall, and other events in the installation sequence. Treat those events as executable behavior, not passive metadata.

The npm 11 distinction: inventory is not enforcement
npm 11 adds an allowScripts field and an npm approve-scripts workflow for recording reviewed dependencies. The current npm approve-scripts documentation says the command manages approvals for dependencies with install scripts. It also gives a critical limitation for npm 11.19.0: the field is advisory in the current release, so unreviewed scripts still run by default while npm reports packages awaiting review. A future release may block unreviewed scripts, but a future plan is not a control you can count on today.
| Choice | What it does now | Useful when | Main limitation |
|---|---|---|---|
Normal npm ci | Clean install from the existing lockfile; applicable lifecycle behavior runs | A controlled build genuinely needs package setup | A lockfile freezes resolution; it does not disable execution |
npm ci --ignore-scripts | Skips scripts declared in package manifests during installation | Lint, inventory, or static checks that operate without built add-ons | Packages needing compilation or setup may fail later |
allowScripts plus review | Records reviewed or denied packages and surfaces pending script owners | Building a team inventory before stronger enforcement arrives | In npm 11.19.0, approvals are advisory, not default-deny enforcement |
The npm configuration reference says ignore-scripts=true prevents scripts in package manifests from running, while commands explicitly intended to run a script—such as npm test or npm run—still run the requested script without its pre- and post-hooks. A job that installs with --ignore-scripts can therefore execute project code later when the workflow explicitly invokes it.
Inventory the actual execution surface
Do not approve a package merely because its name is familiar. Start from a clean branch and capture four things without running a newly introduced script on a privileged workstation:
- package and resolved version;
- lifecycle hook names it declares;
- command and files the hook invokes;
- credentials, filesystem paths, and network destinations available when it runs.
A direct dependency may bring a transitive package with a lifecycle hook. A package from a Git URL or local path can have different preparation behavior from a registry archive. npm’s lifecycle reference notes that prepare applies in several packaging and installation contexts and that lifecycle order differs by command. Review the exact command your workflow uses.

For a new or changed dependency, inspect the lockfile diff, package manifest, published artifact contents where available, repository ownership, release history, and the hook’s purpose. Do not paste an unknown script into a shell merely to see what it does on a credentialed machine. If inspection requires execution, use a disposable environment with no production secrets and a narrow or disabled network.
The AI coding-agent repository safety checklist applies when an agent proposes a dependency or lockfile update. An agent should not add a package, broaden lifecycle permission, and approve its own result. Keep approval and merge authority independent.
Use the lockfile for reproducibility, not as a trust certificate
The npm ci reference requires an existing package-lock.json or npm-shrinkwrap.json, exits when the lock and manifest disagree, removes an existing node_modules, and does not rewrite package files. Those properties are valuable for repeatability and review.
The package-lock documentation says the file describes the exact generated dependency tree and records artifact integrity information. That helps reproduce which artifact was resolved. It does not prove the artifact’s lifecycle script is harmless or that a runner should expose cloud credentials to it.
Use both layers:
- Resolution control: committed lockfile, reviewed diff, frozen
npm ci, consistent install flags. - Execution control: scripts disabled where unnecessary; isolated, least-privilege runner where necessary; reviewed lifecycle inventory; restricted outbound access.
If the lockfile was generated with dependency-tree flags such as --legacy-peer-deps or --install-links, npm’s ci documentation says the same settings need to be supplied to the clean install. Commit project-scoped configuration rather than adding an unexplained CI-only exception.

Decide policy per job, not once per repository
A documentation check may only need package files. A typecheck may need a compiler but not every optional native build. An integration test may require native modules. A release build may need the full reviewed setup.
| Job | Needs lifecycle output? | Credentials | Recommended starting mode |
|---|---|---|---|
| Manifest or license check | Usually no | None | npm ci --ignore-scripts, then verify the tool runs |
| Lint or typecheck | Project-specific | Read-only repository token at most | Try no-script install; document exceptions |
| Unit test | Sometimes | No production secrets | No-script if tests pass; otherwise isolated install |
| Native build | Often | Artifact store only if required | Script-enabled disposable, restricted runner |
| Package release | Project-specific | Short-lived publish identity | Separate reviewed release environment |
Do not place cloud administration credentials, publisher tokens, signing keys, or production database access in the job that first evaluates a new dependency hook. GitHub’s secure use reference recommends least-privilege workflow credentials and immutable full commit identifiers for third-party actions. The same blast-radius principle applies to package lifecycle execution.
If the workflow publishes npm packages, pair these consumer-side controls with the npm trusted publishing migration guide. Trusted publishing reduces standing registry-token exposure; it does not make earlier dependency execution safe.

Build an approval record without overstating it
Run npm approve-scripts --allow-scripts-pending in a review branch to identify dependencies not covered by policy. Read the proposed entries before updating package.json. The npm command documentation says the command is currently unaware of workspaces, so a monorepo owner must verify each workspace and root policy rather than assuming one command covered the whole tree.
For every approval, record:
- exact package and version or deliberately broader scope;
- lifecycle hooks and command executed;
- why the application needs their output;
- expected files and network access;
- review source, date, owner, and next-review trigger;
- a test showing what breaks under
--ignore-scripts; - rollback: remove approval, restore lockfile, rebuild cleanly.
Prefer version-pinned approvals when supported. The npm configuration documentation says allow-scripts-pin defaults to writing package@version approvals. A name-only approval has broader future reach and needs an explicit reason. A reviewed version is not an approval for every future release.
The same reference describes one-off --allow-scripts use for contexts without a project package.json, such as global installs or npx. Avoid turning that exception into a wildcard. A global developer environment usually has more personal credentials and unrelated files than a purpose-built CI container.
Calculate the review load
A transparent workload model distinguishes a manageable allowlist from ceremonial approval:
monthly review minutes = changed script-bearing packages × minutes per package + exceptions × retest minutes
Assume 8 changed packages with lifecycle hooks. If review averages 12 minutes each and 3 need an additional 18-minute no-script comparison, the modeled load is 8 × 12 + 3 × 18 = 150 minutes, or 2 hours 30 minutes per month.
At 40 script-bearing changes and 10 retests, the same assumptions produce 40 × 12 + 10 × 18 = 660 minutes, or 11 hours. This does not estimate breach probability and does not prove 12 minutes is enough. It exposes staffing: reduce update batch size, automate inventory, split repositories, or add reviewers instead of clicking approvals faster.
A second decision aid is:
privileged lifecycle executions = script-enabled installs × concurrent jobs × scheduled runs
If one workflow performs 3 script-enabled installs across 4 environments on 5 weekdays, that is 3 × 4 × 5 = 60 execution opportunities per week. Consolidating builds to one restricted artifact job and letting test jobs consume the artifact can reduce the modeled count to 1 × 1 × 5 = 5. This is an exposure count, not a claim that any run is malicious.
Add dependency and vulnerability checks
GitHub’s dependency review documentation explains that pull requests can show direct and indirect dependency changes and known-vulnerability information from manifest and lockfile updates. Require that review where available, but still inspect lifecycle behavior: advisory databases do not describe every risky script or compromised release immediately.
Dependabot alerts documentation says alerts use the dependency graph and reviewed advisories, and notes that alerts cannot catch every issue and may arrive later. “No alert” does not mean “safe package.”
The npm audit reference says the command sends a dependency description to the default registry for known-vulnerability analysis. It also documents npm audit signatures for supported registry signatures and provenance attestations. These checks answer different questions from install-script approval. A valid signature does not prove an authorized publisher’s script is benign.
OWASP’s vulnerable dependency management guidance recommends introducing automated analysis early and warns that its approach is not a universal solution. Combine automation with ownership and escalation.
Test in quarantine before normal access
- Install from the reviewed lockfile with
--ignore-scripts. - Run jobs that claim not to need lifecycle output.
- Record the exact missing artifact or failure without enabling every script.
- Inspect the responsible package and transitive path.
- Run the required hook in a disposable environment with no inherited secrets.
- Compare filesystem changes, child processes, and network destinations with the approval record.
- Enable only the minimum job scope and rerun from a clean image.
- Remove the environment after preserving non-sensitive evidence.

The isolation model in the client browser isolation guide applies here: unfamiliar code starts away from valuable sessions and data. For CI, that means no reused developer home, mounted Docker control socket, broad cloud profile, or write access to unrelated repositories.
A hook may fetch a platform binary or compile locally. If a download is necessary, prefer a documented official destination, preserve integrity evidence, and block arbitrary network access. An allowlisted package name is not an allowlisted network destination.
Roll back when a package surprises you
Stop when a hook reaches an unexplained host, reads unrelated paths, changes shell startup files, writes outside the workspace, produces unexpected binaries, asks for elevated permission, or behaves differently from the reviewed version.
- Stop the runner and revoke temporary credentials that may have been exposed.
- Preserve relevant logs and the dependency diff without publishing secrets.
- Restore the last reviewed manifest and lockfile.
- Remove the package approval.
- Rebuild from a clean image rather than reusing
node_modules. - Check other workflows, caches, and artifacts that used the version.
- Open an internal incident or upstream report through an appropriate channel.
The AI connector permission audit provides a companion inventory pattern for tokens and integrations the runner may have reached. Rotate based on plausible exposure, not on whether the script printed a credential.

Limitations and package-manager differences
This checklist is specific to npm 11 behavior observed on August 20, 2026. npm can change allowScripts enforcement, flags, and workspace handling. Pin the npm version in CI and recheck live documentation before relying on a policy boundary.
Do not copy npm flags to Yarn or pnpm by analogy. Yarn’s current enableScripts setting documents a different model for third-party postinstall scripts and workspaces. Use the exact versioned documentation and clean-install tests for the manager committed to the repository.
Disabling scripts can break legitimate packages, and enabling a reviewed script cannot guarantee safety. Static inspection can miss generated commands or platform-specific paths. Sandboxes, containers, lockfiles, signatures, vulnerability feeds, and human review each reduce a different risk; none makes dependency execution universally safe.
Final checklist
- Pin and record the npm version used by CI.
- Use
npm ciwith a committed, reviewed lockfile. - Try
--ignore-scriptsin every job that may not need lifecycle output. - Inventory direct and transitive packages with install-time hooks.
- Treat npm 11
allowScriptsas advisory until the installed release documents enforcement. - Pin approvals to versions unless broader scope is deliberately reviewed.
- Keep script-enabled jobs away from production secrets and unrelated repositories.
- Restrict network destinations and compare behavior with the approval record.
- Require dependency and lockfile review before merge.
- Run known-vulnerability and supported signature checks as separate evidence.
- Preserve rollback that removes the package, approval, cache, and artifacts.
- Re-review when package version, script content, registry source, runner image, or npm behavior changes.
The practical goal is not zero lifecycle scripts at any cost. It is known executable dependencies, narrow execution environments, reproducible inputs, and an ordinary way to say no.