Testland
Browse all skills & agents

trivy-config

Runs Trivy's misconfiguration scanner (`trivy config`) against IaC directories to detect security issues across Terraform, CloudFormation, Kubernetes manifests, Helm charts, Dockerfiles, and Azure ARM templates - installs Trivy, scans with severity gating via `--exit-code`, suppresses findings via `.trivyignore` / `.trivyignore.yaml` or inline annotations, extends built-in checks with custom Rego policies, and emits SARIF for GitHub Code Scanning. Trivy is the forward path from tfsec (per Aqua Security's own migration guidance). Use when adopting a consolidated IaC scanner for new projects, migrating away from tfsec, or scanning mixed IaC stacks with a single tool.

trivy-config

Overview

Per trivy.dev misconfiguration docs:

"Trivy provides built-in checks to detect configuration issues in popular Infrastructure as Code files, such as: Docker, Kubernetes, Terraform, CloudFormation, and more."

Trivy is Aqua Security's consolidated scanner and the forward path from tfsec. Per the tfsec skill and Aqua's own documentation, new projects should evaluate Trivy first; tfsec's checks ship inside Trivy under trivy config.

Step 1 - Install

Per trivy.dev (v0.71.0 is the latest release as of 2026-06-04):

# macOS
brew install trivy

# Debian / Ubuntu
sudo apt-get install wget apt-transport-https gnupg
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key \
  | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] \
  https://aquasecurity.github.io/trivy-repo/deb generic main" \
  | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install trivy

# RPM (RHEL / Fedora)
sudo rpm -ivh https://github.com/aquasecurity/trivy/releases/latest/download/trivy_Linux-64bit.rpm

# Docker (no local install)
docker run --rm -v $(pwd):/workspace aquasec/trivy config /workspace

Verify: trivy --version.

Step 2 - First scan

Per cli reference, trivy config accepts a path (file or directory). Trivy auto-detects IaC types - Terraform, CloudFormation, Kubernetes manifests, Helm charts, Dockerfiles, and Azure ARM templates can all coexist in the same directory per mc docs:

"The specified directory can contain mixed types of IaC files. Trivy automatically detects config types and applies relevant checks."

# Scan the current directory (all IaC types)
trivy config .

# Scan a specific subdirectory
trivy config ./infra/

# Show only HIGH and CRITICAL findings
trivy config --severity HIGH,CRITICAL .

# Fail CI when any finding is found (exit code 1)
trivy config --exit-code 1 --severity HIGH,CRITICAL .

# Include passed checks alongside failures
trivy config --include-non-failures .

Built-in checks are distributed as an OPA bundle at ghcr.io/aquasecurity/trivy-checks (per checks repo). Trivy caches the bundle locally and refreshes every 24 hours. An embedded fallback is included in the binary for air-gapped environments.

Step 3 - Severity gating

Per cli reference, --exit-code and --severity are the two levers for CI gating:

FlagPurposeExample
--exit-code intExit code when findings match--exit-code 1
--severity stringsComma-separated severity filterHIGH,CRITICAL

Pattern: gate hard on CRITICAL first; expand to HIGH after the team has reviewed the initial finding set.

# Hard fail on CRITICAL only (bootstrap phase)
trivy config --exit-code 1 --severity CRITICAL .

# Ratchet: add HIGH once existing findings are triaged
trivy config --exit-code 1 --severity HIGH,CRITICAL .

Step 4 - Output formats

Per trivy.dev reporting docs, --format accepts:

ValueUse case
table (default)Human-readable terminal output
jsonMachine-parseable; pipe to jq
sarifGitHub Code Scanning / SARIF 2.1.0
templateCustom templates (JUnit, ASFF, HTML via contrib/)
# JSON for parsing / badge generation
trivy config --format json --output trivy.json .

# SARIF for GitHub Code Scanning
trivy config --format sarif --output trivy.sarif .

# JUnit XML for CI test reporting
trivy config --format template \
  --template "@contrib/junit.tpl" \
  --output trivy-junit.xml .

Step 5 - Suppressing findings

Per trivy.dev filtering docs:

.trivyignore (check-ID list)

# .trivyignore
# Suppress a specific misconfig check
AVD-DS-0002

# Suppress a CVE alongside a misconfig in the same file
CVE-2018-14618

.trivyignore.yaml (structured suppression)

Per filter docs, the YAML variant supports scoped expiring suppressions:

# .trivyignore.yaml
misconfigurations:
  - id: AVD-DS-0001
  - id: AVD-DS-0002
    paths:
      - "infra/legacy/Dockerfile"
    statement: "Legacy image; migration tracked in JIRA-4321"
    expired_at: "2026-12-31"

Run with: trivy config --ignorefile ./.trivyignore.yaml .

Per cli reference, --ignorefile defaults to .trivyignore and accepts an alternate path.

Rego-based ignore policy

Per filter docs, pass --ignore-policy with a Rego file that contains a trivy package and an ignore rule:

