Trivy Security Testing: Container Image Scanning, Misconfiguration Detection, and Supply Chain Security

Trivy is Aqua Security's open-source vulnerability scanner, now one of the most widely deployed security scanning tools in DevSecOps pipelines. It scans container images, filesystems, Git repositories, IaC configurations, and Kubernetes manifests for vulnerabilities, secrets, and misconfigurations. Its ubiquity in CI/CD pipelines makes it a target for authorized security assessment: scanner suppression, false negative exploitation, registry credential exposure, SBOM integrity attacks, and pipeline injection. This guide covers Trivy from a security tester's perspective — both assessing Trivy-protected environments and testing the scanner itself.

Table of Contents

  1. Trivy Architecture and Attack Surface
  2. Registry Credential Exposure
  3. Vulnerability Suppression and False Negative Abuse
  4. Container Image Vulnerability Scanning
  5. IaC and Kubernetes Misconfiguration Testing
  6. Secret Detection Bypass Techniques
  7. SBOM Generation and Supply Chain Analysis
  8. CI/CD Pipeline Integration Security
  9. Trivy Vulnerability Database Attacks
  10. Hardening Checklist

Trivy Architecture and Attack Surface

Trivy operates as a client-server tool or standalone CLI. In client-server mode, a Trivy server caches the vulnerability database and serves scan requests from multiple clients — the server becomes a central target. In standalone mode, each Trivy instance downloads the vulnerability database from GitHub releases. The attack surface includes the scanner configuration, registry authentication credentials, the vulnerability database itself, and the CI/CD pipeline that invokes Trivy.

Key Components

ComponentDefault LocationSecurity Risk
Trivy Server API:4954/trivyUnauthenticated scan submissions; database cache poisoning
Docker credentials~/.docker/config.jsonRegistry access for private images; lateral movement
Vulnerability database cache~/.cache/trivy/db/Stale DB → false negatives; tampered DB → suppressed CVEs
trivy.yaml / .trivyignoreProject root or HOMESuppressed vulnerabilities; severity filters hiding criticals
CI/CD environment variablesPipeline secretsRegistry tokens; TRIVY_USERNAME/PASSWORD in logs
SBOM filesScan output artifactsFull component inventory enabling targeted exploit research

Registry Credential Exposure

Trivy requires registry credentials to pull private container images for scanning. These credentials are typically stored in ~/.docker/config.json, passed via environment variables, or injected as CI/CD secrets. Registry credentials extracted from Trivy's execution environment provide pull (and sometimes push) access to private container registries — enabling supply chain attacks, sensitive image inspection, and lateral movement.

Locating Trivy Registry Credentials

# Docker credential file — used by Trivy automatically
cat ~/.docker/config.json
# Contains base64-encoded registry credentials:
# {"auths": {"registry.example.com": {"auth": "dXNlcjpwYXNz"}}}
# Decode: echo "dXNlcjpwYXNz" | base64 -d  → user:pass

# Trivy-specific environment variables
env | grep -E "TRIVY_USERNAME|TRIVY_PASSWORD|TRIVY_TOKEN|TRIVY_REGISTRY"
# TRIVY_USERNAME / TRIVY_PASSWORD — used for registry auth
# TRIVY_GITHUB_TOKEN — used for GitHub Container Registry (ghcr.io)

# Credential helpers (often hold real tokens)
cat ~/.docker/config.json | python3 -c "
import sys, json, subprocess, base64
config = json.load(sys.stdin)
for registry, creds in config.get('auths', {}).items():
    if 'auth' in creds:
        decoded = base64.b64decode(creds['auth']).decode()
        print(f'{registry}: {decoded}')
    elif 'credsStore' in config:
        result = subprocess.run(['docker-credential-' + config['credsStore'], 'get'],
                                input=registry, capture_output=True, text=True)
        print(f'{registry} via store: {result.stdout}')
"

