Post

Day 11: Container Scanning with Trivy - Finding CVEs Before Deploy

A full walkthrough of Trivy - scanning container images, filesystems, Git repos, and IaC files for vulnerabilities and misconfigurations, plus integrating it into GitHub Actions.

Day 11: Container Scanning with Trivy - Finding CVEs Before Deploy

What is Trivy?

Trivy is an open-source, all-in-one security scanner by Aqua Security. It scans for:

TargetWhat it finds
Container imagesOS package CVEs, language library CVEs
FilesystemsSame as above, on local directories
Git repositoriesSecrets, CVEs in dependency files
IaC filesMisconfigurations in Terraform, Dockerfile, K8s manifests
SBOMVulnerabilities in a Software Bill of Materials

Trivy is fast, has no server component, and is the most widely used container scanner in CI/CD pipelines.


Installation

1
2
3
4
5
6
7
8
# Windows (via Scoop)
scoop install trivy

# Or download the binary from GitHub releases
# https://github.com/aquasecurity/trivy/releases

# Verify
trivy --version

Scanning Container Images

Basic Scan

1
trivy image nginx:latest

Trivy scans:

  1. The OS packages (apt/apk/yum)
  2. Application libraries (pip, npm, gem, cargo, etc.)
  3. For each: checks its vulnerability database (NVD, GitHub Advisory, OS vendor advisories)

Filtering by Severity

1
2
3
4
5
# Only show Critical and High
trivy image --severity CRITICAL,HIGH nginx:latest

# Only Critical
trivy image --severity CRITICAL nginx:latest

Failing CI on Critical Findings

1
2
# Exit code 1 if any CRITICAL CVE is found: use this in CI to block the pipeline
trivy image --severity CRITICAL --exit-code 1 myapp:latest

If the scan finds a Critical CVE, the command exits with code 1, failing the pipeline step.


Scanning a Specific Architecture

1
trivy image --platform linux/amd64 myapp:latest

Important when building multi-arch images on an ARM Mac - scan the architecture that will actually run in production.


Output Formats

1
2
3
4
5
6
7
8
# JSON output (for parsing or feeding into other tools)
trivy image --format json --output results.json nginx:latest

# SARIF output (for GitHub Code Scanning)
trivy image --format sarif --output results.sarif nginx:latest

# Table (default)
trivy image --format table nginx:latest

Scanning Filesystems and Repos

Scan a Local Directory

1
2
3
4
# Scan your project directory for vulnerabilities in dependency files
trivy fs .

# This picks up: package-lock.json, requirements.txt, Gemfile.lock, go.sum, etc.

Scan a Git Repository

1
2
3
4
5
# Scan a remote repo without cloning
trivy repo https://github.com/yourusername/yourrepo

# Scan and also check for secrets
trivy repo --scanners vuln,secret https://github.com/yourusername/yourrepo

Scanning IaC Files

Trivy checks Dockerfiles, Terraform, Kubernetes manifests, and Helm charts for misconfigurations.

1
2
3
4
5
6
7
8
# Scan a Dockerfile
trivy config Dockerfile

# Scan a Terraform directory
trivy config ./terraform/

# Scan a Kubernetes manifest
trivy config k8s-deployment.yaml
1
2
# Scan your MindCraft Terraform code
trivy config ./terraform/ --severity MEDIUM,HIGH,CRITICAL

Secret Scanning

Trivy can scan for hardcoded secrets (API keys, passwords, tokens) in your codebase:

1
trivy fs --scanners secret .

Trivy detects secrets like:

  • AWS access keys
  • GitHub tokens
  • Private keys (RSA, EC)
  • Generic API key patterns

Understanding CVE Output

Each finding shows:

FieldMeaning
LibraryThe package with the vulnerability
Vulnerability IDCVE identifier
SeverityCRITICAL / HIGH / MEDIUM / LOW
Installed VersionWhat you have
Fixed VersionWhat you need to upgrade to
TitleShort description of the vulnerability

When “Fixed Version” is empty: No fix is available yet. Your options are:

  1. Accept the risk (document it)
  2. Switch to a different package
  3. Use a different base image that doesn’t include this package

Ignoring False Positives

Create a .trivyignore file in your project to suppress known false positives or accepted risks:

1
2
3
4
5
6
7
# .trivyignore

# Accepted risk: no fix available, low exploitability in our context
CVE-2023-12345

# This CVE is in a test-only dependency, not in production
CVE-2023-67890
1
trivy image --ignorefile .trivyignore myapp:latest

Integrating Trivy into GitHub Actions

Basic Image Scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# .github/workflows/trivy.yml
name: Trivy Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  trivy-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Run Trivy image scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: 1

      - name: Upload results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

The SARIF upload makes findings appear in the GitHub Security tab → Code scanning alerts.


Full Pipeline with IaC + Image Scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: Full Security Scan

on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Trivy IaC scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: config
          scan-ref: .
          severity: HIGH,CRITICAL
          exit-code: 0   # don't fail on IaC findings (informational for now)

      - name: Build image
        run: docker build -t myapp:test .

      - name: Trivy image scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:test
          severity: CRITICAL
          exit-code: 1   # fail on Critical image CVEs

Lab: Scan a Public Image and Fix It

Objective: Scan an old image, understand the findings, update the base image, re-scan.

  1. Scan an old Python image:
    1
    
    trivy image --severity CRITICAL,HIGH python:3.9
    
  2. Count the Critical findings:
    1
    
    trivy image --severity CRITICAL --format json python:3.9 | jq '.Results[].Vulnerabilities | length'
    
  3. Scan the latest slim version:
    1
    
    trivy image --severity CRITICAL,HIGH python:3.12-slim
    
  4. Update your Dockerfile FROM line and rebuild
  5. Re-scan your built image - verify CVE count dropped

Key Takeaways

  • Trivy is a single binary that scans images, filesystems, repos, IaC, and secrets - use it everywhere
  • Add --exit-code 1 --severity CRITICAL to your CI pipeline to block on critical CVEs
  • Upload SARIF results to GitHub Security tab for persistent tracking
  • Keep base images updated - most CVEs are in outdated OS packages
  • Use .trivyignore for accepted risks, not as a way to silence everything
  • Scan both the image AND the IaC in the same pipeline

References


You can find me online at:

My signature image

This post is licensed under CC BY 4.0 by the author.