Bind the update to two files and one decision
Start with the exact manifest and lockfile diff. npm documents that npm install uses the lockfile when its resolved versions satisfy the declared
ranges and updates the lockfile when they do not. npm ci is the clean-install
check: it expects an existing lockfile and fails when the manifest and lockfile
do not match.
Record:
- package name and old/new version or range;
- direct versus transitive change;
- resolved URL or registry identity;
- lockfile diff and integrity data;
- engine, peer, native, and platform assumptions; and
- the named human who decides whether residual risk is acceptable.
Run the clean behavior gate
Use a clean dependency tree rather than the agent's already-warm
node_modules:
npm ci --ignore-scripts --no-audit --no-fund
npm run build
npm test
The flags do not make the dependency safe. --ignore-scripts prevents install-
time lifecycle scripts while you inspect them; it does not prove that the
application works when a package's required build step is withheld. If the
package needs a lifecycle script, review and allow that exact package and
version in a controlled environment instead of silently running every script.
The local P78 fixture upgraded a package from 1.0.0 to 1.1.0 on Node 22.18.0,
npm 10.9.3, and Darwin arm64. It bound the direct manifest specification and
target lockfile package, rejected an injected unrelated node, ran a clean
dependency-tree install, and passed build and behavior tests. The candidate
package advertised postinstall; the fixture blocked its execution and left
the merge decision blocked for human review.
npm ci removes the existing dependency tree, but it does not isolate npm
configuration, cache, credentials, environment variables, operating system,
architecture, or registry behavior. Use a fresh checkout or controlled worker
when those boundaries matter.
Treat lifecycle scripts as code
npm's lifecycle documentation lists preinstall, install, postinstall, prepare, and related events. A dependency's script can run during installation even when the application code looks unchanged. Inspect the candidate manifest and the resolved package contents. Record each script, why it exists, what files or network access it can touch, and whether a reviewer allows it.
An allowlist is narrower than “scripts are fine.” npm 11 documents a
version-pinned install-script approval mechanism, but this fixture ran on npm
10.9.3 and did not test that npm 11 workflow. Its observed claim is limited to
--ignore-scripts blocking the synthetic postinstall.
Treat audit output as one evidence channel
npm audit uses the dependency description and asks the configured registry
for known-vulnerability information. Its exit code alone does not say whether
evidence is usable: a valid adverse report can exit nonzero, while malformed or
missing output is not a clean report. Parse the JSON, validate the report
structure, record severity counts, and apply an explicit threshold. A zero exit
code is not a guarantee that the package has no vulnerability or supply-chain
risk.
The fixture exercises three deterministic negative cases. It blocks malformed or unavailable audit evidence, a report with a high-severity finding, and a missing human reviewer. Its actual local audit reported no high or critical finding, but the merge decision remained blocked because lifecycle approval and a named reviewer were absent.
OpenSSF Scorecard checks can add repository and dependency signals. They are signals with scope and assumptions, not a maintainer identity proof or a substitute for reading the change.
Define recovery before merge
Restore package.json and the lockfile together to the reviewed prior revision.
Run the clean dependency-tree install, build, behavior tests, and runtime
verification again. A simple downgrade may be insufficient when the update
changed migrations, generated artifacts, native components, persisted data, or
external formats; name the additional recovery owner and procedure before
merge.
Merge decision
Merge only when the reviewer can point to the exact diff, clean-install result, build and behavior result, lifecycle-script decision, vulnerability evidence, and rollback or downgrade path. Stop when any of these is missing.
Download the public dependency-update gate and run:
node dependency-update-gate.js
An exit code of zero means the fixture assertions passed. It does not approve a
real pull request; the example deliberately leaves reviewer and lifecycle
approval absent, so its merge decision is blocked. Compare the boundary with
human ownership of a pull request,
API integration testing,
and the Terraform plan gate.