# Kubernetes secret containing registry credentials (often used with Trivy Operator)
kubectl get secret regcred -o yaml 2>/dev/null
kubectl get secrets -A -o json | python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
for item in data.get('items', []):
    if item.get('type') == 'kubernetes.io/dockerconfigjson':
        raw = item['data'].get('.dockerconfigjson', '')
        config = json.loads(base64.b64decode(raw))
        for reg, creds in config.get('auths', {}).items():
            auth = base64.b64decode(creds.get('auth', '')).decode()
            print(f'Namespace: {item[\"metadata\"][\"namespace\"]} | Registry: {reg} | Creds: {auth}')
"

Using Extracted Registry Credentials

# Authenticate to private registry with extracted credentials
docker login registry.example.com -u USER -p PASS

# List all repositories in a private registry (if registry API exposed)
# Docker Registry HTTP API v2
curl -s -u USER:PASS https://registry.example.com/v2/_catalog | python3 -m json.tool

# List tags for a repository
curl -s -u USER:PASS https://registry.example.com/v2/myapp/tags/list

# Pull a private image to inspect its contents
docker pull registry.example.com/myapp:production
docker save registry.example.com/myapp:production | tar xv

# Inspect image layers for embedded secrets
docker history --no-trunc registry.example.com/myapp:production
docker inspect registry.example.com/myapp:production | python3 -m json.tool

# Extract filesystem from image layers
docker create --name tmp registry.example.com/myapp:production
docker export tmp | tar -xv --wildcards "*.env" "*.conf" "*.key" "*.pem"
docker rm tmp

# Scan with Trivy itself to enumerate components before manual inspection
trivy image --username USER --password PASS registry.example.com/myapp:production
ECR and GCR token abuse: AWS ECR tokens (AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY) obtained from Trivy's CI/CD environment may grant ListImages, DescribeImages, and GetDownloadUrlForLayer beyond just scanning. GCP service account JSON stored for GCR access may have broader IAM permissions than registry-only.

Vulnerability Suppression and False Negative Abuse

Trivy supports multiple mechanisms for suppressing findings: .trivyignore files, inline annotations, and configuration-level severity filters. Security teams often suppress known accepted risks — but misconfigured suppression rules or over-broad ignore patterns silently hide critical vulnerabilities. Assessing the suppression configuration is essential for any authorized Trivy security review.

Auditing Suppression Configuration

# Find .trivyignore files in the repository
find . -name ".trivyignore" -o -name ".trivyignore.yaml" | xargs cat 2>/dev/null

# Example of a dangerously broad .trivyignore:
# CVE-2023-44487       # HTTP/2 Rapid Reset — suppressed everywhere
# HIGH                 # Ignores ALL high-severity findings — never acceptable

# .trivyignore.yaml format (Trivy v0.46+)
# More structured but also more dangerous when misconfigured:
cat .trivyignore.yaml 2>/dev/null
# vulnerabilities:
#   - id: CVE-2021-44228        # Log4Shell — should never be suppressed globally
#     paths:
#       - "**/*"                # Wildcard suppresses across ALL paths

# Check trivy.yaml for severity filtering
cat trivy.yaml ~/.trivy.yaml /etc/trivy/trivy.yaml 2>/dev/null
# severity: HIGH,CRITICAL   (missing LOW/MEDIUM — findings suppressed from output)
# exit-code: 0              (scan never fails the pipeline — defeats the purpose)
# ignore-unfixed: true      (suppresses all CVEs without upstream patches)

# Scan WITH all suppressions disabled to see what's hidden
trivy image --ignorefile /dev/null --severity LOW,MEDIUM,HIGH,CRITICAL \
  --ignore-unfixed=false registry.example.com/myapp:latest

# Compare suppressed vs unsuppressed results
trivy image registry.example.com/myapp:latest --format json > suppressed.json
trivy image registry.example.com/myapp:latest --ignorefile /dev/null \
  --severity LOW,MEDIUM,HIGH,CRITICAL --format json > full.json
python3 -c "
import json
suppressed = {v['VulnerabilityID'] for v in json.load(open('suppressed.json')).get('Results',[{}])[0].get('Vulnerabilities',[])}
full = {v['VulnerabilityID'] for v in json.load(open('full.json')).get('Results',[{}])[0].get('Vulnerabilities',[])}
hidden = full - suppressed
print(f'Hidden by suppression rules: {len(hidden)} CVEs')
for cve in sorted(hidden): print(f'  {cve}')
"

