Secure the PipelineIaCStep 16 of 31

Migrate Off tfsec to trivy config

difficulty intermediatehands-on 20 min hands-on

52% complete

prereqs · checkov-terraform-misconfig

concepts · IaC scanning · scanner comparison · tool lifecycle risk · severity models

If your pipeline still calls tfsec, it is running a project that no longer exists independently — tfsec was folded into Trivy, and its checks now ship as trivy config. Terrascan, the other common alternative, was archived in November 2025.

This is worth more than a find-and-replace in a workflow file. A scanner can die quietly while still exiting zero. A pipeline calling an unmaintained binary against a rule database that stopped updating is worse than no gate at all — it produces a green check that means nothing, and nobody looks at a green check.

Verified against Trivy 0.72.0, scanning the same Range Terraform the Checkov lesson used. Every output is copied from a real terminal.

Step 1 — Install and scan

bash
brew install trivy
trivy --version
bash
git clone https://github.com/jaybilgaye/aiopsone-range
cd aiopsone-range

trivy config terraform/
text
Tests: 11 (SUCCESSES: 0, FAILURES: 11)
Failures: 11 (UNKNOWN: 0, LOW: 2, MEDIUM: 1, HIGH: 7, CRITICAL: 1)

No AWS account, no credentials, nothing applied — same as Checkov, same input.

Step 2 — The mapping from tfsec

Old check IDs became AWS-####:

text
AWS-0086 (HIGH):     Public access block does not block public ACLs
AWS-0087 (HIGH):     Public access block does not block public policies
AWS-0089 (LOW):      Bucket has logging disabled
AWS-0090 (MEDIUM):   Bucket does not have versioning enabled
AWS-0091 (HIGH):     Public access block does not ignore public ACLs
AWS-0093 (HIGH):     Public access block does not restrict public buckets
AWS-0104 (CRITICAL): Security group rule allows unrestricted egress to any IP address
AWS-0107 (HIGH):     Security group rule allows unrestricted ingress from any IP address
AWS-0107 (HIGH):     Security group rule allows unrestricted ingress from any IP address
AWS-0124 (LOW):      Security group rule does not have a description
AWS-0132 (HIGH):     Bucket does not encrypt data with a customer managed key

The migration itself is small:

diff
- tfsec ./terraform
+ trivy config ./terraform

Ignores move from tfsec:ignore:aws-s3-enable-bucket-encryption comments to trivy:ignore:AWS-0132. If you have many, migrate them deliberately rather than mechanically — an ignore written three years ago for a reason nobody remembers is a finding in itself.

Step 3 — Now compare it with Checkov

Same directory. Same Terraform. Two tools:

Checkov trivy config
Findings 26 11
Severity model pass/fail per check LOW → CRITICAL
Unrestricted egress CKV_AWS_382 (no severity) AWS-0104 CRITICAL
Customer-managed KMS key not flagged AWS-0132 HIGH
IAM data exfiltration CKV_AWS_288 not flagged
IAM "*" admin CKV_AWS_62, CKV_AWS_355 not flagged

Read that table before you pick a tool.

Trivy is the only one that rates unrestricted egress CRITICAL. Checkov reports it without a severity, so in a soft_fail_on: LOW,MEDIUM gate it lands wherever your config puts it. Trivy makes it the single most severe thing in the repository — defensible, since egress to anywhere is how data leaves.

Checkov is the only one that flagged the IAM role at all. The Range contains an Action: "*", Resource: "*" policy attached to an EC2 role — a wildcard admin grant. Trivy's Terraform rules did not report it. If you ran only trivy config, the worst IAM configuration in the repository would pass silently.

Neither reports the missing CloudTrail, for the same structural reason: there is no resource to attach a rule to.

The uncomfortable conclusion. Neither tool is a superset. Trivy is stronger on network egress and encryption specifics; Checkov is stronger on IAM policy semantics. "We scan our Terraform" is not a control statement until you can say which checks actually run.

Step 4 — Run both, and say why

They are both fast and both free, so the sensible pipeline runs both:

yaml
# .github/workflows/iac.yml
name: iac
on: [pull_request]

permissions:
  contents: read

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Checkov (IAM policy semantics)
        uses: bridgecrewio/checkov-action@master
        with:
          directory: terraform/
          soft_fail_on: LOW,MEDIUM

      - name: Trivy config (network and encryption)
        run: trivy config terraform/ --exit-code 1 --severity HIGH,CRITICAL

The step names carry the reasoning. Six months from now, when someone asks whether one can be dropped to save forty seconds, the answer is in the workflow file rather than in somebody's memory.

Step 5 — Do not let a dead tool stay green

Whatever you run, pin the version and watch it:

yaml
- run: |
    trivy --version
    trivy config terraform/ --exit-code 1 --severity HIGH,CRITICAL

Printing the version into the build log costs nothing and means a stale scanner is visible in every run rather than discovered during an audit. tfsec's last independent release still exits zero on a clean scan today — it simply stopped learning about new misconfigurations. That is what a dead gate looks like from the inside: green.

What you learned

  • tfsec is now trivy config; Terrascan is archived. The command change is one line, the ignore-comment migration deserves actual review.
  • 26 findings versus 11 on identical input. Different rule sets, not different quality.
  • Trivy rates unrestricted egress CRITICAL and catches KMS specifics; Checkov is the only one that flagged a wildcard admin IAM policy. Running one leaves a real gap.
  • Neither can report a missing CloudTrail. IaC scanning is blind to absence.
  • Print the scanner version in CI. A dead scanner exits zero, and a green check nobody questions is the worst outcome available.