A06 Vulnerable Components
A06:2021 Vulnerable and Outdated Components. Most apps ship with hundreds of third-party packages. Each one is a supply chain that can carry a known CVE into production. The defense is boring and repeatable: inventory, monitor, patch.
SBOM, scanning, patch hygiene
EXAMPLE
// SCENARIO — keeping a real app on top of its dependencies.
// This is defensive engineering, not exploit content.
// ─── STEP 1 — Inventory (Software Bill of Materials) ───────────
// You cannot patch what you cannot see.
// Node
npm ls --all --json > sbom.json # tree
npx @cyclonedx/cyclonedx-npm --output-file sbom.cdx.json # CycloneDX
// Python
pip freeze > requirements.lock
syft packages dir:. -o cyclonedx-json > sbom.json # syft, language-agnostic
// Containers
syft packages docker:myapp:1.2.3 -o cyclonedx-json > image-sbom.json
// Store SBOMs as build artifacts — every release ships with its inventory.
// ─── STEP 2 — Scan against known vulnerabilities ───────────────
// Pick at least one of: GitHub Dependabot, Snyk, Trivy, OSV-Scanner, npm audit
npm audit --omit=dev
npm audit --audit-level=high --json | jq '.vulnerabilities | keys'
// Containers — fail the build on HIGH/CRITICAL
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:1.2.3
// Language-agnostic via SBOM
grype sbom:./sbom.json --fail-on high
osv-scanner --lockfile package-lock.json --lockfile poetry.lock
// ─── STEP 3 — Wire it into CI ──────────────────────────────────
// .github/workflows/security.yml
name: security
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm audit --omit=dev --audit-level=high
- uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
format: 'table'
severity: 'HIGH,CRITICAL'
exit-code: '1'
- uses: github/codeql-action/init@v3
with:
languages: javascript
- uses: github/codeql-action/analyze@v3
// Enable Dependabot (renovate is also great):
// .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule: { interval: weekly }
open-pull-requests-limit: 10
groups:
minor-patches:
update-types: ['minor', 'patch']
- package-ecosystem: github-actions
directory: /
schedule: { interval: weekly }
- package-ecosystem: docker
directory: /
schedule: { interval: weekly }
// ─── STEP 4 — Triage SLAs ──────────────────────────────────────
// Without a target, scanners just generate noise.
//
// • CRITICAL (CVSS 9+): patch within 7 days
// • HIGH (7-8.9): patch within 30 days
// • MEDIUM (4-6.9): next regular release
// • LOW (<4): tracked, no SLA
//
// Track findings in a real ticket system, not just a scanner dashboard.
// ─── STEP 5 — Lock-file hygiene ────────────────────────────────
// package.json says 'react: ^18.2.0'.
// package-lock.json pins the exact tree that was tested.
// Commit the lock file. Use 'npm ci' (not 'npm install') in CI.
npm ci // installs from lockfile; fails if drift
yarn install --frozen-lockfile
pnpm install --frozen-lockfile
pip install -r requirements.lock --require-hashes
poetry install --no-update --sync
// ─── STEP 6 — Remove what you don't use ────────────────────────
npx depcheck // unused deps
npx knip // unused exports + deps + files
pip-extra-reqs // python
// ─── STEP 7 — Pin and verify ───────────────────────────────────
// GitHub Actions: pin to commit SHA, not tag (tags can move)
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
// Docker: pin to digest
FROM node:20.10.0-alpine@sha256:abc123...
// npm: optionally enforce package-lock integrity
npm config set audit-signatures true // verifies registry signatures
// ─── STEP 8 — Stay on supported branches ───────────────────────
// Node 16 went EOL in 2023; using it now means CVEs without patches.
// Track EOL: endoflife.date
// node: LTS only
// python: 3.10+ (3.9 EOL Oct 2025)
// ubuntu: 22.04 or 24.04 base images
// alpine: pin to a maintained release; rebuild monthly
// ─── STEP 9 — Runtime detection ────────────────────────────────
// SCA shows what you SHIPPED. Runtime shows what actually LOADS.
// Tools like Falco or eBPF-based runtime security catch loading of
// known-vulnerable libs even when SCA missed them.
// ─── STEP 10 — Incident playbook ───────────────────────────────
// When a new critical CVE drops in a transitive dependency at 5pm Friday:
// 1. Grep SBOMs across all services to find affected versions
// 2. Check exploitability: is the vulnerable code path reachable?
// 3. Patch or pin to a fixed version; rebuild + redeploy
// 4. If patch unavailable: WAF rule, kill switch, or feature-flag off
// 5. Postmortem: how did this slip past CI? tune severity threshold
// ─── CHECKLIST ─────────────────────────────────────────────────
// 1. SBOM generated per build, stored with the artifact
// 2. Scanner runs on every PR + nightly
// 3. Dependabot/Renovate open PRs continuously
// 4. Documented SLAs by severity
// 5. Lock files committed; CI uses --frozen-lockfile / npm ci
// 6. Actions/images pinned to digests or SHAs
// 7. Quarterly review of EOL runtimes and base images
Why it matters
A06 boils down to operational hygiene: generate SBOMs, scan in CI, patch on a documented SLA, pin actions and base images by digest. The vulnerabilities are almost always public — the gap is process, not knowledge.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// A06 Vulnerable / Outdated Components — Log4Shell, xz backdoor, leftPad… // Fix: SBOM (SPDX / CycloneDX), dep scanners (Dependabot, Snyk, Trivy), // patch SLAs.Try it Yourself »
Discussion
Loading…