Incident bundles
When a supply-chain incident hits the news, thousands of teams scramble through the same hours-long question: are we resolving the compromised versions? Incident bundles turn that into a one-liner:
npx lockwarden check --incident node-ipc-may26What a bundle is
Section titled “What a bundle is”An incident IOC bundle is a small JSON document — an incident id, a human-readable
summary, references, the affected package + version list, and optionally file-level
IOCs. check --incident matches the package list against your
resolved lockfile, including every transitive path, and exits 1 if anything
matches. scan additionally matches fileIocs sha256 hashes against
file contents inside artifacts.
Bundles ship vendored inside the npm package. Running an incident check requires zero network, zero account, and works in airgapped CI.
Vendored bundles
Section titled “Vendored bundles”| Incident id | What happened |
|---|---|
axios-mar26 | Phantom transitive dep plain-crypto-js ran a postinstall payload, then replaced its own files with clean decoys — visible only in the lockfile, never in package.json. One variant shipped pre-baked in vendored node_modules. |
node-ipc-may26 | Malicious payload delivered via a binding.gyp node-gyp hook at install time (executes even with lifecycle scripts disabled); published across multiple major version lines simultaneously to maximise semver-range blast radius. |
shai-hulud-jun26 | Worm family that evolved from lifecycle scripts to AI-agent SessionStart hooks, IDE folder-open tasks, and node-gyp — moving faster than known-bad databases could update. |
Run npx lockwarden check --incident <id> with any id above. An unknown id exits 2
and lists the available bundles.
Bundle schema
Section titled “Bundle schema”The canonical JSON Schema lives in the repo at
packages/cli/src/data/incidents/_schema.json.
Field by field:
| Field | Type | Required | Meaning |
|---|---|---|---|
id | string, ^[a-z0-9][a-z0-9-]*$ | yes | Stable incident id used by check --incident <id>, e.g. node-ipc-may26 |
name | string | yes | Human-readable incident name |
date | string (ISO date) | yes | Incident date |
summary | string | yes | One-paragraph description, printed by check --incident |
references[] | string (URI) array | no | Advisories, write-ups |
packages[] | array, min 1 | yes | Affected packages |
packages[].name | string | yes | Package name |
packages[].versions[] | string array | no | Exact compromised versions |
packages[].ranges[] | string array | no | Semver ranges — for incidents published across whole version lines |
fileIocs[] | array | no | On-disk indicators, matched by scan |
fileIocs[].path | string | yes (in entry) | Payload filename (informational; matching is by content) |
fileIocs[].sha256 | string, 64 hex chars | yes (in entry) | sha256 of the payload file’s contents |
No other fields are accepted (additionalProperties: false). A minimal, valid bundle:
{ "id": "internal-2026-07", "name": "Internal registry compromise (July 2026)", "date": "2026-07-04", "summary": "Compromised internal package shipped a postinstall payload.", "packages": [ { "name": "acme-utils", "versions": ["2.4.1", "2.4.2"] }, { "name": "acme-core", "ranges": [">=3.0.0 <3.0.5"] } ]}Staging your own bundles: LOCKWARDEN_INCIDENT_DIR
Section titled “Staging your own bundles: LOCKWARDEN_INCIDENT_DIR”Set LOCKWARDEN_INCIDENT_DIR to a directory of bundle JSON files to make additional
incidents available locally:
LOCKWARDEN_INCIDENT_DIR=./our-bundles npx lockwarden check --incident internal-2026-07Local bundles are added to the vendored set (a local bundle with the same id takes
precedence for that id, but the directory never replaces the vendored bundles). Files
must end in .json; filenames starting with _ are ignored (reserved for schema
files). Use it to:
- stage a bundle for a breaking incident before the patch release ships,
- encode organisation-internal incidents,
- test bundle authoring.
Walkthrough: author, validate, use
Section titled “Walkthrough: author, validate, use”- Write the bundle to
./our-bundles/internal-2026-07.json(as above). Keep theidmatching the filename by convention. - Self-test it the same way the release pipeline does — a hit tree must exit
1, a clean tree must exit0:A malformed bundle or unknown id exitsTerminal window cd some-repo-that-resolves-acme-utilsLOCKWARDEN_INCIDENT_DIR=../our-bundles npx lockwarden check --incident internal-2026-07 --ciecho $? # expect 1cd ../some-clean-repoLOCKWARDEN_INCIDENT_DIR=../our-bundles npx lockwarden check --incident internal-2026-07 --ciecho $? # expect 02— never treat2as clean. (Working in the lockwarden repo itself,scripts/validate-incident-bundle.tsruns this same gate automatically — see CONTRIBUTING.) - Fan it out: the environment variable composes with everything —
--json,--ci, multiple--dir, the incident-bridge scripts.
The release cadence is the data pipeline
Section titled “The release cadence is the data pipeline”lockwarden has no backend, so there is no feed to poll. When a major incident lands, a
new IOC bundle is cut and published as an npm patch release, typically within hours
— an automated workflow takes the bundle JSON through schema validation, the
hit/clean self-test, and a regression gate, then publishes to npm (the one-liner works
worldwide immediately) and lands the commit through a PR. Updating is just running the
latest version, which npx does by default. The same mechanism refreshes the vendored
OSV snapshot used by the Layer-2 overlay — on a
weekly schedule, automatically: the refresh keeps the largest recency window of OSV.dev
npm malicious-package entries that fits a fixed size budget, always preserving the
canonical incident entries. Freshness is visible in every report (advisories dates)
and enforceable in CI via --max-advisory-age <days> (exit 2 when your installed
lockwarden’s data is older). Note the age basis is the OSV snapshot’s generation date,
not incident dates — incidents are event-dated, and a quiet month is not stale data.
This is a deliberate trade: you get auditable, versioned, reproducible advisory data (a bundle is a diffable JSON file in a release published with npm provenance) instead of an opaque live endpoint that malware can block — as 2026 CI-targeting malware demonstrably does to hosted security agents. Full rationale: architecture decisions.
See also
Section titled “See also”- Incident response runbook — bundles in the full triage flow.
checkreference — flags and captured output.scanreference —fileIocsmatching in artifacts.