Software Composition Analysis inventories every direct and transitive dependency and matches it against known-vulnerability databases. Run it locally and in CI, and fix by upgrading — automatically where you can.
You choose a handful of direct dependencies, but each drags in its own dependencies, recursively — the transitive set is usually many times larger and is where most vulnerabilities hide, precisely because nobody chose them deliberately. SCA tools walk the full resolved tree (from your lockfile) so a critical CVE four levels deep cannot hide.
package.json says 12 deps. The lockfile resolves to ~900.
express (direct)
└── body-parser
└── qs <- a CVE here is YOUR CVE
└── ... dozens more
You audited the 12. The 888 you never saw are the attack surface.
SCA reads the LOCKFILE (the fully resolved tree), not just the
manifest — that's the difference between real coverage and theater.Every ecosystem has a native auditor, and cross-ecosystem scanners like Trivy or OSV-Scanner cover polyglot repos. Run them locally for fast feedback and in CI as the enforced gate (as wired in the DevSecOps Pipeline course). Fail on fixable, high-severity issues so a red build always means "there is an upgrade you should take."
# native, per-ecosystem
npm audit --audit-level=high
pip-audit # python
govulncheck ./... # go, reachability-aware
# cross-ecosystem, from the lockfile — good default in CI
osv-scanner --lockfile=package-lock.json
trivy fs --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed .
# --ignore-unfixed: only fail where a patched version EXISTS,
# so the gate is always actionable.Not every CVE in your tree is exploitable — the vulnerable function may never be reached by your code. Naive scanners flag all of them and create alert fatigue; reachability-aware tools (govulncheck, some commercial scanners) tell you which CVEs are actually invoked. Prioritize reachable vulnerabilities, but do not ignore the rest — a dormant path today can become live tomorrow.
Two CVEs in your dependency tree:
CVE-A in a function your code calls on every request -> URGENT
CVE-B in a code path you never import -> lower priority
Reachability analysis separates these. It cuts noise dramatically:
often <20% of raw CVE findings are actually reachable.
But "not reachable now" != "safe forever" — a future refactor can
reach it. Track B; fix A first.The remediation for a vulnerable dependency is almost always "upgrade to the patched version." Bots like Dependabot and Renovate open the upgrade PRs for you; your pipeline's tests then decide whether the bump is safe. This closes the loop: scanning finds it, automation proposes the fix, CI verifies it, a human clicks merge.
# renovate.json — grouped, automerge patch-level security updates
{
"extends": ["config:recommended"],
"vulnerabilityAlerts": { "labels": ["security"], "automerge": true },
"packageRules": [
{ "matchUpdateTypes": ["patch"], "automerge": true }
]
}
# Renovate opens PRs; CI (SCA + tests) gates them; low-risk patches
# merge themselves. You review the majors. Patching stops being a chore.