cyberine
UsageRun recipes

Dependency audit gate — npm run selftest:dep-audit (T673, multi-tree since T707)

Recurring guard against silently-accruing vulnerable dependencies (the gap that let fast-uri / ip-address / nanoid / postcss sit at HIGH for weeks before 2026-08-20). It discovers every package-lock.json in the repo, runs npm audit --json once per tree with that tree as the cwd, and applies one standing contract:

npm run selftest:dep-audit                     # every dependency tree in the repo
node scripts/dep-audit-smoke.mjs --tree site   # just one, by directory name

The repo has two independent trees — the root, and site/ (its own package.json, its own lockfile, its own npm ci). Until T707 this gate called npm audit with no cwd, so it only ever saw the root: site/'s dependencies had never been audited by anything. Both are now reported separately, and the site CI job runs the site-scoped form as its own step.

  • FAIL (exit 1) on any HIGH or CRITICAL advisory whose fix is a plain in-range npm audit fix (fixAvailable: true). Remedy when red: npm audit fix (never --force), commit the package-lock.json diff, re-run the gate.
  • WARN (exit 0) on HIGH+ advisories with no in-range fix (no upstream fix yet, or a semver-major upgrade) — deliberate major bumps are their own filed task; the gate names them but never goes red for them.
  • Moderate/low advisories are counted in the summary line but never gate.

Known limitations and deferred work: needs npm-registry network access (npm audit calls the advisory API), so it is a recurring-sweep / CI gate, deliberately not part of npm run typecheck; and npm's fixAvailable: true can be optimistic — npm may claim a fix that npm audit fix cannot actually apply because an intermediate pin blocks the bump (live example: gaxios@6.7.1 pins uuid@^9 while every uuid <11.1.1 carries GHSA-w5hq-g745-h8pq, so that MODERATE stays). If a HIGH ever lands in that state, verify with npm ls <pkg>, file a task, and add the package name to the ALLOWLIST constant in scripts/dep-audit-smoke.mjs with a comment citing the task. It is a plain .mjs rather than a .ts smoke so the site CI job — which installs only site/'s tree and therefore has no tsx — can run it. It is also deliberately NOT part of npm run sweep: the sweep is offline-safe by contract, and this gate is not. CI runs it as its own step in both the selftests job (every tree) and the site job (the site tree).