Software Composition Analysis (SCA): The Complete Guide
Software Composition Analysis (SCA) scans your project's dependencies—npm packages, Maven artifacts, pip libraries, Go modules—against vulnerability databases (NVD, OSV, GitHub Advisory) to identify known CVEs. Modern applications use hundreds of open source libraries, and 70–90% of the code in production is third-party. SCA is how you know whether Log4Shell, Spring4Shell, or whatever next year's critical CVE is affects your application. Run SCA in CI, set CVSS thresholds, and treat vulnerable dependencies as blocking bugs.
Key Takeaways
Most production code is third-party. Typical applications are 70–90% open source libraries by line count. Your application's attack surface includes every vulnerability in every dependency.
SCA is different from SAST. SAST analyzes your code. SCA analyzes the libraries your code depends on. You need both.
CVE coverage varies by database. Use tools that query multiple vulnerability databases: NVD, OSV (Google), GitHub Advisory Database, and vendor-specific advisories. No single database has complete coverage.
Transitive dependencies are the real risk. Direct dependencies are easy to audit. Transitive dependencies (dependencies of dependencies) are harder to see and just as vulnerable. SCA tools traverse the full dependency tree.
Reachability analysis reduces noise. Advanced SCA tools check whether vulnerable code paths are actually called in your application. A vulnerable function in a library you use only for its unrelated utility functions may not be exploitable.
What SCA Scans
SCA operates on your dependency manifest and lock files:
| Ecosystem | Manifest | Lock File |
|---|---|---|
| Node.js | package.json |
package-lock.json, yarn.lock, pnpm-lock.yaml |
| Python | requirements.txt, pyproject.toml |
poetry.lock, Pipfile.lock |
| Java | pom.xml, build.gradle |
(resolved at build time) |
| Go | go.mod |
go.sum |
| Ruby | Gemfile |
Gemfile.lock |
| Rust | Cargo.toml |
Cargo.lock |
| PHP | composer.json |
composer.lock |
| .NET | *.csproj, packages.config |
packages.lock.json |
Always use lock files. Lock files pin exact versions. SCA against a manifest without a lock file resolves version ranges and may report vulnerabilities in versions you don't actually use.
How SCA Works
- Dependency enumeration: Parse your manifest and lock files to build a full dependency tree, including all transitive dependencies.
- Version normalization: Normalize version strings to semver.
~1.2.0,^1.2.3, and>=1.2.0 <2.0.0resolve to specific installed versions via the lock file. - Vulnerability matching: Query CVE databases to find known vulnerabilities in specific package versions. Match on package name + version range.
- Severity scoring: Report findings with CVSS score, CVE identifier, and affected version range. Most tools also report the fixed version.
- Reachability analysis (advanced): Some tools analyze whether your code actually calls the vulnerable code path in the dependency.
Top SCA Tools
Snyk
Commercial, with a generous free tier. Strong reachability analysis for JavaScript/TypeScript, Java, and Python. Best-in-class developer UX.
# Install Snyk CLI
npm install -g snyk
# Authenticate
snyk auth
# Scan current project
snyk test
# Scan with JSON output for CI
snyk test --json > snyk-results.json
# Monitor (uploads to Snyk dashboard, notifies on new CVEs)
snyk monitorOWASP Dependency-Check
Open source, mature, supports most ecosystems. Good for air-gapped environments since you can download the NVD feed locally.
# Scan a Maven project
dependency-check.sh \
--project "MyApp" \
--scan ./target \
--format JSON \
--out ./dependency-check-report
# Update NVD data feed
dependency-check.sh --updateonlyGrype (Anchore)
Fast, open source, runs locally. Good for containers and filesystem scans. Uses the Grype vulnerability database (backed by NVD + GitHub Advisory + OSV).
# Scan a directory
grype dir:.
# Scan a container image
grype nginx:latest
# Output SARIF for GitHub
grype dir:. -o sarif > grype.sarif
# Fail on high/critical only
grype dir:. --fail-on highTrivy (Aqua Security)
Open source, very fast, excellent container support. Also scans infrastructure-as-code for misconfigurations.
# Scan filesystem
trivy fs .
# Scan container image
trivy image myapp:latest
# Scan with severity threshold
trivy fs . --severity HIGH,CRITICAL --exit-code 1
# Output JSON for processing
trivy fs . -f json -o trivy-results.jsonOSV-Scanner (Google)
Open source, uses the OSV database which aggregates GitHub Advisory, NVD, PyPA, RustSec, and more. Best multi-ecosystem coverage.
# Install
go install github.com/google/osv-scanner/cmd/osv-scanner@latest
# Scan project
osv-scanner -r .
# Scan specific lockfile
osv-scanner --lockfile=package-lock.json
# Output JSON
osv-scanner -r . --json > osv-results.jsonCI/CD Integration
GitHub Actions — Multi-Tool Setup
name: SCA Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy SCA scan
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: .
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
exit-code: '1'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
- name: Run OSV-Scanner
uses: google/osv-scanner-action@v1
with:
scan-args: |-
--recursive
.GitLab CI
dependency_scanning:
image: docker:stable
stage: test
services:
- docker:dind
script:
- docker run --rm
-v "$CI_PROJECT_DIR:/project"
anchore/grype
dir:/project
--fail-on high
artifacts:
reports:
dependency_scanning: gl-dependency-scanning-report.jsonFail Thresholds
Choose your threshold based on your risk tolerance:
# Block on any critical (CVSS >= 9.0)
grype dir:. --fail-on critical
# Block on high and critical (CVSS >= 7.0)
grype dir:. --fail-on high
# Only report, never block (not recommended for new projects)
grype dir:. --fail-on noneManaging Vulnerability Findings
Triage Workflow
Not every CVE requires immediate action. Triage by:
- CVSS score: Critical (9.0–10.0) → fix within 24h. High (7.0–8.9) → fix this sprint. Medium → fix next sprint. Low → backlog.
- Reachability: Is the vulnerable code path actually exercised by your application? If not, severity drops one level.
- Exploit availability: Is there a public exploit? CISA's Known Exploited Vulnerabilities (KEV) catalog is authoritative. KEV entries → emergency patch regardless of CVSS.
- Fixed version availability: Is there a patched version? If yes, update immediately. If no patched version exists, consider the mitigating controls.
.grype.yaml — Ignore File
# .grype.yaml
ignore:
# CVE-2023-XXXXX - Vulnerability in a test dependency, never deployed to production
- vulnerability: CVE-2023-XXXXX
reason: "Test-only dependency, not in production build"
# Low-severity, no fixed version available, no public exploit
- package:
name: some-utility
version: 1.2.3
vulnerability: CVE-2022-YYYYY
reason: "No fix available as of 2026-01. Monitoring for update."
expires: "2026-07-01"Snyk .snyk Policy File
# .snyk
version: v1.25.0
ignore:
SNYK-JS-LODASH-1018905:
- '*':
reason: Prototype pollution only reachable in admin-only path
expires: '2026-12-01'
created: '2026-01-15T00:00:00.000Z'Transitive Dependency Management
80% of vulnerable findings come from transitive dependencies—libraries your direct dependencies depend on.
Identifying Transitive Dependencies
# npm — show full dependency tree
npm ls --all
# Show why a specific package is installed
npm ls lodash
# Python — show dependency tree
pip install pipdeptree
pipdeptree
# Go — show dependency graph
go mod graph | grep vulnerable-pkg
# Maven
mvn dependency:tree -Dincludes=log4j:log4jForcing Transitive Dependency Updates
// package.json — override transitive dependency version
{
"overrides": {
"lodash": ">=4.17.21"
}
}<!-- pom.xml — force transitive dependency version -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
</dependencies>
</dependencyManagement>License Compliance
SCA also identifies open source license types. This matters for commercial software:
| License | Commercial Use | Copyleft Risk |
|---|---|---|
| MIT | ✓ | None |
| Apache 2.0 | ✓ | None |
| BSD 2/3-Clause | ✓ | None |
| LGPL | ✓ with care | Dynamic linking OK, static linking requires source disclosure |
| GPL v2/v3 | Restricted | Derivative works must be open source |
| AGPL | Very restricted | Even SaaS use triggers copyleft |
| CC-BY-SA | Non-software | Attribution + share-alike |
# Snyk license scanning
snyk test --license-check
# FOSSA (dedicated license compliance)
fossa analyze
fossa test --fail-on CRITICALSCA in the Security Pipeline
Developer Commit
│
▼
SCA in CI (Grype/Trivy/Snyk)
│
├── Critical CVE found → Block PR
│
└── High CVE found → Comment on PR, require acknowledgment
│
▼
Weekly Full Scan (all branches)
│
└── Email report to security team
│
▼
Continuous Monitoring (Snyk Monitor / Dependabot)
│
└── New CVE published → Immediate alertSCA vs SAST vs DAST
| Dimension | SCA | SAST | DAST |
|---|---|---|---|
| What it scans | Third-party dependencies | Your source code | Running application |
| When to run | Every PR, continuously | Every PR | After deployment |
| Speed | Seconds–minutes | Minutes | Minutes–hours |
| False positive rate | Low (CVE data is definitive) | High (pattern-based) | Medium |
| What it misses | Vulnerabilities in your own code | Runtime behavior | Server-side logic |
Summary
SCA is non-negotiable for any application that uses open source libraries—which is every application. The practical minimum:
- Pick one SCA tool (Grype or Trivy for open source; Snyk for commercial with developer UX focus)
- Run it on every PR
- Fail the build on CRITICAL findings
- Set up continuous monitoring (Dependabot or Snyk monitor) to alert when new CVEs are published against your current dependency tree
- Triage HIGH findings every sprint
The Log4Shell incident in December 2021 demonstrated that critical vulnerabilities can exist in ubiquitous libraries for years before discovery—and that teams without automated SCA discovered their exposure days or weeks late.