# ignore_legacy.rego
package trivy

default ignore = false

ignore {
    input.Type == "terraform"
    input.Namespace == "user.legacy"
}
trivy config --ignore-policy ignore_legacy.rego .

Step 6 - Custom Rego policies

Per trivy.dev custom checks docs, pass custom policies with --config-check and scope them with --namespaces:

trivy config \
  --config-check ./policies/ \
  --namespaces user \
  ./infra/

Per custom docs, each policy file requires a unique package declaration and a METADATA annotation block:

# policies/require_cost_center_tag.rego
# METADATA
# title: "EC2 instances must have cost_center tag"
# description: "Untagged resources cannot be allocated to cost centers"
# schemas:
#   - input: schema["cloud"]
# custom:
#   id: USER-TF-001
#   severity: HIGH
#   input:
#     selector:
#       - type: cloud

package user.terraform.USER-TF-001

import rego.v1

deny contains res if {
    instance := input.aws.ec2.instances[_]
    not instance.tags["cost_center"]
    res := result.new(
        sprintf("EC2 instance '%s' missing cost_center tag", [instance.id.value]),
        instance,
    )
}

Per custom docs, the --namespaces value (here: user) must match the first segment of the package path. Supported input.selector types include cloud (Terraform / CloudFormation), kubernetes, dockerfile, yaml, json, toml, and terraform-raw.

Step 7 - CI integration (GitHub Actions)

Per trivy.dev reporting docs, SARIF output integrates directly with GitHub Code Scanning:

# .github/workflows/trivy-iac.yml
jobs:
  trivy-config:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v5

      - name: Run Trivy config scan
        uses: aquasecurity/trivy-action@0.31.0
        with:
          scan-type: config
          scan-ref: .
          severity: HIGH,CRITICAL
          exit-code: 1
          format: sarif
          output: trivy.sarif
          ignore-unfixed: true

      - name: Upload SARIF to GitHub Code Scanning
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy.sarif

The if: always() on the upload step ensures findings appear in the Security tab even when the scan step exits non-zero.

To scope the scan to a single IaC type, pass --misconfig-scanners per cli reference:

# Terraform only
trivy config \
  --misconfig-scanners terraform \
  ./terraform/

# Kubernetes manifests only
trivy config \
  --misconfig-scanners kubernetes \
  ./k8s/

Per cli reference, --misconfig-scanners accepts a comma-separated list from: azure-arm, cloudformation, dockerfile, helm, kubernetes, terraform, terraformplan-json, terraformplan-snapshot.

Anti-patterns

Anti-patternWhy it failsFix
--exit-code 0 in CIMisconfigs are logged but never blockUse --exit-code 1 with --severity HIGH,CRITICAL (Step 3)
.trivyignore entries without a statementInvisible to reviewers; silent security debtUse .trivyignore.yaml with statement + expired_at (Step 5)
Running trivy config and tfsec in parallel without a unifierDuplicate findings flood CI outputRoute through iac-policy-checker
Custom policies missing METADATA blockNo severity, no title in report outputAlways include METADATA with id, severity, schemas (Step 6)
Skipping bundle updates (--skip-check-update) permanentlyStale checks miss new misconfig rulesUse for caching in CI; re-enable updates on a scheduled run

Limitations

  • Network required on first run. Trivy downloads the checks bundle from ghcr.io/aquasecurity/trivy-checks. Air-gapped setups need the embedded binary fallback or a mirror.
  • Terraform variable resolution is partial. Dynamic values computed at apply time may cause false positives; pass --tf-vars to reduce noise per cli reference.
  • Helm rendering requires chart values. Pass --helm-values for accurate rendering of templated manifests per cli reference.
  • Custom policy schemas must match input type. A policy with schema["cloud"] will not fire against Kubernetes manifests; use the correct selector type per custom docs.

References

  • mc - Trivy misconfiguration scanner overview, supported IaC types, auto-detection behavior, air-gap fallback, network requirements.
  • cli - trivy config CLI reference: --exit-code, --severity, --format, --output, --ignorefile, --config-check, --namespaces, --misconfig-scanners, --tf-vars, --helm-values, --cf-params, --include-non-failures.
  • custom - Custom Rego policy authoring: METADATA fields (title, description, schemas, custom.id, custom.severity, custom.input.selector.type), deny rule pattern, --namespaces scoping.
  • filter - .trivyignore and .trivyignore.yaml suppression format (id, paths, statement, expired_at), --ignore-policy Rego-based filtering.
  • report - Output formats: table, json, sarif, template; --output flag; SARIF 2.1.0 compliance.
  • checks - aquasecurity/trivy-checks - the upstream OPA bundle for all built-in checks.
  • tfsec-policy - Migration context: tfsec is transitioning to Trivy; this skill is the forward path.
  • checkov-policy - Sister scanner; broader Python-check framework, different rule coverage.
  • iac-policy-checker - Unifies Trivy + Checkov + KICS results into a single verdict.