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.
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.
ghcr.io/aquasecurity/trivy-db or a custom mirror. The database is cached at ~/.cache/trivy/db/. Stale databases miss recent CVEs.~/.docker/config.json, environment variables, or explicit flags. These credentials grant pull access to private registries.trivy.yaml or environment variables configure ignore files, severity filters, exit codes, and database sources. Misconfigured policies suppress critical findings.| Component | Default Location | Security Risk |
|---|---|---|
| Trivy Server API | :4954/trivy | Unauthenticated scan submissions; database cache poisoning |
| Docker credentials | ~/.docker/config.json | Registry access for private images; lateral movement |
| Vulnerability database cache | ~/.cache/trivy/db/ | Stale DB → false negatives; tampered DB → suppressed CVEs |
| trivy.yaml / .trivyignore | Project root or HOME | Suppressed vulnerabilities; severity filters hiding criticals |
| CI/CD environment variables | Pipeline secrets | Registry tokens; TRIVY_USERNAME/PASSWORD in logs |
| SBOM files | Scan output artifacts | Full component inventory enabling targeted exploit research |
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.
# 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}')
"
# 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
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.
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.
# 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}')
"
# 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}')
"
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?
# 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}')
"
# 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}')
"
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.
# 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
| Check ID | Finding | Security Impact |
|---|---|---|
| KSV001 | Container running as root | Container escape privilege escalation |
| KSV006 | Capabilities not dropped | NET_RAW, SYS_PTRACE enable attack pivoting |
| KSV017 | Privileged container | Full host kernel access; complete node compromise |
| KSV019 | hostPID: true | Process injection on the host node |
| KSV020 | hostNetwork: true | Full access to host network stack |
| KSV030 | No seccomp profile | Unrestricted syscall access |
| AVD-AWS-0001 | S3 bucket public read | Public data exposure |
| AVD-AWS-0057 | IAM policy wildcard actions | Privilege escalation paths |
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.
# 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]}\")
"
# 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}')
"
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.
# 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}')
"
# 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
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.
# 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 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 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
| Control | Action | Priority |
|---|---|---|
| Enforce non-zero exit codes | Set --exit-code 1 in CI/CD; never use continue-on-error: true on Trivy steps | Critical |
| Scan all severity levels | Use --severity LOW,MEDIUM,HIGH,CRITICAL; do not exclude LOW/MEDIUM from automated scans | Critical |
| Review .trivyignore files regularly | Audit suppressions quarterly; require justification and expiry dates for each entry | High |
| Protect registry credentials | Use short-lived registry tokens (ECR, GCR); never hardcode credentials in pipeline YAML | High |
| Pin base image digests | Use FROM image@sha256:... in Dockerfiles to prevent tag mutation attacks | High |
| Authenticate Trivy Server | Deploy Trivy server behind an authenticating reverse proxy; restrict to internal network only | High |
| Keep database fresh | Update Trivy DB daily in CI/CD; alert if database age exceeds 24 hours | High |
| Enable SBOM generation | Generate and store SBOM as a build artifact; use for rapid blast-radius assessment on new CVEs | Medium |
| Enable secret scanning | Add --scanners vuln,secret,misconfig to all scans; not just vulnerability scanning | Medium |
| Verify mirror integrity | If using internal DB mirror, verify checksum on download; alert on database hash mismatch | Medium |
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