What they are and why there are two
tfsec appeared in 2019 as a Go application from Aqua Security, a security scanner for Terraform. It is fast (tens of milliseconds on a medium-sized project), ships as a single binary, and has no Python dependencies.
In 2023 Aqua merged tfsec into Trivy, the same vendor's multi-tool (container image scanner, SBOM, IaC). IaC scanning is now `trivy config
<dir>`. The rules and engine are the same as in tfsec. tfsec itself is deprecated as a standalone project, but its commands and rule identifiers are still common in legacy pipelines.The short answer: use Trivy. If you are on a legacy pipeline with tfsec, migration is straightforward. See the migration section in tf-trivy-tfsec.
Running Trivy
trivy config .
Scans all supported formats in the current directory: Terraform, CloudFormation, Kubernetes, Dockerfile.
Output:
Tests: 28 (SUCCESSES: 25, FAILURES: 3, EXCEPTIONS: 0)
Failures: 3 (UNKNOWN: 0, LOW: 0, MEDIUM: 2, HIGH: 1, CRITICAL: 0)
main.tf (terraform)
AVD-AWS-0086 (HIGH): No public access block so not blocking public acls
...
AVD-AWS-0089 (MEDIUM): S3 Bucket does not have logging enabled.
...
Terraform only:
trivy config --skip-dirs node_modules --type terraform .
Specific severity levels:
trivy config --severity HIGH,CRITICAL .
Exit code: 0 when there are no findings by default. --exit-code 1 sets
exit 1 on any finding (for CI).
tfsec to trivy migration
| tfsec | trivy |
|---|---|
tfsec . | trivy config . |
tfsec --exclude aws-s3-encryption-customer-key | trivy config --ignore-policy aws-s3-encryption-customer-key |
tfsec --tfvars-file dev.tfvars | trivy config --tf-vars dev.tfvars |
.tfsec/config.yaml | trivy.yaml |
| finding-ID: AWS001 | finding-ID: AVD-AWS-0086 (new) |
The rules are the same, but the identifiers were renamed. If you had
tfsec:ignore:AWS001 in HCL, replace it with trivy:ignore:AVD-AWS-0086.
Suppression
Inline:
#trivy:ignore:AVD-AWS-0089
resource "aws_s3_bucket" "logs" {bucket = "no-logging-on-purpose"
}
Multiple rules:
#trivy:ignore:AVD-AWS-0089
#trivy:ignore:AVD-AWS-0086
resource "aws_s3_bucket" "public_assets" {# ...
}
Config file trivy.yaml:
ignored-policies:
- AVD-AWS-0086
Trivy/tfsec does not require a reason in the comment. This differs from Checkov. Teams often add a convention such as `#trivy:ignore:AVD-AWS-0086
see ADR-014` for traceability.
Trivy/tfsec vs Checkov
| Aspect | Trivy/tfsec | Checkov |
|---|---|---|
| Language | Go | Python |
| Installation | Single binary (curl + chmod) | pip install (Python env) |
| Speed | Faster (milliseconds) | Noticeably slower (seconds on a medium project) |
| Rules | ~150 for Terraform | ~600 for Terraform |
| Cloud provider coverage | AWS, GCP, Azure, Oracle, DigitalOcean | Same plus Kubernetes policies |
| Custom rules | YAML or Rego | Python class or YAML (since 3.0) |
| SARIF output | Yes | Yes |
| Maintained | Yes (via Trivy) | Yes (Prisma Cloud) |
In practice both tools catch similar classes of problems. Checkov has broader coverage; Trivy is faster. Many teams pick one to avoid duplicating suppressions. If you are choosing: Trivy for CI speed, Checkov for policy specifications.
CI integration
# .github/workflows/security.yml
jobs:
trivy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy config scan
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: config
scan-ref: .
severity: HIGH,CRITICAL
exit-code: 1
format: sarif
output: trivy-results.sarif
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
You get findings in the Security tab and a mandatory fail on HIGH/CRITICAL.
Custom rules via Rego
Trivy can read custom rules written in Rego (the same language as OPA, see tf-policy-as-code):
# policies/costcenter.rego
package custom.costcenter
__rego_metadata__ := {"id": "AVD-CUSTOM-001",
"title": "CostCenter tag required",
"severity": "MEDIUM",
}
__rego_input__ := { "selector": [{"type": "defsec", "subtypes": [{"service": "s3"}]}],}
deny[res] {bucket := input.aws.s3.buckets[_]
not bucket.tags.CostCenter
res := { "msg": sprintf("S3 bucket %s missing CostCenter tag", [bucket.name.value]),}
}
To load the policy:
trivy config --config-policy ./policies .
Pitfalls
-
AVD identifiers are unstable. They were originally AWS001, then renamed to AVD-AWS-0086. Old tfsec suppress comments will keep "working" as no-ops, but the rule now lives under a different name. Verify your suppressions after migration.
-
Trivy scans everything by default. In a directory that contains both a Dockerfile and .tf files it runs both scans. To limit to Terraform, use
--type terraform(or--scanners configfor all IaC formats). -
Severity reflects vendor opinion. Something rated "MEDIUM" by Trivy may be "CRITICAL" in your environment. Do not trust the vendor grading blindly. Set your own
--severityfilter or use a soft-fail on lower levels. -
trivy.yamlvs CLI flags: conflicts. When both are present, CLI wins. This trips teams up. Keep configuration in one place. -
Does not cover state. This is a source scanner, not a runtime tool. What is already deployed and running is outside its scope. For drift, see tf-drift-detection. For scanning existing resources, Trivy has an AWS account scan mode, but that is a separate feature.
-
Suppression without a reason becomes a maintenance problem. Trivy does not require one, unlike Checkov. Have the team adopt a convention:
#trivy:ignore:X # reason. Without it, nobody will remember why the ignore is there six months later.