IaC Scanning Tools: Checkov, TFLint, Grype, and pre-commit
Tool Overview #
| Tool | Role | What it catches |
|---|---|---|
| Checkov | IaC security scanner | Misconfigurations in Terraform, CloudFormation, K8s |
| TFLint | Terraform linter | Invalid resource types, deprecated syntax, bad practices |
| Grype | Vulnerability scanner | CVEs in container images, filesystems, SBOMs |
| pre-commit | Git hook framework | Runs all of the above automatically on every commit |
git commit → pre-commit → TFLint + Checkov (local)
↓
CI pipeline → Checkov + TFLint + Grype (full gate)
1. Checkov #
Static analysis tool for IaC security. 1,000+ built-in policies covering AWS, Azure, GCP, and Kubernetes. Maintained by Palo Alto Networks (acquired Bridgecrew) under Apache 2.0.
Install & Run #
pip install checkov
checkov -d . # scan current directory
checkov -d ./terraform --framework terraform
checkov -d . --output sarif # SARIF for GitHub
checkov -d . --skip-check CKV_AWS_18 # skip a check
checkov -d . --soft-fail # always exit 0
checkov --config-file checkov.yaml # use config file
checkov.yaml #
directory:
- terraform/
framework:
- terraform
skip-check:
- CKV_AWS_144
output:
- cli
- sarif
compact: true
GitHub Actions #
- uses: bridgecrewio/checkov-action@master
with:
directory: ./terraform
framework: terraform
output_format: sarif
Security note: The official docs use
@master, but best practice is to pin to a specific commit SHA (e.g.,bridgecrewio/checkov-action@<sha>) to guard against supply chain attacks.
2. TFLint #
Terraform linter — not a security scanner. Catches provider-specific errors that terraform validate misses entirely (invalid instance types, nonexistent AMIs, deprecated attributes).
terraform validatechecks HCL syntax. TFLint checks whether your values are valid in AWS/Azure/GCP.
Install & Run #
brew install tflint # macOS
curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
tflint --init # download plugins
tflint # lint current directory
tflint --chdir=./terraform/modules/vpc
tflint --recursive
tflint --format json
.tflint.hcl #
plugin "aws" {
enabled = true
version = "0.47.0" # verify latest at github.com/terraform-linters/tflint-ruleset-aws/releases
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
plugin "terraform" {
enabled = true
preset = "recommended"
}
What It Catches #
instance_type = "t1.2xlarge" # ERROR: invalid type — t1 family only has t1.micro
acl = "private" # WARNING: acl is deprecated since AWS provider v4;
# use aws_s3_bucket_acl resource instead
# missing required_version # WARNING
AWS context (as of 2026-06-08): Amazon S3 disabled ACLs by default for new buckets in April 2023. The
aclargument onaws_s3_bucketwas deprecated in the AWS Terraform provider v4.
Inline Ignore #
instance_type = "t1.2xlarge" # tflint-ignore: aws_instance_invalid_type
GitHub Actions #
- uses: terraform-linters/setup-tflint@v6 # v6 is latest as of 2026-06-08
- run: tflint --init
- run: tflint --recursive --format compact
3. Grype #
Vulnerability scanner for container images, filesystems, and SBOMs. Maintained by Anchore under Apache 2.0. Pairs with Syft for SBOM-first scanning.
Install & Run #
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
grype alpine:latest # scan image from registry
grype dir:./my-project # scan local directory
grype sbom:./sbom.json # scan a Syft SBOM
grype alpine:latest -o sarif # SARIF output
grype alpine:latest --fail-on high # fail build on HIGH+ CVEs
SBOM-First Workflow (Syft + Grype) #
# Generate SBOM once
syft nginx:latest -o syft-json > sbom.json
# Scan now — and rescan later without re-pulling the image
grype sbom:./sbom.json
.grype.yaml #
fail-on-severity: high
ignore:
- vulnerability: CVE-2023-1234
reason: "not exploitable in this context"
output:
- table
- sarif
GitHub Actions #
- uses: anchore/scan-action@v7 # v7 is latest as of 2026-06-08
with:
image: my-app:${{ github.sha }}
fail-build: true
severity-cutoff: high
output-format: sarif
4. pre-commit #
Git hook framework that runs checks automatically on every git commit. One config file, committed to git, runs consistently across the entire team.
Install & Setup #
pip install pre-commit # or: brew install pre-commit
pre-commit install # one-time per repo — hooks now run on every commit
.pre-commit-config.yaml #
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0 # latest as of 2026-06-08
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-merge-conflict
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.106.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
- id: terraform_checkov
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1 # latest as of 2026-06-08
hooks:
- id: gitleaks
Run
pre-commit autoupdateperiodically to refresh allrev:pins to the latest releases.
Common Commands #
pre-commit run --all-files # run all hooks against all files
pre-commit run terraform_checkov --all-files # run one hook only
pre-commit autoupdate # update all hook versions
git commit --no-verify -m "skip hooks" # bypass all (use sparingly)
SKIP=terraform_checkov git commit -m "msg" # skip one hook only
Full GitHub Actions Pipeline #
name: IaC Security Pipeline
on: [push, pull_request]
jobs:
checkov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bridgecrewio/checkov-action@master
with:
directory: ./terraform
output_format: sarif
tflint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: terraform-linters/setup-tflint@v6
- run: tflint --init && tflint --recursive
grype:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t app:${{ github.sha }} .
- uses: anchore/scan-action@v7
with:
image: app:${{ github.sha }}
fail-build: true
severity-cutoff: high
Key Takeaways #
- TFLint ≠ Checkov — run both; they catch completely different things
- Grype runs in CI, not pre-commit — image scanning is too slow for local hooks
- Pin
rev:versions in.pre-commit-config.yamlfor consistent team behavior; runpre-commit autoupdateregularly - Use SARIF output to surface findings as GitHub pull request annotations
- Syft + Grype — generate the SBOM once, rescan cheaply as new CVEs emerge
- Pin GitHub Actions to a SHA instead of
@masterfor supply chain safety