What Is Software Composition Analysis
Software Composition Analysis (SCA) scans your third-party dependencies for known vulnerabilities. Modern applications are 70-90 percent open source code — your team writes the glue logic, but the heavy lifting comes from packages you did not write and do not audit.
SCA answers one question: "Do any of my dependencies have known security vulnerabilities?"
This is not theoretical risk. The Log4Shell vulnerability (CVE-2021-44228) affected virtually every Java application. The event-stream incident injected a cryptocurrency stealer into a popular npm package. The ua-parser-js compromise turned a download utility into malware.
You need SCA at three levels: source code (lock files), built artifacts (JARs, wheels, bundles), and container images (OS packages plus application dependencies). Each level catches different things, and a mature pipeline runs all three.
---
Level 1: Source Code Scanning
Source-level SCA reads your dependency manifests and lock files to identify vulnerable packages before you even build.
npm audit — JavaScript and TypeScript
Built into npm, zero setup required:
npm audit
npm audit --audit-level=high
npm audit fix
npm audit --json > npm-audit-results.json
The --audit-level=high flag exits with a non-zero code only for HIGH and CRITICAL vulnerabilities, letting you ignore low-risk findings in CI.
GitHub Actions step:
- name: npm Security Audit
run: |
npm ci
npm audit --audit-level=high --production
The --production flag skips devDependencies, focusing on what actually ships to production.
pip-audit — Python
pip install pip-audit
Run against your requirements:
pip-audit -r requirements.txt
pip-audit --fix --dry-run
pip-audit -f json -o pip-audit-results.json
pip-audit queries the OSV database (Google's aggregated vulnerability database) and PyPI advisory feeds.
GitHub Actions step:
- name: Python Dependency Audit
run: |
pip install pip-audit
pip-audit -r requirements.txt --desc --fix --dry-run
OWASP Dependency-Check — Java, .NET, Ruby, Node.js
OWASP Dependency-Check is the most comprehensive free SCA tool. It downloads the NVD database locally and matches your dependencies against known CVEs.
brew install dependency-check
Or download directly:
wget https://github.com/jeremylong/DependencyCheck/releases/download/v9.0.9/dependency-check-9.0.9-release.zip
unzip dependency-check-9.0.9-release.zip
Run against a Maven project:
dependency-check --project "my-app" \
--scan ./target \
--format HTML \
--format JSON \
--out ./reports \
--failOnCVSS 7
The --failOnCVSS 7 flag fails the build if any vulnerability scores 7.0 or above (HIGH severity).
GitHub Actions step:
- name: OWASP Dependency-Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'my-app'
path: '.'
format: 'HTML'
args: '--failOnCVSS 7 --enableRetired'
- name: Upload Report
uses: actions/upload-artifact@v4
with:
name: dependency-check-report
path: reports/
Snyk CLI — Multi-ecosystem
Snyk has a generous free tier (200 tests per month for open source projects):
npm install -g snyk
snyk auth
snyk test
snyk test --severity-threshold=high
snyk monitor
snyk test checks for vulnerabilities. snyk monitor sends results to the Snyk dashboard for continuous monitoring.
GitHub Actions step:
- name: Snyk Security Scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
For Python:
- name: Snyk Python Scan
uses: snyk/actions/python@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
---
Level 2: Artifact Scanning
After building your application, scan the compiled artifact. This catches transitive dependencies that might not appear in your manifest files — especially common in Java where a single dependency can pull in dozens of transitive JARs.
OSV-Scanner — Google's Vulnerability Scanner
OSV-Scanner uses the Open Source Vulnerabilities database, aggregating advisories from GitHub, PyPI, npm, crates.io, and more.
go install github.com/google/osv-scanner/cmd/osv-scanner@latest
Scan a directory:
osv-scanner --recursive ./
osv-scanner --lockfile=package-lock.json
osv-scanner --sbom=bom.json
osv-scanner --format json --recursive ./ > osv-results.json
GitHub Actions step:
- name: OSV-Scanner
uses: google/osv-scanner-action/osv-scanner-action@v1
with:
scan-args: |-
--recursive
--format=sarif
--output=osv-results.sarif
./
Grype on Built Artifacts
Grype (by Anchore) scans built JARs, Python wheels, Go binaries, and more:
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
Scan a built JAR:
grype ./target/my-app-1.0.jar
grype dir:./build/libs/ --only-fixed --fail-on high
Scan a Python wheel:
grype ./dist/my_package-1.0.0-py3-none-any.whl
The --only-fixed flag shows only vulnerabilities that have a fix available — actionable findings only.
GitHub Actions step:
- name: Scan Built Artifact with Grype
uses: anchore/scan-action@v4
with:
path: "./target/my-app-1.0.jar"
fail-build: true
severity-cutoff: high
output-format: sarif
---
Level 3: Container Image Scanning
Container images include everything — your application, its dependencies, the language runtime, system libraries, and the base OS. Scanning at this level gives you the most complete picture.
Trivy — The Swiss Army Knife
Trivy scans container images, filesystems, git repos, and Kubernetes clusters:
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
Scan a local image:
trivy image my-app:latest
trivy image --severity HIGH,CRITICAL my-app:latest
trivy image --exit-code 1 --severity HIGH,CRITICAL my-app:latest
trivy image --format json --output trivy-results.json my-app:latest
Scanning in CI before push to registry:
docker build -t my-app:latest .
trivy image --exit-code 1 --severity HIGH,CRITICAL my-app:latest
docker push my-app:latest
GitHub Actions step:
- name: Build Docker Image
run: docker build -t my-app:${{ github.sha }} .
- name: Trivy Container Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'my-app:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'HIGH,CRITICAL'
exit-code: '1'
- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
Grype for Containers
grype my-app:latest
grype my-app:latest --fail-on critical
grype registry.example.com/my-app:latest --only-fixed
Docker Scout
Docker Scout is integrated into Docker Desktop and Docker Hub:
docker scout cves my-app:latest
docker scout cves --only-severity critical,high my-app:latest
docker scout recommendations my-app:latest
The recommendations command suggests base image upgrades that would reduce vulnerability count.
GitHub Actions step:
- name: Docker Scout Scan
uses: docker/scout-action@v1
with:
command: cves
image: 'my-app:${{ github.sha }}'
only-severities: critical,high
exit-code: true
---
Understanding Findings
When scanners report vulnerabilities, they reference several identification systems. Understanding these helps you prioritize and communicate risk.
CVE — Common Vulnerabilities and Exposures
CVEs are unique identifiers for specific vulnerabilities. Format: CVE-YEAR-NUMBER.
Examples:
- CVE-2021-44228: Log4Shell (remote code execution in Log4j)
- CVE-2023-44487: HTTP/2 Rapid Reset (denial of service)
- CVE-2024-3094: xz Utils backdoor (supply chain attack)
CVEs are assigned by CNAs (CVE Numbering Authorities) — organizations authorized by MITRE to issue CVE IDs. Major CNAs include GitHub, Google, Red Hat, and Microsoft.
The NVD (National Vulnerability Database) maintained by NIST enriches CVEs with severity scoring and affected version ranges.
CWE — Common Weakness Enumeration
CWEs categorize the type of weakness. While CVE identifies a specific bug in a specific version, CWE describes the underlying pattern.
Common CWEs you will encounter:
- CWE-79: Cross-site Scripting (XSS)
- CWE-89: SQL Injection
- CWE-78: OS Command Injection
- CWE-502: Deserialization of Untrusted Data
- CWE-798: Use of Hard-coded Credentials
- CWE-22: Path Traversal
CWEs help identify training opportunities. If your team repeatedly produces CWE-89 findings, they need SQL injection training specifically.
GHSA — GitHub Security Advisories
GHSA IDs are GitHub-specific advisory identifiers. Format: GHSA-xxxx-xxxx-xxxx.
GitHub maintains its own advisory database with curated vulnerability information, affected version ranges, and patch versions. GHSAs often map to CVEs but sometimes describe vulnerabilities before a CVE is assigned.
CVSS Scoring
CVSS (Common Vulnerability Scoring System) rates severity from 0.0 to 10.0:
- 0.0: None
- 0.1 to 3.9: Low
- 4.0 to 6.9: Medium
- 7.0 to 8.9: High
- 9.0 to 10.0: Critical
CVSS has three metric groups:
Base Score — Intrinsic properties that do not change over time. Considers attack vector (network vs. local), complexity, privileges required, and impact on confidentiality, integrity, and availability.
Temporal Score — Properties that change over time. Includes exploit code maturity (is there a public exploit?), remediation level (is there a patch?), and report confidence.
Environmental Score — Properties specific to your deployment. Adjusts based on how critical the affected system is in your environment.
When a scanner reports CVSS 9.8, it means: network-exploitable, low complexity, no privileges needed, high impact across confidentiality, integrity, and availability. When it reports 4.3, it typically means: requires specific conditions, limited impact, or local-only access.
Vulnerability Database Comparison
NVD (National Vulnerability Database) — NIST-maintained, gold standard for CVE data. Updated within hours of CVE publication. Includes CPE matching for precise version identification.
OSV (Open Source Vulnerabilities) — Google-maintained, aggregates advisories from GitHub, PyPI, npm, Go, Rust, and more. Better for open source package matching because it uses ecosystem-native identifiers rather than CPE.
Snyk Vulnerability Database — Proprietary database with additional advisories not in NVD. Includes Snyk-specific research and faster disclosure timelines. Contains remediation advice.
GitHub Advisory Database — Curated by GitHub security team. Integrated into Dependabot and npm audit. Excellent for JavaScript and Python ecosystems.
---
Remediation Workflow
Finding vulnerabilities is step one. Fixing them requires a structured process.
Upgrade Path
The simplest fix — upgrade to a patched version:
npm audit fix
npm update vulnerable-package
pip install --upgrade vulnerable-package
go get -u vulnerable/module@latest
For Maven, identify available updates:
mvn versions:display-dependency-updates
mvn versions:use-latest-releases
Patch via Overrides
When upgrading the direct dependency would introduce breaking changes, force the transitive dependency version:
{
"overrides": {
"vulnerable-transitive-dep": ">=2.1.1"
}
}
For Maven, use dependency management to force versions:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>vulnerable-lib</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</dependencyManagement>
Workaround
When no patch exists, mitigate the vulnerability at a different layer:
- WAF rule to block known exploit patterns
- Network segmentation to limit blast radius
- Input validation before the vulnerable code path
- Feature flag to disable the affected functionality temporarily
Risk Acceptance
For LOW severity findings with no available fix:
---
Pipeline Integration Strategy
Gate Configuration
name: Dependency Security Gate
on:
pull_request:
branches: [main]
jobs:
sca-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy SCA Scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
Severity-Based Decision Matrix
| Severity | CI Behavior | SLA |
|---|---|---|
| CRITICAL | Block merge | Fix within 24 hours |
| HIGH | Block merge | Fix within 7 days |
| MEDIUM | Warning, create ticket | Fix within 30 days |
| LOW | Log only | Fix within 90 days or accept risk |
Ignoring Unfixed Vulnerabilities
Do not fail your pipeline on vulnerabilities that have no available fix. Create a .trivyignore file:
# No fix available yet, tracked in JIRA-1234
CVE-2024-1234
# False positive - not applicable to our usage pattern
CVE-2024-5678
Scheduled Full Scans
Beyond PR-level scanning, run comprehensive scans on a schedule to catch newly disclosed vulnerabilities:
name: Weekly Full SCA Scan
on:
schedule:
- cron: '0 6 1'
jobs:
full-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Full Dependency Scan
run: |
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
trivy fs . --format json --output full-scan.json
- name: Notify on New Findings
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{"text": "New vulnerabilities found in weekly SCA scan. Review results."}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
---
Complete Multi-Level Pipeline Example
Here is a production-ready pipeline implementing all three scanning levels:
name: Complete SCA Pipeline
on:
pull_request:
branches: [main]
jobs:
level-1-source:
name: Source Dependency Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Dependencies
run: npm ci
- name: npm audit
run: npm audit --audit-level=high --production
- name: OSV-Scanner
uses: google/osv-scanner-action/osv-scanner-action@v1
with:
scan-args: '--recursive --format=sarif --output=osv.sarif ./'
level-2-artifact:
name: Built Artifact Scan
needs: [level-1-source]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci && npm run build
- name: Grype Artifact Scan
uses: anchore/scan-action@v4
with:
path: "./dist/"
fail-build: true
severity-cutoff: high
level-3-container:
name: Container Image Scan
needs: [level-2-artifact]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Image
run: docker build -t my-app:${{ github.sha }} .
- name: Trivy Container Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'my-app:${{ github.sha }}'
exit-code: '1'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
- name: Push to Registry
if: success()
run: |
docker tag my-app:${{ github.sha }} registry.example.com/my-app:${{ github.sha }}
docker push registry.example.com/my-app:${{ github.sha }}
This progressive approach catches manifest-level issues fast, artifact issues after build, and full image issues before deployment. Each level adds coverage that the previous level misses.
---
Frequently Asked Questions
What is SCA and how does it differ from SAST?
SCA (Software Composition Analysis) scans third-party dependencies and open-source libraries for known vulnerabilities (CVEs) and license compliance issues. SAST scans your own source code for coding flaws. SCA tells you "your dependency has a known exploit" while SAST tells you "your code has a security bug." Both are essential — most applications are 80%+ third-party code.
What tools should I use for dependency scanning?
Top options include: Snyk (comprehensive with fix PRs), Dependabot (GitHub-native, free), Renovate (auto-update PRs), OWASP Dependency-Check (open-source Java/Python/JS), and Trivy (scans lockfiles and container images). For npm specifically, npm audit is built-in. Use Renovate or Dependabot for automated update PRs combined with Snyk or Trivy for vulnerability alerting.
How do I handle a critical vulnerability in a transitive dependency?
First identify the dependency chain with npm ls <package> or equivalent. Check if the direct dependency has released a patch — upgrade it if so. If not, use override/resolution fields in package.json to force the transitive dependency version. As a last resort, evaluate if the vulnerable code path is actually reachable in your application and document the risk acceptance.
How often should I run dependency scans?
Run SCA on every pull request and on a daily schedule against your default branch. Scheduled scans catch newly published CVEs affecting existing code. Set up automated alerts for critical vulnerabilities that need immediate attention. Also run scans before releases and during security reviews. Configure auto-merge for patch-level updates that pass tests.
---
Related Resources
- DevOpsKit Prompt Library — 500 DevOps prompts including dependency management automation
- Engineering Toolkit — Tools and scripts for security scanning setup
- Production Troubleshooting — Scenarios including vulnerability incident response
- SAST Complete Guide for DevOps — Companion guide covering static code analysis
- Docker Production Best Practices — Secure container building patterns