Every pipeline accumulates tools that were the right answer once. Nothing tells you when that stops being true — a scanner that has quietly stopped receiving new rules keeps exiting zero and keeps looking like a control.
This lesson is about a real instance of that, and about the check you should run before trusting any migration path a vendor hands you.
Verified against the Range Terraform — tfsec 1.28.14, Trivy 0.72.0, Checkov 3.2.x. Project status read from the GitHub API the same day.
Step 1 — The tool tells you itself
brew install tfsec
git clone https://github.com/jaybilgaye/aiopsone-range
cd aiopsone-range
tfsec terraform/Before any finding:
======================================================
tfsec is joining the Trivy family
tfsec will continue to remain available
for the time being, although our engineering
attention will be directed at Trivy going forward.
You can read more here:
https://github.com/aquasecurity/tfsec/discussions/1994
======================================================Aqua Security acquired tfsec and moved development to Trivy. tfsec still runs, still finds things, and prints this on every invocation.
In CI, nobody sees it. It goes to a log nobody reads on a job that passes. A tool can announce its own retirement on every run for two years and still be in your pipeline, because the only thing anyone checks is the exit code.
Step 2 — Check the status yourself, for every scanner you run
Do not take a banner's word for it, and do not take mine. The GitHub API answers this in one call, and it is worth running against your whole toolchain once a quarter:
for r in aquasecurity/tfsec tenable/terrascan bridgecrewio/checkov aquasecurity/trivy; do
curl -s "https://api.github.com/repos/$r" | python3 -c "
import json,sys; d=json.load(sys.stdin)
print(f\"{d['full_name']:28} archived={str(d['archived']):5} last_push={d['pushed_at'][:10]}\")"
doneaquasecurity/tfsec archived=False last_push=2026-03-25
tenable/terrascan archived=True last_push=2025-11-20
bridgecrewio/checkov archived=False last_push=2026-08-13
aquasecurity/trivy archived=False last_push=2026-08-13Read those four lines carefully, because they say three different things.
Terrascan is genuinely over. archived=True — the repository is read-only. Tenable stopped. If Terrascan is in a pipeline, it is not a control; it is a job that passes. Remove it.
tfsec is not archived, and that is the more dangerous state. It looks alive. It has 7,000 stars and a repository that accepts issues. But its last commit is March, while Checkov and Trivy both pushed yesterday. New AWS services and new CVE classes arrive continuously, and a rules engine that has not been updated in five months is silently narrowing its coverage every week.
An archived project is safer than a frozen one. Archiving is a signal your tooling and your team can act on. "Still technically maintained" is what keeps a stale scanner in a pipeline for years.
The number to watch is last_push, not stars. Popularity is a lagging indicator by definition.
Step 3 — Run the old tool and the new one on the same Terraform
The migration path is official: Aqua says use Trivy. So run both.
tfsec terraform/
trivy config terraform/tfsec 14 potential problems (3 critical, 8 high, 2 medium, 1 low) — 5 passed
trivy config 11 failures (1 critical, 7 high, 1 medium, 2 low)Fourteen versus eleven, on identical files, from a tool and its own designated successor.
Match them by rule ID rather than by count, because that is the only comparison that means anything:
| tfsec ID | Trivy ID | Count |
|---|---|---|
aws-ec2-no-public-ingress-sgr |
AWS-0107 |
2 |
aws-ec2-no-public-egress-sgr |
AWS-0104 |
1 |
aws-ec2-add-description-to-security-group-rule |
AWS-0124 |
1 |
aws-s3-block-public-acls |
AWS-0086 |
1 |
aws-s3-block-public-policy |
AWS-0087 |
1 |
aws-s3-ignore-public-acls |
AWS-0091 |
1 |
aws-s3-no-public-buckets |
AWS-0093 |
1 |
aws-s3-encryption-customer-key |
AWS-0132 |
1 |
aws-s3-enable-bucket-logging |
AWS-0089 |
1 |
aws-s3-enable-versioning |
AWS-0090 |
1 |
aws-iam-no-policy-wildcards |
— none — | 2 |
aws-s3-enable-bucket-encryption |
— none — | 1 |
Eleven map one-to-one. The rules genuinely moved across — same checks, renamed to Trivy's AVD scheme.
Three did not.
Step 4 — Confirm the gap is real before believing it
A missing finding usually means a differently-named rule, a different severity, or a check that passed. Rule those out — --include-non-failures prints everything Trivy evaluated, not just what failed:
trivy config terraform/ -f json -q --include-non-failures \
| python3 -c "
import json,sys
ids={m['ID'] for r in json.load(sys.stdin).get('Results',[]) for m in (r.get('Misconfigurations') or [])}
for c in ('AWS-0057','AWS-0088'): print(c, 'evaluated?', c in ids)"AWS-0057 evaluated? False
AWS-0088 evaluated? FalseTrivy assessed dozens of AWS, Azure and GCP checks against this Terraform — including neighbouring IAM rules like AWS-0345 (unrestricted S3 IAM policies) and AWS-0143 (policies attached directly to users), both of which passed.
But AWS-0057 — a general "IAM policy uses a wildcard action on a wildcard resource" check — is not in the ruleset. It did not pass. It did not get suppressed. It was never evaluated, because it does not exist.
Here is what Trivy is not looking at:
resource "aws_iam_role_policy" "admin_everything" {
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "*" # every action
Resource = "*" # on every resource
}]
})
}Action = "*" on Resource = "*" is full administrative access. tfsec rates it HIGH, twice. Checkov flags it. Trivy says nothing — not "low severity", not "ignored". Nothing.
The successor is not a superset. A migration recommended by the tool's own maintainer, followed exactly, deletes a finding for full-admin IAM from your pipeline — and every subsequent build passes, which is what makes it invisible.
They also disagree where they agree: tfsec rates unrestricted ingress CRITICAL; Trivy rates the same finding (AWS-0107) HIGH. If your gate is --severity CRITICAL, that check silently stops blocking too.
Step 5 — Migrate, but keep the coverage
Three findings and eleven, from a third tool, put the whole picture together — the Checkov and Trivy config lessons measured the same files:
| Findings | Flags wildcard IAM | Status | |
|---|---|---|---|
| Checkov 3.2 | 26 failed / 13 passed | yes | active (pushed yesterday) |
| tfsec 1.28.14 | 14 | yes | frozen since March |
| trivy config 0.72.0 | 11 | no | active (pushed yesterday) |
| Terrascan | — | — | archived |
The migration that actually preserves coverage is not tfsec → Trivy. It is tfsec → Checkov, or Trivy plus a custom policy for what Trivy does not check.
# .github/workflows/iac.yml
name: iac
on: [pull_request]
permissions:
contents: read
jobs:
iac:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# gate: broadest ruleset, and it has the IAM checks
- uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
soft_fail: false
# second opinion, non-blocking
- name: Trivy config (audit)
run: trivy config terraform/ --severity HIGH,CRITICAL --exit-code 0If you must gate on Trivy, write the missing check as a Rego policy and load it with --config-policy. That is real work — which is the point. Migrating tools is not a version bump, and "the vendor said to" is not a verification.
Step 6 — Make this routine
The failure here was never tfsec. It was that nothing in the pipeline was watching whether tfsec was still alive.
- Run the
last_pushcheck quarterly across every scanner you gate on. It is one API call each. - Diff rule IDs, not counts, whenever you change tools. Totals hide substitutions in both directions.
- Record the finding count and tool version with each release. A count that drops without a fix landing means the ruleset changed, not the code.
- Treat a frozen project as a finding.
archived=Falsewith a five-month-old commit is a risk to raise, not a reassurance.
What you learned
- tfsec prints its own retirement notice on every run, into a log nobody reads on a job that passes.
- Terrascan is archived (
archived=True). tfsec is not — it is frozen since March while Checkov and Trivy pushed yesterday. The frozen one is more dangerous, because it looks alive. - 14 findings from tfsec, 11 from Trivy on identical Terraform. Eleven map one-to-one by rule ID.
AWS-0057is not in Trivy's ruleset at all — verified with--include-non-failures. A full-adminAction="*"onResource="*"policy goes unreported.- The successor is not a superset. Following the official migration exactly drops a wildcard-IAM finding, and every build still passes.
- Severities diverge too: ingress is CRITICAL in tfsec, HIGH in Trivy. A
--severity CRITICALgate stops blocking it. - Check
last_pushon every scanner quarterly, and diff rule IDs, not totals, on any tool change.