Software Composition Analysis (SCA): The Complete Guide

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

  1. Dependency enumeration: Parse your manifest and lock files to build a full dependency tree, including all transitive dependencies.
  2. Version normalization: Normalize version strings to semver. ~1.2.0, ^1.2.3, and >=1.2.0 <2.0.0 resolve to specific installed versions via the lock file.
  3. Vulnerability matching: Query CVE databases to find known vulnerabilities in specific package versions. Match on package name + version range.
  4. Severity scoring: Report findings with CVSS score, CVE identifier, and affected version range. Most tools also report the fixed version.
  5. 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 monitor

OWASP 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 --updateonly

Grype (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 high

Trivy (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.json

OSV-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.json

CI/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.json

Fail 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 none

Managing Vulnerability Findings

Triage Workflow

Not every CVE requires immediate action. Triage by:

  1. CVSS score: Critical (9.0–10.0) → fix within 24h. High (7.0–8.9) → fix this sprint. Medium → fix next sprint. Low → backlog.
  2. Reachability: Is the vulnerable code path actually exercised by your application? If not, severity drops one level.
  3. Exploit availability: Is there a public exploit? CISA's Known Exploited Vulnerabilities (KEV) catalog is authoritative. KEV entries → emergency patch regardless of CVSS.
  4. 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:log4j

Forcing 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 CRITICAL

SCA 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 alert

SCA 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:

  1. Pick one SCA tool (Grype or Trivy for open source; Snyk for commercial with developer UX focus)
  2. Run it on every PR
  3. Fail the build on CRITICAL findings
  4. Set up continuous monitoring (Dependabot or Snyk monitor) to alert when new CVEs are published against your current dependency tree
  5. 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.

Read more

Start now free