Supply-chain security
For the controls themselves and day-to-day procedures, see Dependency management. Neighboring security topics have their own homes: workflow trigger and token privileges in CI workflows, and vulnerability reporting in the security policy.
Threat model
The August 2026 npm worm (security notice) set the current posture:
malicious versions of popular npm packages were published from compromised
maintainer accounts. The packages’ install-time lifecycle scripts executed
the payload and propagated it using harvested credentials. Several of this
repository’s PR branches were affected before containment; none reached main
or production.
The attack paths that matter for this repository:
- Version resolution: any install that resolves version ranges can pull a freshly published malicious release.
- Lifecycle scripts: install-time script execution lets a bad package compromise contributor hosts, CI runners, and build images.
- Unattended installs: CI jobs and the Netlify build image install without a human watching, as do agent sessions.
Design decisions
The recurring theme is fail closed: when a control can’t be enforced, the install fails rather than proceeding without it.
Each decision answers an attack path, ordered roughly by when it acts. The closing table maps decisions to their enforcement.
- Every dependency, direct or transitive, is surface an attacker can reach.
- An install that resolves version ranges can pull a freshly published
malicious release.
- Install from the lock: installs are
lock-exact, reproducing the committed, reviewed
package-lock.json. The one exception: a localnpm installcan rewrite a disagreeing lock; verification catches such rewrites. - Resolve deliberately: version resolution happens only in deliberate dependency updates, never as an install side effect.
- Resolve only cooled-down releases: even deliberate resolution ignores releases younger than a cooldown period; registry-side takedowns of malicious releases need a few days to land.
- Install from the lock: installs are
lock-exact, reproducing the committed, reviewed
- A package’s install-time scripts run attacker code on contributor hosts and
build machines: the worm’s payload path.
- Run only reviewed lifecycle scripts: lifecycle
scripts are default-deny.
- Approvals are version-exact, so a compromised patch release can’t inherit its predecessor’s approval.
- Reviews record denials too, so silence always means unreviewed.
- Exceptions are named and re-enabled inline at the point of use, never by weakening the default posture.
- Run only reviewed lifecycle scripts: lifecycle
scripts are default-deny.
- Netlify’s own npm install runs unattended, outside the
scripts this repository controls, and can’t be disabled.
- Neutralize the auto-install: the configuration neutralizes it; the build command performs the real install.
- A control that silently stops being enforced is worse than none.
Enforcement at a glance:
| Decision | Enforced by |
|---|---|
| Minimize dependencies | Maintainer judgment in dependency review; no mechanical control |
| Install from the lock | npm ci in every install contract |
| Resolve deliberately | Convention, backed by the lock: an unexpected resolution rewrites it, which verification flags |
| Resolve only cooled-down releases | The cooldown control, for npm and Renovate alike |
| Run only reviewed lifecycle scripts | The allowlist in strict mode; unreviewed fails the install |
| Neutralize the auto-install | The inert auto-install control |
| Fail closed on old npm | The npm engines floor with strict engine checking |
| Verify, don’t trust | Clean-working-tree checks failing the build; a postinstall warning on local lock rewrites |
Prior art
- Default-deny lifecycle scripts is the ecosystem direction:
- Release cooldowns are established practice:
- pnpm defers releases younger than a day by default.
- The 3-day value follows the long-standing Renovate
minimumReleaseAgeconvention.
- The control set maps onto established framework guidance:
- TUF’s attack taxonomy: arbitrary software installation, mix-and-match, and extraneous-dependencies attacks.
- The OpenSSF npm guide: lock-exact CI installs.