Security scanning of your code with Trivy

Published on by

Security vulnerabilities in your codebase need to be dealt with. Trivy is an open source tool that is straightforward to set up.

Trivy doesn't just scan for known vulnerabilities in your dependencies. It examines Docker images, filesystem content, repository configurations, secrets, and even license compliance.

This blog will help you set up Trivy scanning in GitHub Actions and publish security reports to GitHub Pages, giving you visibility and control over your security posture. These foundations provide a solid base for building your security toolchain.

Other commercial security scanning tools include Snyk, GitHub Advanced Security, GitLab Security Scanning, JFrog Xray, SonarQube, and FOSSA. They are all solid tools, however here we'll focus on Trivy since it is open source and free to use. It offers many of the same capabilities as commercial security scanning tools without licensing costs.

Running locally

First, install Trivy on your system. Then you can run scans against your local repository:

# Scan filesystem for vulnerabilities
trivy fs .

# Scan container images
trivy image your-app:latest

# Check repository for misconfigurations and secrets
trivy repo .

This gives you essential scans: filesystem vulnerabilities, container image analysis, and repository security issues. Run trivy fs . and you'll immediately see any known vulnerabilities in your dependencies, along with severity ratings and available fixes.

GitHub Actions Integration

Now let's get this into our pipelines. Trivy provides a GitHub Action that is easy to drop into place.

name: Security Scan

on:
  push:

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

      - name: Run Trivy filesystem scanner
        uses: aquasecurity/trivy-action@0.32.0
        with:
          scan-type: "fs"
          scan-ref: "."
          format: "table"

      - name: Run Trivy image scanner
        uses: aquasecurity/trivy-action@0.32.0
        with:
          image-ref: "your-app:latest"
          format: "table"

      - name: License compliance scan
        uses: aquasecurity/trivy-action@0.32.0
        with:
          scan-type: "fs"
          scan-ref: "."
          scanners: "license"
          format: "table"

This setup runs on every push. The table format provides human-readable output directly in your GitHub Actions logs. For example, the pipeline logs might look like:

Running Trivy with options: trivy image hello-world
2025-08-07T15:35:44Z	INFO	[vuln] Vulnerability scanning is enabled
2025-08-07T15:35:44Z	INFO	[secret] Secret scanning is enabled
2025-08-07T15:35:44Z	INFO	[secret] If your scanning is slow, please try '--scanners vuln' to disable secret scanning
2025-08-07T15:35:44Z	INFO	[secret] Please see also https://trivy.dev/v0.64/docs/scanner/secret#recommendation for faster secret detection
2025-08-07T15:35:46Z	INFO	Detected OS	family="alpine" version="3.21.2"
2025-08-07T15:35:46Z	INFO	[alpine] Detecting vulnerabilities...	os_version="3.21" repository="3.21" pkg_num=15
2025-08-07T15:35:46Z	INFO	Number of language-specific files	num=0
2025-08-07T15:35:46Z	WARN	Using severities from other vendors for some vulnerabilities. Read https://trivy.dev/v0.64/docs/scanner/vulnerability#severity-selection for details.

Report Summary

┌─────────────────────────────┬────────┬─────────────────┬─────────┐
│           Target            │  Type  │ Vulnerabilities │ Secrets │
├─────────────────────────────┼────────┼─────────────────┼─────────┤
│ hello-world (alpine 3.21.2) │ alpine │        6        │    -    │
├─────────────────────────────┼────────┼─────────────────┼─────────┤
│ /bad-secret.txt             │  text  │        -        │    1    │
└─────────────────────────────┴────────┴─────────────────┴─────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)

hello-world (alpine 3.21.2)
===========================
Total: 6 (UNKNOWN: 2, LOW: 0, MEDIUM: 2, HIGH: 2, CRITICAL: 0)

┌────────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┐
│  Library   │ Vulnerability  │ Severity │ Status │ Installed Version │ Fixed Version │                            Title                            │
├────────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┼───────────────────────────────────��─────────────────────────┤
│ libcrypto3 │ CVE-2024-12797 │ HIGH     │ fixed  │ 3.3.2-r4          │ 3.3.3-r0      │ openssl: RFC7250 handshakes with unauthenticated servers    │
│            │                │          │        │                   │               │ don't abort as expected                                     │
│            │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2024-12797                  │