Container Annotation-Based Suppression

# Trivy respects inline annotations in Kubernetes manifests and Dockerfiles
# An attacker with access to modify manifests can suppress their own backdoor's CVEs

# Kubernetes manifest annotation suppression (Trivy Operator)
# If you can modify a deployment, you can suppress findings on that workload:
kubectl annotate pod my-compromised-pod \
  "trivy-operator.aquasecurity.github.io/ignore-unfixed=true"

# Dockerfile label suppression
# A malicious base image can include labels that suppress its own vulnerabilities:
# LABEL trivy.ignore=CVE-2023-1234,CVE-2023-5678

# Check for suppression annotations in existing workloads
kubectl get pods -A -o json | python3 -c "
import sys, json
data = json.load(sys.stdin)
for item in data.get('items', []):
    anns = item.get('metadata', {}).get('annotations', {})
    trivy_anns = {k: v for k, v in anns.items() if 'trivy' in k.lower()}
    if trivy_anns:
        ns = item['metadata']['namespace']
        name = item['metadata']['name']
        print(f'{ns}/{name}: {trivy_anns}')
"

Container Image Vulnerability Scanning

Trivy's primary function is scanning container images for OS package vulnerabilities and language-level dependency vulnerabilities. From a security testing perspective, the key questions are: what does the image actually contain, what is Trivy finding vs. missing, and how does the severity of findings map to real exploitability?

Comprehensive Image Scanning

# Full image scan with all vulnerability classes
trivy image \
  --severity LOW,MEDIUM,HIGH,CRITICAL \
  --vuln-type os,library \
  --format json \
  --output scan-results.json \
  registry.example.com/myapp:production

# Scan specific vulnerability classes separately
# OS-level packages (apt, rpm, apk)
trivy image --vuln-type os registry.example.com/myapp:production

# Language dependencies (npm, pip, go modules, maven, nuget)
trivy image --vuln-type library registry.example.com/myapp:production

# Include unfixed CVEs (often hidden by default)
trivy image --ignore-unfixed=false registry.example.com/myapp:production

# Scan a locally saved image
docker save myapp:latest | trivy image --input /dev/stdin

# Scan a running container's filesystem
trivy rootfs /proc/$(docker inspect --format '{{.State.Pid}}' myapp)/root/

# Extract CRITICAL and HIGH CVEs with exploit availability
trivy image --format json registry.example.com/myapp:production | python3 -c "
import json, sys
data = json.load(sys.stdin)
for result in data.get('Results', []):
    for vuln in result.get('Vulnerabilities', []):
        if vuln.get('Severity') in ('CRITICAL', 'HIGH'):
            cve = vuln.get('VulnerabilityID')
            pkg = vuln.get('PkgName')
            ver = vuln.get('InstalledVersion')
            fixed = vuln.get('FixedVersion', 'No fix')
            refs = [r for r in vuln.get('References', []) if 'exploit' in r.lower()]
            if refs:
                print(f'[EXPLOIT] {cve} | {pkg}@{ver} → fix: {fixed}')
                for r in refs: print(f'  {r}')
"

Comparing Images Across Environments

# Organizations often run different image versions in dev vs. prod
# Compare vulnerability profiles to identify production-specific exposure

# Scan all image tags to find which environment has the most exposure
for TAG in dev staging production; do
  echo "=== $TAG ==="
  trivy image --format json registry.example.com/myapp:$TAG 2>/dev/null | python3 -c "
import json, sys
data = json.load(sys.stdin)
all_vulns = []
for r in data.get('Results', []):
    all_vulns.extend(r.get('Vulnerabilities', []))
critical = sum(1 for v in all_vulns if v.get('Severity') == 'CRITICAL')
high = sum(1 for v in all_vulns if v.get('Severity') == 'HIGH')
print(f'  CRITICAL: {critical}  HIGH: {high}  TOTAL: {len(all_vulns)}')
"
done

