Most of the code in a modern application was written by someone else, and most teams cannot say with confidence which open source versions they ship or which of them are exploitable. Software composition analysis (SCA) is the practice and tooling that inventories third-party components, matches them against vulnerability, malware and licence data, and drives fixes. This guide covers how SCA works under the hood, where its results go wrong, how to prioritise with KEV, EPSS, reachability and VEX, and how to roll it out without drowning developers in alerts. It is written for application security engineers, platform teams and security leads who own the dependency risk programme, and it goes deeper than the broader supply chain overview.
Key takeaways
- SCA is only as good as its inventory. Lockfile and build-time resolution find far more of the real dependency tree than scanning manifests, and transitive dependencies are where most findings live.
- Severity alone is a poor queue. Combine CISA KEV status, EPSS probability, reachability and exposure before asking a developer to act on a CVSS score.
- Known vulnerabilities and malicious packages are different problems. A CVE feed will not catch a freshly published credential stealer; you need malware advisories and install-time controls as well.
- Record decisions, not just findings. VEX statements with one of the five CISA not_affected justifications turn repeated triage into a reusable, auditable record.
- Gate on what is new. Blocking pull requests that introduce new critical or exploited issues gets adoption; failing every build for legacy debt gets the tool switched off.
What SCA does and what it does not
OWASP describes the underlying risk in its Top 10 category A06:2021 Vulnerable and Outdated Components: you are likely exposed if you do not know the versions of all client-side and server-side components, including nested dependencies, if you do not scan regularly, or if patching runs on slow monthly or quarterly cycles. OWASP names software composition analysis tools, alongside OWASP Dependency-Check and retire.js, as the way to automate the inventory and monitoring that prevention requires.
In practice an SCA capability does five jobs:
- Identify: resolve the full dependency tree (direct and transitive) for each repository, container image or build artefact, and give each component a precise identifier.
- Match: compare those identifiers against vulnerability databases such as the GitHub Advisory Database, OSV and the NVD, and against malicious package advisories.
- Assess licences: flag licences that conflict with your distribution model. See the Open Source Licence Compliance Guide.
- Judge health: surface unmaintained, deprecated or end-of-life components (CWE-1104, Use of Unmaintained Third Party Components) and project hygiene signals.
- Drive remediation: propose the minimum safe upgrade, open fix pull requests, and record accepted risk.
SCA does not find flaws in your own code (that is SAST and DAST), does not prove a build was not tampered with (that is provenance and signing), and does not on its own stop a malicious package from running at install time. Those belong in the wider programme described in the Software Supply Chain Security Guide.
How SCA identifies components
The single biggest source of SCA error is identification. A tool that misreads the tree will miss real issues and invent false ones.
| Method | What it reads | Strengths | Blind spots |
|---|---|---|---|
| Manifest parsing | package.json, requirements.txt, pom.xml, go.mod | Fast, no build needed | Version ranges, missing transitive tree, unresolved parent POMs |
| Lockfile parsing | package-lock.json, pnpm-lock.yaml, poetry.lock, Cargo.lock, go.sum | Exact resolved versions including transitive | Only as good as the committed lockfile; ecosystems without lockfiles |
| Build-time resolution | Runs the package manager (mvn dependency:tree, gradle dependencies) | Matches what is actually built, handles profiles and BOM imports | Needs a working build and credentials to private registries |
| Binary and container analysis | Image layers, JAR manifests, OS package databases | Finds vendored, shaded and OS-level components | Weaker identification of renamed or repackaged libraries |
| SBOM ingestion | CycloneDX or SPDX documents | Works for supplier software you cannot build | Trust depends on how and when the SBOM was produced |
Identifiers matter. The Package URL (purl) gives each component a scheme of pkg:type/namespace/name@version?qualifiers#subpath, for example a Maven groupId as the namespace. Purl is published as Ecma standard ECMA-427. Matching on purl is far more reliable than matching on CPE strings, which were designed for products rather than packages and drive many false positives in Java and JavaScript estates.
Data sources differ. OSV aggregates advisories in the OpenSSF OSV schema from sources including GitHub Security Advisories, PyPA and RustSec, describing affected ranges by package version or commit hash, and offers a query API at api.osv.dev/v1/query. GitHub notes that only advisories reviewed by GitHub trigger Dependabot alerts, so new issues can take time to appear. Ask any tool which feeds it uses and how quickly a new advisory reaches your results.
Prioritising findings: KEV, EPSS, reachability and context
A mid-sized codebase routinely carries hundreds of open dependency findings. Treating them by CVSS base score sends developers after theoretical issues in test-only libraries while exploited ones wait. Layer the signals:
| Signal | What it tells you | Source | How to use it |
|---|---|---|---|
| CISA KEV | Reliable evidence of exploitation in the wild, with a clear remediation | CISA Known Exploited Vulnerabilities Catalog | Top of the queue, short fixed SLA |
| EPSS | Probability (0 to 1) of exploitation in the next 30 days, plus a percentile, published daily | FIRST EPSS SIG | Separate the likely from the theoretical within the same severity band |
| Reachability | Whether your code can call the vulnerable code | SCA tool call-graph analysis (coverage varies by ecosystem) | Lower priority, not zero, for unreachable findings |
| Dependency scope | Runtime versus development or test only | Lockfile and manifest metadata | Dev-only issues matter for CI runners, less for production |
| Exposure | Internet-facing service, internal tool, batch job | Asset inventory and CMDB tags | Raise priority for exposed services |
| CVSS | Technical severity | NVD or the advisory | Tie-breaker, not the primary sort |
CISA adds a CVE to KEV only when it has a CVE ID, reliable evidence of active exploitation and a clear remediation action. Scanning and proof-of-concept code do not qualify, so KEV is a high-confidence signal with low noise. GitHub's own Dependabot documentation suggests weighing EPSS alongside severity, dependency scope and relationship, and offers optional reachability analysis in public preview for npm, pnpm, Yarn and TypeScript repositories at the time of writing. Treat any reachability verdict as evidence for triage, not proof: reflection, dynamic loading and configuration-driven code paths defeat static call graphs.
For risk-based patching beyond dependencies, see the Vulnerability Management Guide.
Recording decisions with VEX
Most of the effort in SCA goes on deciding that a finding does not matter, then deciding it again next sprint and again for the next customer questionnaire. A Vulnerability Exploitability eXchange (VEX) record captures that decision. CISA's Minimum Requirements for VEX (April 2023) defines four statuses: not_affected, affected, fixed and under_investigation. A not_affected status needs one of five justifications:
- Component_not_present
- Vulnerable_code_not_present
- Vulnerable_code_not_in_execute_path
- Vulnerable_code_cannot_be_controlled_by_adversary
- Inline_mitigations_already_exist
Keep VEX statements next to the SBOM for each release, reference them from your SCA tool's suppression mechanism rather than using free-text ignores, and give each one an owner and a review date. CycloneDX (version 1.7, standardised as ECMA-424) supports VEX use cases alongside SBOMs. For SBOM production and exchange, see the SBOM Guide and the SBOM operations guide.
Beyond CVEs: malicious packages and project health
The fastest-moving dependency risk is not a vulnerable version but a malicious one. Worm-style campaigns such as the Shai-Hulud npm worm and the ChainDrop npm worm published trojanised versions of legitimate packages using stolen maintainer tokens, and ran credential stealers at install time, before any CVE existed. Controls that help:
- Malware advisories: confirm your SCA source ingests malicious package advisories (OSV carries them with a MAL- prefix) and alerts on them separately from vulnerabilities.
- Cooldown on new versions: delay adoption of versions published in the last few days for automated upgrades, so community detection has time to work.
- Install-time controls: disable or allow-list lifecycle scripts and route installs through an internal proxy registry. See the npm Install-Time Security Guide and the Malicious Package and Dependency Confusion Guide.
- Project health scoring: OpenSSF Scorecard runs 18 checks, including Maintained, Pinned Dependencies, Dangerous Workflow, Token Permissions, Signed Releases and Vulnerabilities (which uses OSV), each scored out of 10 and weighted into an aggregate. Use it to vet a new dependency before adoption, not as a pass or fail gate on its own.
Where identity fits
SCA tools hold powerful non-human credentials: tokens to read every repository, pull private registries and open pull requests, and sometimes to write to package feeds. Scope the SCA integration to read-only on code and write only to its own branches, use a GitHub App or short-lived tokens rather than a personal access token tied to an engineer, and keep registry credentials out of CI logs. The dependencies themselves are a credential risk: the XZ Utils backdoor came through a trusted maintainer identity, and many npm worms harvest CI and cloud secrets from the build host. See the npm Tokens Security Guide.
Choosing an approach
| Option | Fits | Trade-offs |
|---|---|---|
| Platform-native alerts (for example Dependabot) | Small and mid-size teams on one code host | Low effort, limited cross-repo reporting, policy and licence depth |
| Open source scanners (OSV-Scanner, OWASP Dependency-Check) | Teams wanting transparency and CI control | Free, scriptable; you build triage, VEX storage and dashboards |
| Commercial SCA platform | Large, polyglot estates with audit demands | Reachability, licence policy and portfolio reporting; cost and lock-in to its data |
| SBOM platform plus vulnerability matching | Organisations consuming much supplier software | Covers software you cannot build; depends on supplier SBOM quality |
A small organisation can get most of the value from platform-native alerts, committed lockfiles and a written triage rule. A large one needs a single portfolio view, consistent identifiers across tools, and VEX that survives tool changes. Whatever you choose, test it against a repository where you already know the answer: a known vulnerable transitive dependency, a shaded JAR and a dev-only package.
Implementing SCA: a phased rollout
- Inventory first, in monitor mode. Enable scanning on all repositories and the container registry with no build gates. Require committed lockfiles. Measure coverage per ecosystem.
- Set the triage rule. Write down the priority order (KEV, then high EPSS on reachable runtime dependencies, then the rest by severity and exposure) and SLAs per tier. Agree it with engineering leads.
- Pilot gates on one ring. For two or three willing teams, fail pull requests that introduce a new KEV-listed, malicious or critical reachable finding. Track false positives and time to merge.
- Automate fixes. Turn on grouped upgrade pull requests with a cooldown window and test requirements. See the Dependency Update Automation Guide.
- Introduce VEX and exceptions. Replace ad hoc ignores with VEX entries carrying a justification, owner and expiry.
- Expand gates and add licence policy. Roll gates to further rings once false positive rates are acceptable. Add release-time checks on container images.
- Roll back safely. Keep gates switchable per repository to warn-only, and document who may do it, so a feed error or mass advisory does not stop all releases.
Detection and measurement
Log SCA scan results, gate decisions, overrides and VEX changes centrally, and alert on new malicious package matches immediately rather than in the daily digest. Useful measures:
- Coverage: share of active repositories and deployed images scanned in the last seven days, by ecosystem.
- Exposure to exploited issues: count of open KEV-listed findings in production services, and their age.
- Mean time to remediate: by priority tier, compared with the agreed SLA.
- Introduction rate: new high-priority findings merged per month (should trend to zero once gates work).
- Exception health: share of VEX or risk-accepted entries past their review date.
- Unmaintained share: runtime dependencies with no release or maintenance activity for over a year.
Framework mapping
| Framework | Reference | SCA relevance |
|---|---|---|
| NIST CSF 2.0 | ID.AM-02, ID.RA-01, PR.PS-02, GV.SC-05 | Software inventory, vulnerability identification, maintaining and replacing software, supply chain requirements |
| NIST SP 800-53 Rev. 5 | CM-8, RA-5, SI-2, SA-11, SA-15, SA-22, SR-3, SR-4, SR-11 | Component inventory, scanning, flaw remediation, developer testing and tools, unsupported components, provenance and authenticity |
| NIST SP 800-218 (SSDF 1.1) | Secure development practices | Common vocabulary for third-party component expectations with suppliers |
| CIS Controls v8.1 | Safeguards 16.4 and 16.5 | Inventory of third-party software components; use up-to-date and trusted components |
| ISO/IEC 27001:2022 Annex A | A.8.8, A.8.28, A.5.21 | Technical vulnerability management, secure coding, ICT supply chain security |
| MITRE ATT&CK | T1195.001, T1195.002 | Compromise Software Dependencies and Development Tools; Compromise Software Supply Chain |
| OWASP Top 10 2021 | A06 | Vulnerable and Outdated Components |
Common failure patterns
- Scanning manifests without lockfiles: version ranges are guessed, transitive dependencies are missed, and results change between runs.
- Failing builds on the whole backlog: teams add blanket ignores or remove the scanner; gate on new issues and burn down the backlog separately.
- Treating no CVE as safe: worm campaigns such as the Miasma and Hades worms spread through versions that no vulnerability feed flagged.
- Ignoring CI and developer tooling: dev-only dependencies and CI actions run with secrets; the tj-actions/changed-files compromise showed a pipeline dependency leaking credentials at scale.
- Free-text suppressions: undocumented ignores outlive the reason for them; VEX with a justification and expiry avoids this.
- Old frameworks nobody owns: unsupported components with no patch path, the classic route for Apache Struts style exploitation. See the Apache Struts Security guide.
Practitioner checklist
- Require committed lockfiles and scan every active repository and deployed container image at least weekly.
- Match on purl, and confirm which advisory and malware feeds your tool uses and their latency.
- Publish a written triage order using KEV, EPSS, reachability, scope and exposure, with SLAs per tier.
- Gate pull requests on newly introduced KEV-listed, malicious or critical reachable findings only.
- Record not_affected decisions as VEX with one of the five CISA justifications, an owner and an expiry.
- Vet new dependencies with OpenSSF Scorecard and maintenance signals before adoption.
- Apply a cooldown window to automated upgrades and route installs through a proxy registry.
- Run the SCA integration on a scoped app identity or short-lived token, not a personal token.
- Report coverage, KEV exposure, time to remediate and exception health monthly.
Standards and references
- OWASP: A06:2021 Vulnerable and Outdated Components
- NIST: SP 800-218 Secure Software Development Framework Version 1.1
- CISA: Minimum Requirements for Vulnerability Exploitability eXchange (VEX)
- CISA: Known Exploited Vulnerabilities
- FIRST: Exploit Prediction Scoring System (EPSS)
- Package-URL: purl specification
- OpenSSF: OSV Open Source Vulnerabilities
- OpenSSF: Scorecard
Related NHI Mgmt Group resources: Software Supply Chain Security Guide · SBOM Guide · DevSecOps Guide · Dependency Update Automation Guide · Malicious Package and Dependency Confusion Guide