Dependency Scanning with Trivy: Securing Your Supply Chain

Learn how to use Trivy to scan container images, filesystems, and Git repositories for known vulnerabilities (CVEs). Implement automated dependency scanning in CI/CD and establish a vulnerability management workflow.

Why Dependency Scanning Matters

Modern applications rely on hundreds of open-source dependencies. A single vulnerable package can expose your entire system:

  • Log4Shell (CVE-2021-44228): Critical RCE in Log4j affected millions of applications
  • Spring4Shell (CVE-2022-22965): Zero-day in Spring Framework
  • Heartbleed (CVE-2014-0160): OpenSSL vulnerability exposed sensitive data
  • event-stream incident: Malicious code injected into npm package
⚠️ Sobering Statistic:

The average application contains 203 open-source components, with 91% containing known vulnerabilities (Synopsys 2024 OSSRA Report).

Why Trivy?

Trivy stands out among dependency scanning tools for several reasons:

🚀 Fast & Comprehensive

Scans in seconds using pre-built vulnerability databases. Covers OS packages, application dependencies, and IaC misconfigurations.

🆓 Open Source & Free

No licensing fees, no API keys required. Actively maintained by Aqua Security with daily database updates.

🔄 CI/CD Native

Designed for automation with exit codes, JSON output, and GitHub Actions integration out of the box.

Installation

macOS/Linux

Install Trivy via Package Manager
# Homebrew (macOS/Linux)
brew install trivy

# Debian/Ubuntu
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy

# Verify installation
trivy --version

Docker

# Run Trivy as a Docker container
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image nginx:latest

Basic Scanning

Scan Container Images

Docker Image Scanning
# Scan local image
trivy image myapp:latest

# Scan remote image (no pull required)
trivy image nginx:1.21

# Scan with severity filtering (CRITICAL and HIGH only)
trivy image --severity CRITICAL,HIGH myapp:latest

# Output as JSON
trivy image --format json --output results.json myapp:latest

# Exit with error if vulnerabilities found
trivy image --exit-code 1 --severity CRITICAL myapp:latest

Scan Filesystems

Perfect for scanning your local development environment or CI workspace:

Filesystem Scanning
# Scan current directory
trivy fs .

# Scan specific directory
trivy fs ./my-project

# Scan only application dependencies (skip OS packages)
trivy fs --scanners vuln --security-checks vuln .

# Generate SARIF for GitHub Security tab
trivy fs --format sarif --output trivy-results.sarif .

Scan Git Repositories

# Scan remote repository
trivy repo https://github.com/your-org/your-repo

# Scan specific branch
trivy repo --branch develop https://github.com/your-org/your-repo

Understanding Results

Sample Output

Trivy Scan Results
myapp:latest (alpine 3.16.2)
============================
Total: 5 (CRITICAL: 2, HIGH: 3)

┌────────────┬────────────────┬──────────┬───────────────────┬───────────────┬──────────────────┐
│  Library   │ Vulnerability  │ Severity │ Installed Version │ Fixed Version │      Title       │
├────────────┼────────────────┼──────────┼───────────────────┼───────────────┼──────────────────┤
│ openssl    │ CVE-2023-12345 │ CRITICAL │ 1.1.1q            │ 1.1.1r        │ OpenSSL RCE      │
│ curl       │ CVE-2023-67890 │ HIGH     │ 7.83.1            │ 7.84.0        │ Heap overflow    │
│ lodash     │ CVE-2021-23337 │ HIGH     │ 4.17.15           │ 4.17.21       │ Command injection│
└────────────┴────────────────┴──────────┴───────────────────┴───────────────┴──────────────────┘

Severity Levels

Severity CVSS Score Action
CRITICAL 9.0 - 10.0 Fix immediately, block deployment
HIGH 7.0 - 8.9 Fix within 7 days
MEDIUM 4.0 - 6.9 Fix within 30 days
LOW 0.1 - 3.9 Monitor, fix when convenient

Advanced Configuration

Ignore Specific Vulnerabilities

Create .trivyignore to suppress known false positives or accepted risks:

.trivyignore
# CVE-2023-12345: Not exploitable in our usage (library only used for X)
CVE-2023-12345

# Waiting for upstream fix, tracking in JIRA-1234
CVE-2023-67890

# Expired: Re-evaluate on 2025-12-31
# CVE-2023-11111
⚠️ Best Practice:

Always document why a CVE is ignored. Include ticket numbers, expiration dates, and justification. Review .trivyignore monthly.

Custom Severity Filtering

# Only fail on CRITICAL
trivy image --exit-code 1 --severity CRITICAL myapp:latest

# Warn on MEDIUM, fail on HIGH+
trivy image --exit-code 0 --severity MEDIUM myapp:latest || echo "Warnings found"
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest

Policy-Based Scanning

Define custom policies with Rego (Open Policy Agent):

policy.rego
package trivy

default ignore = false

# Ignore LOW severity in dev images
ignore {
    input.Severity == "LOW"
    startswith(input.ImageName, "myapp:dev-")
}

# Block CRITICAL in production images
deny[msg] {
    input.Severity == "CRITICAL"
    contains(input.ImageName, ":prod-")
    msg := sprintf("CRITICAL vulnerability in production image: %s", [input.VulnerabilityID])
}
# Run with custom policy
trivy image --policy ./policy.rego myapp:prod-v1.2.3

