container security

Performing Container Security Scanning with Trivy

Scan container images, filesystems, and Kubernetes manifests for vulnerabilities, misconfigurations, exposed secrets, and license compliance issues using Aqua Security Trivy with SBOM generation and CI/CD integration.

container-securitydevsecopsdockerkubernetessbomsupply-chaintrivyvulnerability-scanning
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Trivy is an open-source security scanner by Aqua Security that detects vulnerabilities in OS packages and language-specific dependencies, infrastructure-as-code misconfigurations, exposed secrets, and software license issues across container images, filesystems, Git repositories, and Kubernetes clusters. Trivy generates Software Bill of Materials (SBOM) in CycloneDX and SPDX formats for supply chain transparency. This skill covers comprehensive container image scanning, CI/CD pipeline integration, Kubernetes operator deployment, and scan result triage for security operations.

When to Use

  • When conducting security assessments that involve performing container security scanning with trivy
  • When following incident response procedures for related security events
  • When performing scheduled security testing or auditing activities
  • When validating security controls through hands-on testing

Prerequisites

  • Trivy v0.50+ installed (binary, Docker, or Homebrew)
  • Docker daemon access for local image scanning
  • Container registry credentials for remote image scanning
  • CI/CD platform (GitHub Actions, GitLab CI, Jenkins) for pipeline integration
  • Kubernetes cluster for Trivy Operator deployment (optional)

Steps

Step 1: Scan Container Images

Run vulnerability and secret scanning against container images from local builds or remote registries. Configure severity thresholds and ignore unfixed vulnerabilities.

Step 2: Generate SBOM

Produce CycloneDX or SPDX SBOM documents from scanned images for supply chain compliance and vulnerability tracking across the software lifecycle.

Step 3: Scan IaC and Kubernetes Manifests

Detect misconfigurations in Dockerfiles, Kubernetes YAML, Terraform, and Helm charts using built-in policy checks aligned with CIS benchmarks.

Step 4: Integrate into CI/CD

Add Trivy scanning as a pipeline gate that blocks builds with critical/high vulnerabilities, generates SARIF reports for GitHub Advanced Security, and produces JUnit XML for test dashboards.

Expected Output

JSON/table report listing CVEs with severity, CVSS scores, fixed versions, affected packages, misconfiguration findings, and exposed secrets with file locations.

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md2.5 KB

API Reference: Performing Container Security Scanning with Trivy

Trivy CLI Commands

# Vulnerability + secret scan on image
trivy image --severity CRITICAL,HIGH nginx:latest
 
# JSON output for CI/CD integration
trivy image --format json --output results.json alpine:3.18
 
# Scan with all scanners
trivy image --scanners vuln,misconfig,secret,license myregistry.io/app:v1.2
 
# Scan Dockerfile/K8s manifests for misconfigurations
trivy config --severity CRITICAL,HIGH ./kubernetes/
 
# Filesystem scan (local project)
trivy fs --scanners vuln,secret ./
 
# Generate CycloneDX SBOM
trivy image --format cyclonedx --output sbom.json myapp:latest
 
# Generate SPDX SBOM
trivy image --format spdx-json --output sbom-spdx.json myapp:latest
 
# Scan existing SBOM for vulnerabilities
trivy sbom ./sbom.json
 
# Ignore unfixed vulnerabilities
trivy image --ignore-unfixed --severity CRITICAL alpine:3.18
 
# SARIF output for GitHub Advanced Security
trivy image --format sarif --output trivy.sarif myapp:latest

Severity Levels

Level CVSS Score CI Gate Default
CRITICAL 9.0 - 10.0 Block
HIGH 7.0 - 8.9 Block
MEDIUM 4.0 - 6.9 Warn
LOW 0.1 - 3.9 Pass

Scanner Types

Scanner Flag Targets
vuln --scanners vuln OS packages, language deps
misconfig --scanners misconfig Dockerfile, K8s, Terraform
secret --scanners secret API keys, passwords, tokens
license --scanners license Package license compliance

GitHub Actions Integration

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

Exit Codes

Code Meaning
0 No vulnerabilities found (or below threshold)
1 Vulnerabilities found matching severity filter

JSON Output Structure

{
  "Results": [{
    "Target": "alpine:3.18 (alpine 3.18.0)",
    "Type": "alpine",
    "Vulnerabilities": [{
      "VulnerabilityID": "CVE-2023-xxxx",
      "PkgName": "openssl",
      "InstalledVersion": "3.1.0-r0",
      "FixedVersion": "3.1.1-r0",
      "Severity": "HIGH"
    }]
  }]
}

References

Scripts 1