# Identify CVEs present in production but patched in dev (regression)
trivy image --format json --output dev.json registry.example.com/myapp:dev
trivy image --format json --output prod.json registry.example.com/myapp:production
python3 -c "
import json
dev_cves = {v['VulnerabilityID'] for r in json.load(open('dev.json')).get('Results',[]) for v in r.get('Vulnerabilities',[])}
prod_cves = {v['VulnerabilityID'] for r in json.load(open('prod.json')).get('Results',[]) for v in r.get('Vulnerabilities',[])}
regression = prod_cves - dev_cves
print('CVEs in production but fixed in dev (regressions):')
for c in sorted(regression): print(f'  {c}')
"

IaC and Kubernetes Misconfiguration Testing

Trivy's misconfiguration scanner checks Terraform, CloudFormation, Helm charts, Kubernetes manifests, Dockerfiles, and other IaC formats against a library of built-in and custom policies. Understanding what Trivy checks — and what it misses — is valuable for both security engineers hardening environments and pentesters identifying gaps in automated detection.

Scanning IaC for Misconfigurations

# Scan Terraform files
trivy config --format json /path/to/terraform/ > tf-findings.json

# Scan Kubernetes manifests
trivy config --format json /path/to/k8s-manifests/ > k8s-findings.json

# Scan Helm chart (rendered output)
helm template myapp ./charts/myapp | trivy config --format json -

# Scan Dockerfile
trivy config --format json Dockerfile

# Scan CloudFormation
trivy config --format json template.yaml