You might want the pipeline to fail if certain security checks fail. For example, fail pipeline on secret violation, using the exit code to halt deployments when secrets are detected:

- name: Scan for secrets (fail on detection)
  uses: aquasecurity/trivy-action@0.32.0
  with:
    scan-type: "fs"
    scan-ref: "."
    scanners: "secret"
    exit-code: 1
    format: "table"

This configuration returns exit code 1 if any secrets are detected, causing CI/CD pipelines to fail and preventing deployment of compromised code.

Generate HTML reports

You can generate JSON reports that can be processed by other tools or integrated into dashboards:

# Create output directory
mkdir -p out/report

# Detailed filesystem scan with JSON output
trivy fs . \
  --scanners secret,vuln,misconfig,license \
  --format json \
  --output out/report/fs-report.json

# Detailed image scan with JSON output
trivy image your-app:latest \
  --scanners secret,vuln,misconfig,license \
  --format json \
  --output out/report/image-report.json

The --scanners flag lets you specify exactly what to examine. Beyond vulnerabilities (vuln), you can detect hardcoded secrets (secret), configuration issues (misconfig), and license violations (license).

The scan2html plugin converts Trivy's JSON output into interactive single page HTML reports.

# Install the plugin first
trivy plugin install scan2html

# Generate HTML reports from JSON output
trivy scan2html generate --scan2html-flags --output out/report/index.html \
  --from out/report/fs-report.json,out/report/image-report.json

Trivy scan2html

These HTML reports can be published to GitHub Pages or internal sites, giving everyone on your team a clear view of security issues. To deploy them to GitHub pages you can use the following:

jobs:
  scan:
    runs-on: ubuntu-latest

    steps:
      # steps to scan
      # then ...
      - name: Upload Pages artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: out/report

  deploy:
    needs: scan
    permissions:
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: $
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Beyond the scan2html plugin, the Trivy ecosystem includes several other community reporting tools that are worth a look at.

Software Bill of Materials (SBOM)

Compliance often requires Software Bill of Materials generation. Trivy handles this natively:

# Generate SPDX-format SBOM for filesystem
trivy fs . --format spdx-json --output out/fs-sbom.json

# Generate SPDX-format SBOM for container image
trivy image your-app:latest --format spdx-json --output out/image-sbom.json

SPDX - "The System Package Data Exchange" is a standard format for SBOM data, supported by tools across the security ecosystem.

Practical Tips

Your first scan may reveal several vulnerabilities. Some may be in transitive dependencies. Focus on HIGH and CRITICAL vulnerabilities first. Trivy provides clear remediation guidance—dependency updates or configuration changes.

Customise severity thresholds and focus on actionable issues by filtering severity levels:

trivy fs . --severity HIGH,CRITICAL

Manage false positives with a .trivyignore file to suppress known issues:

# .trivyignore
CVE-2021-44228  # Log4j - not applicable to our usage
CVE-2022-12345  # Fixed in our custom build

Use this sparingly and document why each CVE is being ignored. Regular reviews of ignored vulnerabilities help ensure they remain valid exceptions.

Policy Enforcement by defining custom security policies using Rego. You can create policy file, e.g. policy.rego:

package trivy

deny[res] {
    input.Results[_].Vulnerabilities[_].Severity == "CRITICAL"
    res := sprintf("Critical vulnerability found: %s", [input.Results[_].Vulnerabilities[_].VulnerabilityID])
}

And use the custom policy

trivy fs . --config-policy policy.rego

And, worth noting, Trivy has experimental features covering the scanning of kubernetes resources and VMs.

Wrap up

Trivy provides visibility into your actual security posture - often revealing gaps you didn't know existed.

The value is setting up security checks that grow with your development pace. Trivy makes this setup straightforward.

Security scanning should be as routine as running tests. Once you've added Trivy to your workflow, you'll see vulnerabilities, secrets, and misconfigurations across your codebase. Local scanning, automated checks, and visual reports give you a solid security base that grows with your team.

Start with basic filesystem and image scans, then add reporting and policies as you need them. The goal isn't perfect security scores - it's catching issues before they hit production.