Treat the lockfile as code
Three packages may run install scripts. Every other dependency change has to ask.
- Problem
Supply-chain attacks arrive through lockfile changes nobody reads and install scripts that run on every machine.
- Idea
Pin exact versions, disable install scripts except a short allowlist with reasons, and block lockfile commits unless they're on purpose.
Supply-chain attacks rarely arrive through code anyone reads. They arrive through a dependency bump in a lockfile nobody opens, or a postinstall script that runs on every developer's machine and every CI runner. This repo's policy is one line:
Treat npm dep and lockfile changes as reviewed code.
The engineering is in how each attack path gets closed.
Every path a dependency can take, and its gate
A silent upgrade
Exact versions only. Every direct dependency is pinned (no ^ or ~), and a script fails the check if one isn't.
Code at install time
No install scripts by default. Installs run with --ignore-scripts, locally and in CI.
A real need for a script
A three-entry allowlist for the install users get, each with a reason, like esbuild's "postinstall selects and verifies the platform-specific esbuild binary."
A lockfile change in passing
A pre-commit gate blocks any commit that stages the lockfile unless you confirm it with an environment variable.
The lockfile gate, in detail
package-lock.json is staged.
Review lockfile changes before committing:
- confirm every new/updated package is intentional
- confirm npm age gates were active for resolution
- review any new lifecycle scripts in the dependency tree
Detected package version changes:
- changed <package> <old> -> <new>
If this lockfile change is intentional, commit with:
…_ALLOW_LOCKFILE_CHANGE=1 git commit ...
- Diff, don't guessIt compares the lockfile at
HEADwith the staged one, entry by entry, and lists what was added, removed or changed. - Quiet when nothing external changedChanges that only touch the repo's own workspace packages pass through, so the gate doesn't train people to ignore it.
- A checklist, not just a blockAge gates (refusing versions published too recently to have been vetted) and new lifecycle scripts are what most attacks rely on, so the hook asks about exactly those.
Agents can't slip a dependency change into an unrelated commit, and neither can humans.
Lists that can't rot
- If an allowlisted install-script package disappears from the tree, the check fails and asks you to remove its entry.
- A nightly
npm auditfails on moderate advisories or worse. Accepting one requires naming the package and explaining why it doesn't apply, and the script warns about accepted entries that no longer match. - The same job runs
npm audit signaturesto verify registry signatures. - The curl installer pins every transitive dependency for users; plain
npm install -gdoesn't, and the README says so.
One dependency has a rule of its own: before updating undici, the HTTP client underneath Node's fetch, read its changelog and check whether anything affects the app.
Steal this
Make dependency changes loud. Pin exact versions, turn off install scripts and allowlist the few that need them with a reason, block lockfile commits unless someone says "yes, on purpose," and make every exception list fail when it goes stale.
Use it in your code
A prompt for your coding agent
Run it from the root of your repo. It checks whether the idea fits before it changes anything.
Harden how this project handles dependency changes.
The idea: treat dependency and lockfile changes as reviewed code. Pin exact versions, install without lifecycle scripts, allow only a short documented list of packages to run install scripts, and block lockfile commits unless someone confirms the change is intentional.
1. Check how dependencies are declared (ranges or exact versions), how CI installs them, and whether install scripts run.
2. Find which dependencies actually have install scripts, and for each say whether it needs them and why.
3. Propose and implement: a check that direct dependencies use exact versions; installs with scripts disabled (for npm, --ignore-scripts) plus an allowlist with a reason per package; and a pre-commit check that stops lockfile commits unless an environment variable confirms them, printing what changed.
4. If there's no scheduled audit, add one that fails on moderate or worse advisories and needs a written reason to accept one.
Before changing CI, show me any dependency that breaks without its install script.Read the source
Where this comes from
Pi (earendil-works/pi), the minimal, extensible agent harness. The files behind this lesson: