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 tfsec successor - the forward path from tfsec per Aqua Security's own migration guidance - and the legacy tfsec workflow (install, custom YAML rules, ignore annotations, migration steps) is kept in references/tfsec-legacy.md. Use when adopting a consolidated IaC scanner for new projects, migrating away from tfsec (or still operating a Terraform-only tfsec stack), or scanning mixed IaC stacks with a single tool.

Install with skills.sh (any agent)

npx skills add testland/qa --skill trivy-config
View source

trivy-config

Overview

Trivy is Aqua Security's consolidated misconfiguration scanner (trivy config) and the forward path from tfsec. Per the tfsec legacy reference, trivy.dev misconfiguration docs (opens in new window), and Aqua's own documentation, new projects should evaluate Trivy first; tfsec's checks ship inside Trivy under trivy config.

Pinned versions

Bump these together when updating; they are the only version-sensitive tokens in this skill. GitHub release assets are version-stamped, so the RPM URL in Step 1 pins a tag rather than using latest/download.

ComponentPinUsed in
Trivyv0.72.0 (latest as of 2026-06-30)Step 1 install
aquasecurity/trivy-action0.31.0CI workflow (Step 7)
actions/checkoutv5CI workflow (Step 7)
github/codeql-action/upload-sarifv3CI workflow (Step 7)

Step 1 - Install

Per trivy.dev installation docs (opens in new window) (the RPM URL pins the Trivy tag from the Pinned versions section above):

# 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/download/v0.72.0/trivy_0.72.0_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 (opens in new window), 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 (opens in new window).

# 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 (opens in new window)). 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 (opens in new window), --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 (opens in new window), --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 (opens in new window):

.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 (opens in new window), 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 (opens in new window), --ignorefile defaults to .trivyignore and accepts an alternate path.

Rego-based ignore policy

Per filter docs (opens in new window), 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 (opens in new window), pass custom policies with --config-check and scope them with --namespaces:

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

Per custom docs (opens in new window), the --namespaces value (here: user) must match the first segment of the package path. For a full policy file (METADATA block + deny rule) and the list of supported input.selector types, see references/trivy-config.md.

Step 7 - CI integration (GitHub Actions)

Per trivy.dev reporting docs (opens in new window), SARIF output integrates directly with GitHub Code Scanning. For the full GitHub Actions workflow (with security-events: write, the pinned trivy-action, and an if: always() SARIF upload so findings surface even when the scan exits non-zero), see references/trivy-config.md.

To scope the scan to a single IaC type, pass --misconfig-scanners per cli reference (opens in new window):

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

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

Per cli reference (opens in new window), --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 both through a single unifying reporter
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 (opens in new window).
  • Helm rendering requires chart values. Pass --helm-values for accurate rendering of templated manifests per cli reference (opens in new window).
  • 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 (opens in new window).

References

  • mc (opens in new window) - Trivy misconfiguration scanner overview, supported IaC types, auto-detection behavior, air-gap fallback, network requirements.
  • inst (opens in new window) - Official install methods: Homebrew, the apt repository, direct .rpm release package, container image.
  • cli (opens in new window) - 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 (opens in new window) - Custom Rego policy authoring: METADATA fields (title, description, schemas, custom.id, custom.severity, custom.input.selector.type), deny rule pattern, --namespaces scoping.
  • filter (opens in new window) - .trivyignore and .trivyignore.yaml suppression format (id, paths, statement, expired_at), --ignore-policy Rego-based filtering.
  • report (opens in new window) - Output formats: table, json, sarif, template; --output flag; SARIF 2.1.0 compliance.
  • checks (opens in new window) - aquasecurity/trivy-checks - the upstream OPA bundle for all built-in checks.
  • tfsec legacy reference - Migration context: tfsec is transitioning to Trivy; this skill is the forward path. tfsec custom rules + CI live in references/tfsec-custom-rules-and-ci.md.
  • checkov-policy - Sister scanner; broader Python-check framework, different rule coverage.

tfsec custom rules and CI integration

View source (opens in new window)

tfsec custom rules and CI integration

Custom rules

# .tfsec/custom_checks.yml
checks:
  - code: CUS001
    description: Ensure all EC2 instances have a cost_center tag
    impact: Untagged resources cannot be allocated to cost centers
    resolution: Add a cost_center tag
    requiredTypes:
      - resource
    requiredLabels:
      - aws_instance
    severity: HIGH
    matchSpec:
      name: tags
      action: contains
      value: cost_center
    errorMessage: EC2 instance is missing cost_center tag

CI integration

jobs:
  tfsec:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: aquasecurity/tfsec-action@v1.0.3
        with:
          additional_args: --minimum-severity HIGH
          format: sarif
          output_file_path: tfsec.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: tfsec.sarif

tfsec (legacy) - migration reference

View source (opens in new window)

tfsec (legacy) - migration reference

Companion reference for trivy-config. tfsec is transitioning to Trivy per Aqua Security's own guidance - the host skill is the forward path, and tfsec's checks ship inside trivy config. Keep this reference for an existing Terraform-only tfsec stack while the migration lands.

Overview

Per tfsec-home (opens in new window), tfsec is transitioning to Trivy, Aqua Security's consolidated scanner. For new projects, evaluate Trivy first; tfsec remains stable for existing usage.

When to use

  • Existing tfsec project; team isn't ready to migrate to Trivy.
  • Terraform-only stack; want a focused Terraform-specific scanner.
  • A specific tfsec rule covers something Trivy doesn't yet.

How to use

  1. Install tfsec via Homebrew or the release binary (Step 1).
  2. Run tfsec . against the Terraform tree, starting at --minimum-severity HIGH to keep the signal high (Step 2).
  3. Emit SARIF for GitHub Code Scanning, or JUnit / Markdown for CI and PR comments (Step 3).
  4. Triage findings - fix real issues, annotate intentional exceptions with justified tfsec:ignore: comments (Step 4).
  5. Verify: re-run tfsec . --minimum-severity HIGH after remediation and assert it reports no HIGH findings before merging; if any remain, fix or justify-ignore them and re-run.
  6. Add custom YAML rules for team-specific policy, then wire the scan into CI (Steps 5-6).
  7. Confirm cloud coverage; fall back to OPA / Conftest for unsupported clouds (Step 7).
  8. Plan the Trivy migration for new work, and combine with Checkov / KICS for overlapping coverage (Steps 8-9).

Step 1 - Install

# macOS
brew install tfsec

# Linux
curl -L https://github.com/aquasecurity/tfsec/releases/latest/download/tfsec-linux-amd64 \
  -o /usr/local/bin/tfsec
chmod +x /usr/local/bin/tfsec

Step 2 - Run

# Scan current directory
tfsec .

# Scan specific path
tfsec ./terraform/

# Concise output
tfsec . --concise-output

# Specific severity threshold
tfsec . --minimum-severity HIGH

Step 3 - Output formats

Per tfsec-home (opens in new window): "JSON and SARIF output capabilities for integration with external tools and workflows."

# JSON
tfsec . -f json > tfsec.json

# SARIF (GitHub Code Scanning)
tfsec . -f sarif -O tfsec.sarif

# JUnit XML
tfsec . -f junit -O tfsec.xml

# Markdown (PR comments)
tfsec . -f markdown

Step 4 - Skip checks

# Skip specific checks
tfsec . -e aws-s3-enable-bucket-encryption,aws-s3-enable-versioning

# Skip everything matching a pattern
tfsec . -e aws-s3-*

Inline:

# main.tf
resource "aws_s3_bucket" "public_data" {
  # tfsec:ignore:aws-s3-enable-bucket-encryption Public dataset, not encrypted by design
  # tfsec:ignore:aws-s3-enable-bucket-logging Public CDN, no audit logging needed
  bucket = "my-public-data"
  acl    = "public-read"
}

Step 5 - Custom rules

Author custom YAML rules for team-specific policy the built-in set misses: tfsec-custom-rules-and-ci.md (opens in new window).

Step 6 - CI integration

Run the official tfsec action with SARIF upload to GitHub Code Scanning: tfsec-custom-rules-and-ci.md (opens in new window).

Step 7 - Supported clouds

Per tfsec-home (opens in new window), tfsec covers AWS (S3, EC2, RDS, IAM, Lambda, API Gateway, and 30+ services), Azure (App Service, Storage, Database, Container, Key Vault), Google Cloud (Compute, GKE, SQL, Storage, IAM, BigQuery), plus Kubernetes, OpenStack, Oracle, DigitalOcean, and CloudStack.

For unsupported clouds, fall back to OPA / Conftest with custom Rego per policy-as-code-runner.

Step 8 - Migration to Trivy

Per tfsec-home (opens in new window) guidance:

# Install Trivy
brew install trivy   # or apt-get / etc.

# Trivy includes tfsec's checks under `trivy config`
trivy config ./terraform/

The migration is mostly mechanical - Trivy ingests the same .tf files; rule names may differ.

Step 9 - Combine with Checkov + KICS

Multiple scanners catch overlapping but non-identical issues. tfsec is faster and Terraform-specific; Checkov (checkov-policy) is broader; the KICS tool adds different rule classes.

tfsec . -f json > tfsec.json
checkov -d . -o json > checkov.json
kics scan -p . --report-formats json
# unify results across the three scanners

Worked example

A team runs tfsec on an AWS Terraform module while planning a Trivy migration.

  1. brew install tfsec, then tfsec ./terraform/ --minimum-severity HIGH to focus on the worst findings first.
  2. tfsec flags an unencrypted S3 bucket (aws-s3-enable-bucket-encryption) as HIGH.
  3. The bucket is a public dataset by design, so the author adds # tfsec:ignore:aws-s3-enable-bucket-encryption Public dataset, not encrypted by design inline.
  4. CI runs aquasecurity/tfsec-action@v1.0.3 with format: sarif; the SARIF upload posts remaining findings to the Security tab.
  5. Ahead of the switch they dry-run trivy config ./terraform/ and confirm the same .tf files scan under Trivy.

Result: HIGH-severity misconfigurations gate the build, intentional exceptions are documented inline, and the Trivy forward-path is validated before switching.

Anti-patterns

Anti-patternWhy it failsFix
Starting new tfsec adoption in 2026+ without Trivy evaluationInvesting in deprecating path.Evaluate Trivy first (Step 8).
tfsec:ignore without justification commentSkips invisible to reviewers; security debt.Always include reason (Step 4 example).
--minimum-severity LOW everywhereNoise floods CI; team disables.Start HIGH; ratchet down.
Custom rules without testsBugs in custom rules let bad config through.Cross-reference with OPA-tested policies (Step 5 + Conftest).
Single-scanner approachTool-specific gaps.Multiple scanners (Step 9).

Limitations

  • Terraform-only. Doesn't scan Kubernetes / Dockerfile / CloudFormation directly (Trivy expands).
  • Maintenance pace slowing. Per tfsec-home (opens in new window), tfsec gets bug fixes but not new features.
  • Some new cloud services lag in coverage. Newer AWS / Azure resources may not have rules yet.
  • No baseline support out of the box. Adopt against legacy via skip annotations or wrapper scripts.

References

  • tfs (opens in new window) - tfsec overview, transition-to-Trivy positioning, developer-friendly output, AWS / Azure / GCP / Kubernetes / OpenStack / Oracle / DigitalOcean / CloudStack support, JSON / SARIF output.
  • Custom YAML rules + CI workflow: tfsec-custom-rules-and-ci.md (opens in new window).
  • checkov-policy - sister scanner.
  • policy-as-code-runner - custom OPA / Rego policies (for unsupported clouds or custom rules).

trivy-config - deep reference

View source (opens in new window)

trivy-config - deep reference

Long inline blocks moved out of the SKILL.md spine to keep the core scan flow lean. Version pins referenced below live in the Pinned versions section of SKILL.md.

Custom Rego policy - full example

Per trivy.dev custom checks docs (opens in new window), 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,
    )
}

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.

GitHub Actions workflow - full YAML

SARIF output integrates directly with GitHub Code Scanning. The if: always() on the upload step ensures findings appear in the Security tab even when the scan step exits non-zero:

# .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

Related skills