An automatic bouncer that rejects a pull request the moment it adds a committed secret, an open security group, or an unencrypted bucket. Plus a self test that proves the bouncer actually works.
| Domains | AWS, Azure, Linux, security |
| Built on | pre-commit-terraform (Anton Babenko), checkov, trivy, gitleaks |
| Cost | $0 (runs in CI). Runtime ~2 hours |
| Status | Working. Verified with all three scanners: bad rejected by gitleaks, trivy and checkov, good accepted by all three (output in findings/) |
A PAM engineer's instinct is that a credential must never reach the repo in the first place. Anton Babenko's pre-commit-terraform ships hooks for Trivy and Checkov but no secrets scanner. That gap is the value add.
Chain a secrets scanner in front of the config scanners so a leaked credential fails first, and then prove the gate actually blocks what it claims to.
The gate runs gitleaks first (secrets are the highest severity failure and should stop everything else), then the config scanners.
Most "we have a security gate" setups never test the gate. This one ships two fixtures and a self test. tests/bad/main.tf has a committed AWS key, a wide open security group, and an unencrypted bucket, and the gate must reject it. tests/good/main.tf is clean, and the gate must pass it. scripts/gate-selftest.sh fails if bad passes or good fails.
Verified locally: bad fails on 5 checks, good passes with 0. Those numbers came from Checkov, and the self test asserts them on every push.
Two things the build surfaced, both in the history. The good fixture originally failed the gate on a missing description and no encryption. Rather than hide it by narrowing the checks, I fixed the fixture, because a clean example that is not clean teaches the wrong thing. And the repo's own CI would have failed on its own bad fixture. Fixed two ways: the real gate skips the tests folder, and a gitleaks config allowlists the fixture's example key (it is AWS's documented example, not a real credential).
The gate blocks on a curated set (tests/gate-checks.txt), not all 1000 Checkov policies, because demanding zero findings on everything blocks every PR forever on nice to haves. Choosing that line is the judgement a gate expresses, and it is stated out loud here.
| Chose | Over | Because |
|---|---|---|
| gitleaks first, config scanners second | running all scanners in parallel | A committed credential is the highest severity failure in the set. It should stop the pipeline before anyone spends time reading Checkov output, and ordering encodes that priority where a parallel run does not. |
| A curated block list (tests/gate-checks.txt) | failing on all ~1000 Checkov policies | Demanding zero findings on everything blocks every PR forever on nice-to-haves, and a gate people route around is worse than no gate. Where that line sits is the judgement a gate expresses, so it is written down rather than implied. |
| Fixed the good fixture | narrowed the checks until it passed | The clean example failed on a missing description and no encryption. Narrowing the rules would have made the gate pass by teaching the wrong thing. |
| Allowlist the fixture key in a scoped gitleaks config | deleting the bad fixture | The repo's own CI failed on its own test data. The fixture has to stay for the self test to mean anything, so the real gate skips tests/ and the example key is allowlisted — it is AWS's documented example, not a credential. |
| A self test that asserts both directions | trusting the badge | Most "we have a security gate" setups never test the gate. Asserting only that bad fails would pass a gate that rejects everything. |
The scanners are upstream. The gate design, the curated check set, the fixtures, and the self test are mine.
pre-commit install # local hooks, secrets first
bash scripts/gate-selftest.sh # prove the gate works
pre-commit run --all-filesNeeds pre-commit, checkov, and for local hooks Terraform, gitleaks, and trivy.
Lab code: MIT (LICENSE). Upstream tools keep their own licenses, credited above.