Supply-chain security

Threat model and rationale behind the site’s npm dependency controls

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.
    • Minimize dependencies: unused and convenience dependencies are dropped rather than carried.
  • 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 local npm install can 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.
  • 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.
  • Netlify’s own npm install runs unattended, outside the scripts this repository controls, and can’t be disabled.
  • A control that silently stops being enforced is worse than none.
    • Fail closed on old npm: installs fail rather than proceed when the active npm is too old to enforce the .npmrc settings.
    • Verify, don’t trust: inert and lock-exact are verified claims, not assumptions.

Enforcement at a glance:

DecisionEnforced by
Minimize dependenciesMaintainer judgment in dependency review; no mechanical control
Install from the locknpm ci in every install contract
Resolve deliberatelyConvention, backed by the lock: an unexpected resolution rewrites it, which verification flags
Resolve only cooled-down releasesThe cooldown control, for npm and Renovate alike
Run only reviewed lifecycle scriptsThe allowlist in strict mode; unreviewed fails the install
Neutralize the auto-installThe inert auto-install control
Fail closed on old npmThe npm engines floor with strict engine checking
Verify, don’t trustClean-working-tree checks failing the build; a postinstall warning on local lock rewrites

Prior art

  • Default-deny lifecycle scripts is the ecosystem direction:
    • pnpm and Yarn block dependency scripts by default.
    • npm’s accepted RFC #54 brings the same model to npm through allowScripts, version-exact entries included.
  • Release cooldowns are established practice:
  • The control set maps onto established framework guidance: