Static Analysis
Static analysis tools read your Terraform code and flag problems validate structurally cannot see: deprecated arguments, invalid instance types, and — the category that matters most — security misconfigurations like a public S3 bucket or a security group open to 0.0.0.0/0 on port 22. They do this without running a plan or touching AWS, by parsing the configuration and matching it against rule sets.
Two distinct kinds of tool live under this name, and conflating them is how teams end up half-covered. tflint is a linter for correctness and provider best practices. tfsec, checkov, and trivy are security scanners for misconfiguration and compliance. Running them in CI moves whole classes of bug — the open port, the unencrypted volume — to the left of the pipeline, caught in seconds on a pull request instead of in production after an incident.
tflint
tflint is provider-aware in a way validate is not. With its AWS ruleset enabled, it knows that t2.mega is not a real instance type, that an argument was deprecated in a recent provider version, and that a declared variable is never used. It catches the correctness mistakes that are valid HCL — they parse and type-check fine — but are wrong against the actual AWS provider. It runs against the code directly, no plan required.
# install the AWS ruleset once, then lint tflint --init tflint --recursive
Security Scanners
Security scanners exist to catch the misconfigurations that cause breaches: an S3 bucket without public-access blocks, an RDS instance without encryption at rest, a security group allowing inbound SSH from anywhere, an IAM policy with Action: "*". tfsec, checkov, and trivy each ship hundreds of such rules and parse your config to find them. They are the reviewers who never get tired and never miss the one open port in a 400-line diff.
# scan the current directory against the bundled ruleset trivy config . # example finding # HIGH: aws_security_group.web ingress allows 0.0.0.0/0 on port 22
Policy Coverage and Suppressions
Each scanner ships a baseline of generic best-practice rules, and you tune that baseline to your standards — disabling checks that do not apply to you, raising or lowering severities. Some findings will be deliberate exceptions: a bucket that genuinely must be public for static hosting, a port that must be open for a known reason. For those you write an inline suppression with a justification, so the build goes green for the right reason and the next reader sees why.
Suppress sparingly and with a comment, because a blanket suppression is indistinguishable from a real exposure to everyone who comes after you. Review the list of suppressions periodically; an exception that made sense a year ago may now be a hole nobody remembers opening.
In the Pipeline
Run the linter and a security scanner on every pull request, after fmt and validate but before or alongside plan. The operational decision is which findings block the merge and which only warn. Treat high-severity security findings — public access, weak or absent encryption, wide-open ingress — as blocking; let lower-severity style and informational findings warn without stopping the build. A scanner that blocks on everything trains the team to bypass it; one that blocks only on what matters keeps its authority.
tflint — focuses on correctness and provider best practices: deprecated arguments, invalid instance types, unused declarations, naming conventions. It tells you the code is wrong against the AWS provider, not that it is insecure. Reach for it to catch mistakes that are valid HCL but bad Terraform.
Security scanners (tfsec / checkov / trivy) — focus on misconfiguration and compliance: public buckets, open security groups, unencrypted storage, over-broad IAM. They tell you the code is dangerous, not that it is malformed. A serious pipeline runs both, because neither category catches the other's findings.
- Relying on human review to catch a public S3 bucket or a
0.0.0.0/0SSH rule that a scanner flags instantly and never misses — reviewers get tired, scanners do not. - Suppressing scanner findings wholesale to get a green build, turning a security gate into decoration and hiding real exposures behind it.
- Running only one tool and assuming it covers both correctness and security — tflint will not flag your public bucket, and tfsec will not flag your deprecated argument.
- Ignoring tflint's deprecation warnings until a provider major-version upgrade turns them into hard breakage on a day you cannot afford it.
- Blocking the build on every finding regardless of severity, training the team to bypass the scanner instead of fixing what actually matters.
- Run both a linter (
tflint) and a security scanner (tfsec,checkov, ortrivy) in CI on every pull request — they cover different bugs. - Treat high-severity security findings as blocking and let lower-severity ones warn, so the gate keeps its authority.
- Suppress a finding only with an inline justification, and review the suppression list periodically so stale exceptions do not become holes.
- Act on tflint deprecation warnings before the next provider upgrade, while they are warnings rather than breakage.
- Enable the provider-specific ruleset (the AWS plugin for tflint) so the linter knows real instance types and current arguments.
Knowledge Check
Your pipeline runs only tflint. Which problem will it most likely miss?
- A security group allowing inbound SSH from
0.0.0.0/0— that is a security-scanner finding, not a linter one - A reference to an EC2 instance type that does not actually exist in AWS
- A deprecated provider argument that will silently break on the next major provider version upgrade down the line
- A declared input variable that is never referenced anywhere in the config
What is the right way to handle a security-scanner finding you have deliberately accepted, like a bucket that must be public?
- Add an inline suppression with a written justification, and review the suppression list periodically
- Disable the scanner entirely for the whole repository so the CI build always stays green from now on
- Lower the severity of every rule across the board until nothing blocks the build
- Delete the finding from the scanner's raw output before it ever reaches CI
Why catch security misconfigurations with a scanner instead of in code review?
- A scanner flags the one open port in a large diff instantly and never tires, where a human reviewer eventually misses it
- A scanner can fix the misconfiguration automatically without any human deciding on it
- Code review simply cannot ever see security group rules at all because they are computed only at apply time by the provider
- Scanners fully replace the need for a separate plan step in the pipeline
Why run both a linter and a security scanner rather than picking one?
- They target different classes of problem — correctness versus security — and neither catches the other's findings
- Running two separate tools is faster than running one because they share a parse cache
- A security scanner requires the linter to have already run first in order to parse the HCL correctly
- One of the two tools checks the static code while the other one checks the live AWS account directly at apply time
You got correct