Skip to main content
Security·14 min read

Dependency Scanning — Finding the Next Log4Shell in Your Stack Before It's Exploited

Complete guide to Software Composition Analysis covering dependency scanning at source code, built artifact, and container image levels. Learn CVE, CVSS, CWE scoring and build remediation workflows.

DT

DevOps Engineer & Technical Writer

What Is Software Composition Analysis

SCA Dependency Scanning Flow Package Manifest package.json go.mod / pom.xml Resolve Tree Direct + Transitive Full dependency graph Match DB NVD / OSV / GHSA CVE correlation Critical: Upgrade now High: Fix version avail Low: Monitor Tools: Trivy, Grype, Snyk, npm audit, pip-audit Runs on every commit — blocks merge on critical CVEs

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:

  • Document the vulnerability and why it cannot be fixed now
  • Identify compensating controls in place
  • Set a review date (re-evaluate in 30, 60, or 90 days)
  • Get sign-off from security team or engineering lead
  • Track in your vulnerability management system
  • ---

    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

    SeverityCI BehaviorSLA
    CRITICALBlock mergeFix within 24 hours
    HIGHBlock mergeFix within 7 days
    MEDIUMWarning, create ticketFix within 30 days
    LOWLog onlyFix 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.

    ---