Vulnerability Management Pattern
How to keep the system patched without bottlenecking on a person watching CVE feeds.
Vulnerability Management Pattern
How to keep the system patched without bottlenecking on a person watching CVE feeds.
TL;DR (human)
Three legs: an SBOM (you know what you ship), automated CVE triage (Dependabot / Renovate / Snyk feeding into your tracker), and signed releases (consumers can verify what you shipped). Patch SLA varies by severity. Supply chain attacks treated as first-class threats — signed deps, lock files committed, install scripts inspected.
For agents
Three legs
| Leg | What it answers | How |
|---|---|---|
| SBOM (Software Bill of Materials) | What did we ship? | Generate per release; store + sign |
| Triage pipeline | Are any of those things vulnerable? | Automated scanners + watchlists |
| Signed releases | Did the consumer get what we shipped? | Sigstore / cosign / GPG attestation |
Without all three, supply-chain attacks become invisible.
SBOM
Generated at build time. Standard formats: CycloneDX, SPDX.
Contents:
- Every direct and transitive dependency.
- Version pinned (lock-file derived).
- Hash / checksum.
- License.
- Source repo / package URL.
The SBOM is committed at release tag (or attached as an artifact). Customers / auditors / regulators can request it.
Tooling: npm sbom, cdxgen, syft.
CVE triage
Continuous scan against the SBOM:
- Dependabot / Renovate: open PRs for patch / minor / major bumps. Per-package config.
- Snyk / GitHub Advanced Security / OSV-Scanner: scan + alert on CVEs.
- Trivy / Grype: scan container images.
Triage flow:
- Scanner posts to a tracker (issue, slack channel, dashboard).
- Severity classified: critical / high / medium / low (CVSS-based, with project-specific adjustments).
- Owner assigned (per package, per service).
- Patch SLA enforced:
- Critical (RCE in your trust boundary): 24h.
- High: 7 days.
- Medium: 30 days.
- Low: next release cycle.
- Verification: after patch, re-scan; CVE no longer fires.
A CVE past its SLA is a tracked overdue item; escalation path defined.
Severity adjustments
CVSS does not know your context. Adjust based on:
- Reachability: is the vulnerable code path actually called? An unreachable CVE is lower priority.
- Exposure: is the vulnerable surface internet-facing or behind auth?
- Mitigation in place: WAF / firewall / sandbox / input validation that closes the vector.
Document adjustments in the triage record. "CVE-XXX-XXXX downgraded from High to Medium because <reason>" — auditable.
Patch cadence (proactive)
Beyond reactive CVE response, schedule:
- Weekly: patch + minor bumps via Renovate / Dependabot auto-merge for low-risk packages.
- Monthly: review pending major bumps; pick low-hanging migrations.
- Quarterly: review packages with no upstream releases in 12+ months (abandoned dependency risk).
Lagging patches is how known-vulnerable code persists.
Lock files
Commit the lock file (pnpm-lock.yaml, package-lock.json, Cargo.lock, etc.).
- The lock file IS the deterministic dependency tree.
- CI runs
--frozen-lockfile: install must match the lock; mismatch fails. - Lock file updates are diff-visible in PR; reviewers see exactly which transitives moved.
Install-script discipline
NPM packages can run scripts on install. Audit:
pnpm config set ignore-scripts truefor default-deny; explicit allow per package.- Or: review install-script content in CI; flag novel scripts.
- Be specifically wary of: post-install scripts that touch unexpected paths, fetch external URLs, modify shell config.
Signed releases
Three signatures matter:
- Source release signature: the release tag / commit is signed (
git tag -sor sigstoregitsign). - Artifact signature: built binaries / packages are signed (
cosign sign,npm publish --provenance). - Attestation: the build process itself is attested (which CI built it, which commit, which steps).
Consumers verify:
# Verify the published artefact came from the expected source + build pipeline
cosign verify <artefact>This converts "we trust the registry" into "we verify the chain".
Supply-chain threats
The big ones:
| Threat | Defense |
|---|---|
Typosquatting (reqeusts vs requests) | Allowlist; explicit dep review on PRs adding new deps |
| Account hijack of maintainer | Signed releases; freeze versions during incident |
| Malicious update from compromised maintainer | Patch-lag-by-days policy (don't auto-update within 24-72h of release); package score tools |
| Build-system compromise | Reproducible builds; verify against attestation |
| Install-script attack | Disable install scripts; explicit allow |
Vulnerability disclosure (inbound)
A SECURITY.md at repo root tells reporters:
- Where to report (
security@\<your-domain\>is standard). - What to include.
- Response SLA.
- Coordinated disclosure terms.
- (Optional) bug bounty program.
Process:
- Acknowledge within 24h.
- Triage within 72h.
- Per severity:
- Critical: emergency patch + coordinated disclosure within 7 days.
- High: patch within 30 days.
- Medium / low: next release.
- Public advisory after fix lands.
Dependency policy
Documented criteria for adding a new dependency:
- Maturity: > 1 year of releases; recent commits.
- Maintenance: active issue triage; not single-maintainer in a critical role.
- Trust: well-known author / org, or a thorough read of the source.
- Necessity: the functionality cannot be reasonably inlined.
- License: compatible with the project license.
- Size: a 2-line problem solved by a 200KB package is overhead.
Each new dep is an attack surface and a maintenance burden. Add deliberately.
Common failure modes
- No SBOM. Vulnerability scan tells you which CVE exists; you cannot tell if you ship it. → Generate SBOM per release.
- Dependabot PRs auto-merged with no test guard. Compromised dep gets in. → Tests must pass; signed-by-known-maintainer check.
- Critical CVE waiting weeks for triage. No SLA; no escalation. → SLAs documented; escalation path.
- Lock file not committed. Different dev / CI sees different versions; CVE scan is unreliable. → Commit; CI uses frozen install.
- Install scripts enabled by default. One malicious dep runs arbitrary code on every install. → Disable by default.
- No signature verification on download. Consumer doesn't verify; spoofed artefact accepted. → Verify in CI / production install.
Tooling stack (typical)
| Concern | Tool |
|---|---|
| SBOM generation | syft, cdxgen, npm sbom |
| CVE scan (deps) | osv-scanner, Snyk, Dependabot, Renovate |
| CVE scan (images) | trivy, grype |
| Signing | sigstore (cosign, gitsign), gpg |
| Provenance | SLSA framework, GitHub Actions native provenance |
| Bounty / disclosure | HackerOne, Bugcrowd, security@<domain> mailbox |
See also
universal.md— Rule 4 (vault refs), Rule 9 (rotation).audit-ledger-pattern.md— security incidents logged here.threat-model-template.md— supply-chain row in the threats table.