SCA: Software Composition Analysis
On this page
In plain terms
Modern software is mostly other people's code. A typical application is 80–90% open-source
dependencies, meaning libraries you pulled in with npm install, go get, pip install, and so
on,
each of which pulled in its dependencies. Software Composition Analysis (SCA) is the
control that inventories all of that and checks it against databases of known vulnerabilities.
Think of it as checking every ingredient in a recipe against a recall list, and not because you made a mistake, but because someone upstream might have.
Why it matters
You didn't write the vulnerable code, but you shipped it. Most real-world breaches that trace
back to "a library" are known, already-patched vulnerabilities that were never upgraded,
the Log4Shell/log4j incident is the famous example, but it happens quietly all the time. SCA
is often the highest-value control to adopt first, because the fix is usually just "upgrade
to the patched version," and the vulnerabilities are publicly cataloged as
CVEs.
Where it fits
- Artifact: your dependency manifests and lockfiles (
package-lock.json,go.sum,poetry.lock,pom.xml, …). - Stage: early and often. It runs on a pull request with no deployment needed, and again continuously (a dependency that was clean yesterday can have a new CVE today).
How it works
An SCA tool resolves your full dependency tree (direct and transitive), identifies each package and version, and matches them against vulnerability databases, chiefly the OSV (Open Source Vulnerabilities) database and the National Vulnerability Database (NVD). Good tools tell you not just what's vulnerable but whether a fixed version exists and how far you'd have to upgrade. Many also flag license risk from the same dependency tree, since a copyleft license that is incompatible with how you distribute. That's a different kind of problem with a different owner, so it's worth treating separately. See software licenses.
Popular & reputable tools
| Tool | Notes |
|---|---|
| Trivy | OSS, fast, scans filesystems/images/repos; very common default |
| OSV-Scanner | OSS, from Google; built directly on the OSV database |
| Grype | OSS, from Anchore; pairs with the Syft SBOM tool |
| retire.js | OSS; fingerprints JavaScript files themselves, so it sees what no lockfile lists |
| Dependabot | Built into GitHub; opens upgrade PRs automatically |
| govulncheck | OSS, from the Go team; call-graph reachability for Go modules |
| Snyk, Mend, Sonatype | Commercial platforms with reachability across more languages, plus policy. Draugr can run Mend for this control if you already have it |
Common pitfalls
- Transitive noise. Most findings are in dependencies-of-dependencies you can't directly upgrade, because you're waiting on a direct dependency to bump its version.
- Not all vulnerabilities are reachable. A CVE in a code path you never call is real but low-priority. Reachability analysis separates the two by building a call graph from what is in the build. Coverage varies sharply by language, and the claim is worth only as much as the evidence behind it, which reachability covers.
- Severity ≠ priority. A wall of "high" CVEs is not a plan. See prioritization.
- The lockfile is not the whole inventory. Front-end code routinely ships JavaScript no
package manager installed, such as a library pulled from a CDN, a vendored file under
static/, bundled output shipped without its manifest. Lockfile-based SCA answers for what was installed, so a repository serving a five-year-old jQuery scans clean. The control runs, reports, and passes. The gap matters because of its shape rather than its size, since nothing in that output suggests there is anywhere left to look.
How Draugr fits. Draugr's sca control runs Trivy in filesystem mode over each of your
component's repositories, normalized to SARIF alongside every other control. Because Draugr knows
each component's exposure and criticality, it ranks those CVEs into a short ordered list
instead of a flat wall. See the quickstart.
A dependency file no scanner took packages from, such as a pyproject.toml with no lockfile, a
requirements file of version ranges or a lockfile in a format no scanner reads, is listed under Caveats as unread, with the reason,
because the packages it declares were not checked. With failOnCaveats: [unread-files] in the
descriptor's gate, such a file fails the run.
Four more scanners serve the same control, each answering a question Trivy alone does not, and all of them land in that same ranked report. Turning one on is a line in the descriptor, described under running two scanners on one control.
| The scanner | What it adds |
|---|---|
| Grype | a second opinion on the same tree. Where it and Trivy disagree, the difference is a statement about coverage that neither makes alone |
| retire.js | the library no manifest lists, identified from a fingerprint of the file itself, so the vendored jQuery above is reported with a name, a version and the version that fixes it |
| govulncheck | on Go modules, which of the known vulnerabilities this code can actually reach, with the call path as evidence. Its verdicts fold onto the findings already there, and a finding nothing can reach is ranked down rather than removed. See reachability |
| Mend | your existing commercial coverage, if you already pay for it. Opt-in and gated on consent, because a Mend scan uploads your dependency inventory and creates a project in your account, which outlives the scan |
Keep learning
- SAST, the same idea for code you write
- SBOM, the inventory SCA depends on
- CVE & CVSS, where vulnerabilities are cataloged and scored