# Extract high-priority misconfigurations from results
python3 -c "
import json
data = json.load(open('k8s-findings.json'))
for result in data.get('Results', []):
    for mis in result.get('Misconfigurations', []):
        if mis.get('Severity') in ('CRITICAL', 'HIGH'):
            print(f\"[{mis['Severity']}] {mis['ID']}: {mis['Title']}\")
            print(f\"  {mis.get('Description', '')[:120]}\")
            print(f\"  Resolution: {mis.get('Resolution', 'N/A')}\")
            print()
"

# Check what Trivy MISSES — compare against checkov or kube-bench for gaps
trivy config --format json /path/to/k8s/ > trivy-k8s.json
checkov -d /path/to/k8s/ -o json > checkov-k8s.json

# Map Trivy check IDs to human-readable names
trivy config --list-all-misconfigs 2>/dev/null | head -50

High-Value Kubernetes Misconfigurations Trivy Detects

Check IDFindingSecurity Impact
KSV001Container running as rootContainer escape privilege escalation
KSV006Capabilities not droppedNET_RAW, SYS_PTRACE enable attack pivoting
KSV017Privileged containerFull host kernel access; complete node compromise
KSV019hostPID: trueProcess injection on the host node
KSV020hostNetwork: trueFull access to host network stack
KSV030No seccomp profileUnrestricted syscall access
AVD-AWS-0001S3 bucket public readPublic data exposure
AVD-AWS-0057IAM policy wildcard actionsPrivilege escalation paths

Secret Detection Bypass Techniques

Trivy's secret scanner detects hardcoded credentials in container images, filesystems, and source code repositories. It uses a combination of regex patterns and entropy analysis to identify API keys, tokens, passwords, and private keys. Understanding its detection logic helps both ensure comprehensive coverage and identify blind spots.

Scanning for Secrets

# Enable secret scanning on a container image
trivy image --scanners secret registry.example.com/myapp:production

# Scan a filesystem or Git repository
trivy fs --scanners secret /path/to/codebase
trivy repo --scanners secret https://github.com/org/repo

# Trivy secret detects patterns including:
# - AWS Access Keys (AKIA...)
# - GitHub/GitLab tokens
# - Google API keys
# - Private keys (-----BEGIN RSA/EC/PGP PRIVATE KEY-----)
# - Generic high-entropy strings in common variable names
# - Docker registry auth tokens

# Check what Trivy secret scanner covers
trivy image --scanners secret --format json registry.example.com/myapp:production | \
  python3 -c "
import json, sys
data = json.load(sys.stdin)
for result in data.get('Results', []):
    for secret in result.get('Secrets', []):
        print(f\"[{secret.get('Severity')}] {secret.get('RuleID')}: {secret.get('Title')}\")
        print(f\"  File: {secret.get('Target')} Line: {secret.get('StartLine')}\")
        print(f\"  Match: {secret.get('Match', '')[:80]}\")
"

Known Detection Gaps

# Trivy secret scanning has known blind spots:
# 1. Secrets in environment variables (set at runtime, not baked into image)
# 2. Encrypted secrets (base64-encoded but not base64-decoded for scanning)
# 3. Secrets in binary files (compiled into application code)
# 4. Secrets injected via Kubernetes secrets or config maps (runtime injection)
# 5. Short-lived tokens (rotated before scanning)

# Complement Trivy with targeted manual inspection for gaps:

# Check for base64-encoded secrets in image layers
docker save registry.example.com/myapp:production | tar -xO \
  | strings | grep -E "^[A-Za-z0-9+/]{40,}={0,2}$" | \
  while read b64; do
    decoded=$(echo "$b64" | base64 -d 2>/dev/null)
    if echo "$decoded" | grep -qE "AKIA|sk-|ghp_|glpat-|-----BEGIN"; then
      echo "Encoded secret found: $b64"
      echo "Decoded: $decoded"
    fi
  done

# Check for secrets in environment variables within Dockerfile history
docker history --no-trunc registry.example.com/myapp:production | \
  grep -E "ENV.*[Pp]ass|ENV.*[Tt]oken|ENV.*[Kk]ey|ENV.*[Ss]ecret"

# Check for secrets in labels
docker inspect registry.example.com/myapp:production | \
  python3 -c "
import sys, json
data = json.load(sys.stdin)
for item in data:
    labels = item.get('Config', {}).get('Labels', {})
    for k, v in labels.items():
        if any(word in k.lower() for word in ['secret', 'key', 'token', 'pass', 'auth']):
            print(f'{k}: {v}')
"

SBOM Generation and Supply Chain Analysis

Trivy generates Software Bill of Materials (SBOM) in CycloneDX and SPDX formats, providing a complete inventory of packages, versions, and dependencies in a container image or application. SBOMs are valuable for supply chain risk assessment — identifying whether specific vulnerable components are present and mapping blast radius when a new CVE is published.

Generating and Analyzing SBOMs

# Generate SBOM in CycloneDX format
trivy image --format cyclonedx --output sbom.json registry.example.com/myapp:production

# Generate SBOM in SPDX format
trivy image --format spdx-json --output sbom.spdx.json registry.example.com/myapp:production

# Extract component inventory from SBOM
python3 -c "
import json
sbom = json.load(open('sbom.json'))
components = sbom.get('components', [])
print(f'Total components: {len(components)}')
print()
print('Libraries and versions:')
for c in sorted(components, key=lambda x: x.get('name','')):
    name = c.get('name')
    version = c.get('version', 'unknown')
    purl = c.get('purl', '')
    print(f'  {name}@{version}  ({purl})')
"

# Cross-reference SBOM against known-vulnerable versions
# Using OSV (Open Source Vulnerabilities) API
python3 - <<'EOF'
import json, urllib.request, urllib.parse

sbom = json.load(open('sbom.json'))
components = sbom.get('components', [])

for comp in components[:20]:  # first 20 for demo
    name = comp.get('name', '')
    version = comp.get('version', '')
    purl = comp.get('purl', '')
    if not (name and version):
        continue
    payload = json.dumps({"version": version, "package": {"name": name, "ecosystem": "PyPI"}}).encode()
    try:
        req = urllib.request.Request("https://api.osv.dev/v1/query",
                                     data=payload, headers={"Content-Type": "application/json"})
        resp = urllib.request.urlopen(req, timeout=5)
        vulns = json.loads(resp.read()).get('vulns', [])
        if vulns:
            print(f"VULNERABLE: {name}@{version} — {len(vulns)} CVEs")
            for v in vulns[:3]:
                print(f"  {v.get('id')}: {v.get('summary','')[:80]}")
    except Exception:
        pass
EOF

# Identify transitive dependencies hidden from direct inspection
trivy image --format cyclonedx registry.example.com/myapp:production | \
  python3 -c "
import json, sys
sbom = json.load(sys.stdin)
deps = sbom.get('dependencies', [])
total_transitive = sum(len(d.get('dependsOn', [])) for d in deps)
print(f'Direct dependencies: {len(deps)}')
print(f'Total dependency edges: {total_transitive}')
"

Supply Chain Attack Surface Assessment

# Identify base images used across your container fleet
# Each base image is a shared supply chain dependency

# Extract base image from Dockerfile
grep "^FROM" Dockerfile | head -5

# Scan the base image for inherited vulnerabilities
BASE_IMAGE=$(grep "^FROM" Dockerfile | head -1 | awk '{print $2}')
trivy image --severity HIGH,CRITICAL "$BASE_IMAGE"

# Check if base images use digest pinning (protection against tag mutation attacks)
# Vulnerable: FROM node:18-alpine  (mutable tag)
# Secure: FROM node:18-alpine@sha256:abcdef...  (immutable digest)
grep "^FROM" Dockerfile | grep -v "@sha256" | wc -l
echo "Unpinned base image references (vulnerable to tag mutation)"

# Check for typosquatted base images
# Legitimate: node, python, ubuntu, debian, alpine
# Suspicious: nod3, pyth0n, ubontu
grep "^FROM" Dockerfile | awk '{print $2}' | cut -d: -f1 | cut -d@ -f1 | \
  while read img; do
    # Check against known official images
    if ! docker search "$img" 2>/dev/null | grep -q "^$img "; then
      echo "WARNING: Possibly unofficial base image: $img"
    fi
  done

CI/CD Pipeline Integration Security

Trivy is commonly integrated into CI/CD pipelines as a quality gate — failing builds that contain CRITICAL or HIGH vulnerabilities. The security of this integration itself is an often-overlooked attack surface: pipeline configuration can disable security gates, exit codes can be silently overridden, and scanner credentials can be extracted from build environments.

Auditing Trivy CI/CD Integration

# Common Trivy CI/CD configurations and their weaknesses

# GitHub Actions — check for security bypasses
cat .github/workflows/*.yml | grep -A5 "trivy"
# Red flags:
# --exit-code 0        (always succeeds, never blocks the pipeline)
# --severity LOW       (only scans low severity — criticals hidden)
# continue-on-error: true  (failure is ignored)
# --ignorefile path/to/large-ignore-file  (suppresses many findings)

# GitLab CI — check for allow_failure
cat .gitlab-ci.yml | grep -B2 -A10 "trivy"
# Red flag: allow_failure: true on the trivy scan job

# Jenkins — Trivyignore path in Jenkinsfile
grep -r "trivy" Jenkinsfile | grep -E "\-\-ignorefile|\-\-exit-code 0"

# Check if Trivy findings actually fail the pipeline
# A misconfigured gate that never fails defeats the entire point
grep -r "trivy" .github/workflows/ .gitlab-ci.yml Jenkinsfile 2>/dev/null | \
  grep -E "\-\-exit-code [^1]|exit-code: [^1]|TRIVY_EXIT_CODE=0"

# Environment variable exposure in CI/CD
# TRIVY_USERNAME/TRIVY_PASSWORD logged in build output
grep -r "TRIVY_PASSWORD\|TRIVY_TOKEN" .github/workflows/ .gitlab-ci.yml 2>/dev/null
# Check if these are properly masked as secrets or hardcoded

Trivy Server Security

# Trivy server mode exposes an HTTP API
# Default: unauthenticated, no TLS

# Check if Trivy server is exposed
nmap -sV -p 4954 TARGET_IP
curl -s http://TARGET_IP:4954/healthz

# Submit an arbitrary image scan to an exposed Trivy server
curl -s http://TARGET_IP:4954/trivy \
  -H "Content-Type: application/json" \
  -d '{"image": "ubuntu:22.04", "options": {"severity": ["CRITICAL"]}}'

# Cache poisoning: Trivy server caches the vulnerability database
# If the database update mechanism can be intercepted (e.g. via DNS poisoning),
# a modified database can suppress specific CVEs from scan results
# Check the database update URL
trivy image --debug target-image 2>&1 | grep -E "db\.|ghcr\.io|database"

# Force Trivy to use a specific (potentially outdated) database
trivy image --skip-db-update --cache-dir /path/to/stale-cache target-image

Trivy Vulnerability Database Attacks

Trivy downloads its vulnerability database from GitHub Container Registry (ghcr.io). Organizations running Trivy in air-gapped environments use internal database mirrors. The integrity of the vulnerability database directly determines the accuracy of scan results — a tampered or stale database is a form of security control bypass.

# Check database age — a stale database misses recent CVEs
trivy image --format json target-image 2>&1 | grep -i "db\|database\|updated"
# Or check directly:
cat ~/.cache/trivy/db/metadata.json 2>/dev/null
# {"Version":2,"NextUpdate":"...","UpdatedAt":"...","DownloadedAt":"..."}

# A database older than 24 hours is missing recent CVE publications
python3 -c "
import json, datetime
meta = json.load(open('/root/.cache/trivy/db/metadata.json'))
updated = datetime.datetime.fromisoformat(meta.get('UpdatedAt','').replace('Z','+00:00'))
age = (datetime.datetime.now(datetime.timezone.utc) - updated).total_seconds() / 3600
print(f'Database age: {age:.1f} hours')
if age > 24:
    print('WARNING: Database is stale — new CVEs from the last {age:.0f} hours not covered')
"

# Custom database mirror security
# Organizations may run private Trivy DB mirrors
# Check Trivy configuration for mirror URLs
grep -r "db-repository\|TRIVY_DB_REPOSITORY" /etc/trivy/ ~/.trivy.yaml trivy.yaml 2>/dev/null
# If pointing to an internal mirror, verify mirror integrity
# A compromised mirror can suppress specific CVEs from all scans

# Force fresh database download
trivy image --download-db-only
trivy image --reset target-image  # clears cache

# Check if Java/Go vulnerability databases are enabled (often missed)
trivy image --java-db-repository ghcr.io/aquasecurity/trivy-java-db \
  --scanners vuln,secret target-image
Ironimo complements Trivy: Where Trivy focuses on known CVEs and IaC misconfigurations, Ironimo performs active web application security testing — testing runtime behavior, authentication, authorization, and business logic that static image scanning cannot assess.

Hardening Checklist

ControlActionPriority
Enforce non-zero exit codesSet --exit-code 1 in CI/CD; never use continue-on-error: true on Trivy stepsCritical
Scan all severity levelsUse --severity LOW,MEDIUM,HIGH,CRITICAL; do not exclude LOW/MEDIUM from automated scansCritical
Review .trivyignore files regularlyAudit suppressions quarterly; require justification and expiry dates for each entryHigh
Protect registry credentialsUse short-lived registry tokens (ECR, GCR); never hardcode credentials in pipeline YAMLHigh
Pin base image digestsUse FROM image@sha256:... in Dockerfiles to prevent tag mutation attacksHigh
Authenticate Trivy ServerDeploy Trivy server behind an authenticating reverse proxy; restrict to internal network onlyHigh
Keep database freshUpdate Trivy DB daily in CI/CD; alert if database age exceeds 24 hoursHigh
Enable SBOM generationGenerate and store SBOM as a build artifact; use for rapid blast-radius assessment on new CVEsMedium
Enable secret scanningAdd --scanners vuln,secret,misconfig to all scans; not just vulnerability scanningMedium
Verify mirror integrityIf using internal DB mirror, verify checksum on download; alert on database hash mismatchMedium

Go Beyond Trivy with Active Security Testing

Trivy tells you what's in your containers. Ironimo tests what your running application actually does — authentication flows, authorization logic, injection vectors, and business logic vulnerabilities that image scanning cannot detect. Use both together.

Start free scan