agent.py6.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Trivy Container Security Agent - scans images for vulnerabilities, misconfigs, and secrets."""

import json
import argparse
import logging
import subprocess
from collections import defaultdict
from datetime import datetime

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)


def run_trivy_scan(image, scanners="vuln,secret", severity="CRITICAL,HIGH,MEDIUM"):
    """Run Trivy image scan and return JSON results."""
    cmd = [
        "trivy", "image", "--format", "json", "--scanners", scanners,
        "--severity", severity, "--quiet", image,
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
    if result.returncode != 0 and not result.stdout:
        logger.error("Trivy scan failed: %s", result.stderr)
        return {}
    return json.loads(result.stdout) if result.stdout else {}


def run_trivy_misconfig(target_path):
    """Run Trivy misconfiguration scan on Dockerfile/K8s manifests."""
    cmd = [
        "trivy", "config", "--format", "json", "--severity", "CRITICAL,HIGH,MEDIUM",
        "--quiet", target_path,
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
    return json.loads(result.stdout) if result.stdout else {}


def generate_sbom(image, sbom_format="cyclonedx"):
    """Generate SBOM from container image."""
    cmd = ["trivy", "image", "--format", sbom_format, "--quiet", image]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
    return result.stdout if result.returncode == 0 else ""


def analyze_vulnerabilities(scan_results):
    """Analyze vulnerability scan results and produce summary."""
    by_severity = defaultdict(int)
    by_pkg_type = defaultdict(int)
    fixable = 0
    total = 0
    cve_list = []
    for target in scan_results.get("Results", []):
        pkg_type = target.get("Type", "unknown")
        for vuln in target.get("Vulnerabilities", []):
            severity = vuln.get("Severity", "UNKNOWN")
            by_severity[severity] += 1
            by_pkg_type[pkg_type] += 1
            total += 1
            if vuln.get("FixedVersion"):
                fixable += 1
            if severity in ("CRITICAL", "HIGH"):
                cve_list.append({
                    "cve_id": vuln.get("VulnerabilityID", ""),
                    "package": vuln.get("PkgName", ""),
                    "installed": vuln.get("InstalledVersion", ""),
                    "fixed": vuln.get("FixedVersion", ""),
                    "severity": severity,
                    "cvss_score": vuln.get("CVSS", {}).get("nvd", {}).get("V3Score", 0),
                    "title": vuln.get("Title", "")[:100],
                })
    return {
        "total_vulnerabilities": total,
        "by_severity": dict(by_severity),
        "by_package_type": dict(by_pkg_type),
        "fixable_count": fixable,
        "fix_rate": round(fixable / max(total, 1) * 100, 1),
        "critical_high_cves": sorted(cve_list, key=lambda x: x.get("cvss_score", 0), reverse=True)[:20],
    }


def analyze_secrets(scan_results):
    """Analyze secret detection results."""
    secrets = []
    for target in scan_results.get("Results", []):
        for secret in target.get("Secrets", []):
            secrets.append({
                "rule_id": secret.get("RuleID", ""),
                "category": secret.get("Category", ""),
                "title": secret.get("Title", ""),
                "severity": secret.get("Severity", ""),
                "target_file": target.get("Target", ""),
            })
    return {"total_secrets": len(secrets), "secrets": secrets[:15]}


def analyze_misconfigs(misconfig_results):
    """Analyze misconfiguration scan results."""
    findings = []
    for target in misconfig_results.get("Results", []):
        for mc in target.get("Misconfigurations", []):
            findings.append({
                "avd_id": mc.get("AVDID", ""),
                "title": mc.get("Title", ""),
                "severity": mc.get("Severity", ""),
                "target": target.get("Target", ""),
                "resolution": mc.get("Resolution", "")[:150],
            })
    by_sev = defaultdict(int)
    for f in findings:
        by_sev[f["severity"]] += 1
    return {"total_misconfigs": len(findings), "by_severity": dict(by_sev), "findings": findings[:15]}


def generate_report(image, vuln_analysis, secret_analysis, misconfig_analysis):
    critical = vuln_analysis["by_severity"].get("CRITICAL", 0)
    high = vuln_analysis["by_severity"].get("HIGH", 0)
    return {
        "timestamp": datetime.utcnow().isoformat(),
        "image": image,
        "vulnerability_summary": vuln_analysis,
        "secret_summary": secret_analysis,
        "misconfiguration_summary": misconfig_analysis,
        "gate_result": "FAIL" if critical > 0 else "WARN" if high > 0 else "PASS",
    }


def main():
    parser = argparse.ArgumentParser(description="Trivy Container Security Scanning Agent")
    parser.add_argument("--image", required=True, help="Container image to scan (e.g., nginx:latest)")
    parser.add_argument("--config-path", help="Path to Dockerfile/K8s manifests for misconfig scan")
    parser.add_argument("--severity", default="CRITICAL,HIGH,MEDIUM", help="Severity filter")
    parser.add_argument("--sbom", action="store_true", help="Generate CycloneDX SBOM")
    parser.add_argument("--output", default="trivy_scan_report.json")
    args = parser.parse_args()

    scan_results = run_trivy_scan(args.image, severity=args.severity)
    vuln_analysis = analyze_vulnerabilities(scan_results)
    secret_analysis = analyze_secrets(scan_results)
    misconfig_analysis = {}
    if args.config_path:
        misconfig_results = run_trivy_misconfig(args.config_path)
        misconfig_analysis = analyze_misconfigs(misconfig_results)
    if args.sbom:
        sbom_data = generate_sbom(args.image)
        if sbom_data:
            with open(args.output.replace(".json", "_sbom.json"), "w") as f:
                f.write(sbom_data)
    report = generate_report(args.image, vuln_analysis, secret_analysis, misconfig_analysis)
    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    logger.info("Trivy: %s - %d vulns (%d critical), %d secrets, gate: %s",
                args.image, vuln_analysis["total_vulnerabilities"],
                vuln_analysis["by_severity"].get("CRITICAL", 0),
                secret_analysis["total_secrets"], report["gate_result"])
    print(json.dumps(report, indent=2, default=str))


if __name__ == "__main__":
    main()
Keep exploring