CI/CD Integration

GitHub Actions

.github/workflows/trivy-scan.yml
name: Trivy Vulnerability Scan

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
  schedule:
    - cron: '0 6 * * *'  # Daily at 6 AM UTC

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .
      
      - name: Run Trivy scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'myapp:${{ github.sha }}'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
      
      - name: Upload to GitHub Security
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'
      
      - name: Check for vulnerabilities
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'myapp:${{ github.sha }}'
          exit-code: '1'
          severity: 'CRITICAL'
          ignore-unfixed: true  # Only fail on patchable CVEs

GitLab CI

.gitlab-ci.yml
trivy_scan:
  image: aquasec/trivy:latest
  stage: test
  script:
    - trivy image --format json --output trivy-report.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - trivy image --exit-code 1 --severity CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  artifacts:
    reports:
      container_scanning: trivy-report.json
    when: always
    expire_in: 1 week

Vulnerability Management Workflow

1. Triage Process

┌─────────────┐
│ Daily Scan  │
└─────┬───────┘
      │
      ▼
┌─────────────────────┐
│ CRITICAL/HIGH found?│──No──▶ Continue
└─────┬───────────────┘
      │ Yes
      ▼
┌──────────────────┐
│ Is it exploitable│──No──▶ Add to .trivyignore with reason
│ in our context?  │
└─────┬────────────┘
      │ Yes
      ▼
┌──────────────────┐
│ Fixed version    │──Yes──▶ Create PR to update dependency
│ available?       │
└─────┬────────────┘
      │ No
      ▼
┌──────────────────┐
│ Apply workaround │
│ or disable       │
│ vulnerable code  │
└──────────────────┘

2. Automated Dependency Updates

Combine Trivy with Dependabot/Renovate for automatic PRs:

.github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
      - "security"
  
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"

3. Monitoring Trends

Track vulnerability counts over time:

# Generate metrics for dashboard
trivy image --format json myapp:latest | \
  jq '{
    critical: [.Results[].Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length,
    high: [.Results[].Vulnerabilities[]? | select(.Severity=="HIGH")] | length,
    medium: [.Results[].Vulnerabilities[]? | select(.Severity=="MEDIUM")] | length,
    total: [.Results[].Vulnerabilities[]?] | length
  }' > metrics.json

# Example output: {"critical":2,"high":5,"medium":12,"total":19}

Best Practices

🎯 Scan Early & Often

Scan on every commit, daily scheduled scans, and before production deployments.

🔒 Block CRITICAL

Use --exit-code 1 --severity CRITICAL to prevent deploying critical vulnerabilities.

📊 Track Metrics

Monitor vulnerability trends over time. Aim for reducing total count month-over-month.

🧹 Minimize Base Images

Use Alpine or distroless images. Fewer packages = smaller attack surface.

🔄 Automate Updates

Use Dependabot/Renovate to automatically create PRs for dependency updates.

📝 Document Ignores

Always explain why a CVE is in .trivyignore. Include expiration dates.

Optimizing Docker Images

Multi-Stage Builds

Reduce vulnerabilities by excluding build tools from final image:

Dockerfile (Before)
# ❌ Bad: Includes build tools in final image (500 MB, 47 vulnerabilities)
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]
Dockerfile (After)
# ✅ Good: Minimal runtime image (150 MB, 8 vulnerabilities)
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

Use Distroless Images

# Minimal image with only runtime dependencies
FROM gcr.io/distroless/nodejs18-debian11
COPY --from=builder /app /app
WORKDIR /app
CMD ["dist/index.js"]

Troubleshooting

Database Update Failures

# Manually update vulnerability database
trivy image --download-db-only

# Use different mirror if default is slow
trivy image --db-repository ghcr.io/aquasecurity/trivy-db myapp:latest

False Positives

  • Vulnerability in unused code path: Document in .trivyignore with justification
  • Fixed in newer patch version: Update dependency in package.json or requirements.txt
  • Not exploitable: Verify with CVE details, add to ignore list with evidence

Performance Issues

# Use local cache to speed up repeated scans
trivy image --cache-dir .trivycache myapp:latest

# Skip DB update if recent
trivy image --skip-update myapp:latest

# Scan only specific layers
trivy image --skip-files /usr/share/doc myapp:latest

Integration with Other Tools

Combine with SAST

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      # SAST for code vulnerabilities
      - name: Semgrep scan
        run: semgrep scan --config=auto --sarif > semgrep.sarif
      
      # Dependency scanning
      - name: Trivy scan
        run: trivy fs --format sarif --output trivy.sarif .
      
      # Upload both results
      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: |
            semgrep.sarif
            trivy.sarif

Generate SBOMs

Software Bill of Materials for compliance:

# Generate SBOM in CycloneDX format
trivy image --format cyclonedx --output sbom.json myapp:latest

# Generate SPDX format
trivy image --format spdx --output sbom.spdx myapp:latest

Next Steps

You're now equipped to implement comprehensive dependency scanning! Continue building your security program:

💡 Need Help with Dependency Management?

ElevatedIQ offers automated dependency scanning, vulnerability triage services, and SLA-based remediation support. Get in touch to secure your supply chain.