Terraform scanning: where infrastructure breaks
Infrastructure rarely breaks at the moment a configuration is applied. Usually it drifts apart more slowly: an engineer edits access rules in the console to unblock a release, adds a route "for five minutes," fixes prod manually — and all of this lives outside the code. After a few iterations, the state in the cloud stops matching the description in the repository.
This is how environment divergence begins. A test environment is assembled from the same module but differs from prod in settings that were made manually and never transferred back.
Checks pass, but failure comes after deployment: a port is open to the outside, a role is granted too broadly, storage is publicly accessible.
Rollback does not help in such a situation — the problem is not in a specific release, but in the fact that the description and reality never matched.
Configuration drift is expensive not because of build time, but because of time to detection: the later it surfaces, the more manual infrastructure edits have to be sorted out and returned to code.
Terraform scanning moves these checks to the moment when the configuration has not yet been applied. The tfsec and Checkov analyzers parse resource descriptions and find unsafe values at the review stage, not after an incident. Let's look at how this works and what it provides in practice.
What regular configuration checks provide: failure classes
Static analysis of infrastructure code shifts the control point left: the configuration is parsed before deployment, not after a change has gone to the production environment.
The check runs against resource descriptions — open ports, disk and storage encryption, access rights, public addresses — and does not require bringing up the environment itself. The report appears at the review stage, while the change has not yet affected anything.
The time to check changes drops from hours of manually reading configurations to minutes: rules are formulated once and applied to every change automatically. An engineer reviews not a hundred lines but a short list of findings with the resource and location indicated.
The check does not depend on the reviewer's attentiveness, so the same error does not return in subsequent releases.
The second part of the effect is a unified picture of environments. One set of rules is run for all environments, and the divergence between the test and production environments is visible as a specific report item, not as a surprise during the next apply. Configuration drift accumulated from manual edits stops being invisible.
The eliminated failure class is not a general "it became safer" but specifics: publicly accessible storage with backups, a database port open to the outside, administrator rights for a service account, disabled encryption.
Our engineers configure Checkov together with tfsec so that such findings block a change before it is applied rather than turning into a task for later.
Terraform scanning: run modes and their limitations
The run mode determines not the scanner's speed but which errors you catch before applying.
tfsec and Checkov parse the same Terraform declarations, but enabled at different points, they give a different signal: a mandatory failure on merge and a nightly report across the entire repository answer different questions.
| Mode | When suitable | Limitations |
|---|---|---|
| Build pipeline check | Every commit in the working branch: the error is visible at the moment it appears | Lengthens the build; in a large repository, waiting time grows noticeably |
| Pre-merge check | Blocking gate for rules that cannot be bypassed | Without configured exceptions, it slows down urgent fixes |
| Scheduled scan | Traverses all code on a schedule: finds new rules and divergences | The report arrives after the fact — some changes have already been applied in the cloud |
| Changed modules check | Incrementally by commit differences: fast feedback | Skips connections between modules and violations inherited from shared code |
In practice, the modes do not compete but are combined into a bundle: fast checks are kept on changed modules in the build pipeline, the full set of rules is enabled before merge, and the entire repository is run on a schedule.
Shared rule sets and exception lists are reused between run points — otherwise the team gets three different answers for the same code.
A separate limitation is stale findings: without a fixed set of exceptions, incremental mode and a full run show a different number of findings, and trust in the report drops.
Implementation stages: from inventory to handover of policies
The order of work is structured so that Terraform scanning provides a predictable result from day one rather than turning into a stream of warnings that everyone has become used to ignoring.
First we capture the actual picture across repositories, then formulate rules for real risks, and only after that enable blocking in the build pipeline.
- Repository audit. Inventory of Terraform modules, state files, providers, and owners: where infrastructure is described, which parts are shared, which have long been unmaintained. The output is a list of the perimeter that will be included in checks.
- Check rule scheme. We map tfsec and Checkov findings to actual risks: public access to storage, open security groups, disabled encryption, excessive role permissions. Anything that carries no risk does not make it into the set.
- Severity levels. We divide rules into blocking and informational. Merge blocking is only for critical items; the rest goes into a report, otherwise the team loses speed on minor issues.
- Exception configuration. Each disablement is documented in a targeted way, indicating the resource, reason, and review deadline. Mass exceptions for entire checks are prohibited — they devalue the set.
- Pipeline integration. The check runs at the merge request stage, and locally at pre-commit, so the author sees the finding before sending the branch for review.
- Threshold tuning on real changes. We run the set through the repository history and calculate the share of triggers that require manual review. We adjust thresholds to the point where findings really require attention.
- Handover of policies. The team receives a document: what blocks, how to add an exception, who owns the rule, how to update the set when infrastructure changes — plus a walkthrough with live examples.
What remains with the customer: configurations, policies, regulations
The result of the work is not a findings report but artifacts that live in the repository and work without us. Check rules are also code: the configuration of the tfsec and Checkov scanners, mandatory conditions, and the threshold at which the build stops.
After handover, an engineer runs the check locally and in the pipeline and gets the same verdict as we do.
An unconfigured scanner produces hundreds of triggers, the team stops looking at them, and protection becomes a formality — that is why we record every exception with a reason and review deadline.
- Admission policies — which resources are allowed, which attributes are mandatory (encryption, private networks, owner labels), and at what severity a change is blocked.
- Module scheme — a map of reusable modules: mandatory parameters, where shared rules are located, and how to connect a new service without copying the configuration.
- Access matrix — roles and rights by environment: what an engineer changes, what only the pipeline changes, where secrets are stored, and who reads them.
- Exception description — every disabled rule with justification, responsible person, and condition for removal.
- Engineer instructions — how to run the check before commit, how to read the verdict, what to do on a trigger, and how to document a deviation.
- Finding triage procedure — the route from trigger to fix: who reviews it, within what timeframe, what blocks release, and what goes into the queue.
Artifacts are updated together with the code in the normal change process — a new engineer sees working rules, not verbal agreements.
Case study: configuration defects caught before deployment
The project's infrastructure is several dozen environments in Terraform; changes were made by three teams and rolled into environments after a visual review of the diff.
Settings were copied from a template, so one unsuccessful fragment spread across dozens of configurations and surfaced only in a running environment.
What measurement showed before implementing checks
We went through the change history: a configuration defect reached an environment in roughly every fourth case.
More than half of the findings were broad network access rules and open ports; the rest were unencrypted disks, publicly readable storage, and secret values directly in variables. The share of rollbacks during deployment stayed around 18%, and reviewing each one took hours.
What we changed
We connected the tfsec and Checkov scanners to the merge request check: critical findings block it, while the rest arrive as a warning with the file and line indicated.
We created a list of deliberate exceptions with justification and our own rules for the project's requirements so that checks do not create noise. The scan report is attached to the review — the reviewer sees not only the diff but also the risk.
What the result was
Configuration defects reaching an environment went from 25% to 2-3%, and rollbacks from 18% to less than 4%. Reviewing a finding takes minutes instead of hours: the report shows the rule, file, and line. All configuration changes are covered by checks, including one-line edits.
What to do if the infrastructure is already deployed manually?
There is no universal path: you need to choose based on the cost of environment downtime and the volume of manual changes that no one documented. Working infrastructure is a fact that the code must converge with, not the other way around.
| Path | When suitable | Limitations |
|---|---|---|
| Describe in code | Critical environments that cannot be stopped | Import into state is manual work; some settings are described anew |
| Leave as is, targeted control | Environments slated for early decommissioning, sandboxes | Divergence accumulates, incidents are poorly reproducible |
| Rebuild from scratch | Test environments with recovery from copies | A downtime window and data migration are required |
Describing in code begins with import: real objects are brought into state, and the next plan must be empty. If the plan changes something, the description did not match reality, and this is visible at review, not at the moment of failure.
Then Terraform scanning is added to the environment: tfsec and Checkov check descriptions for open ports, public storage, and missing encryption before apply.
Targeted control is a compromise: scanning works in check mode, console write access is closed, and divergences are searched for regularly. The limitation is that the state remains manual — reproducing the environment from scratch is still impossible.
Rebuilding provides full manageability but requires a downtime window and data migration. It is trialed on a non-critical environment, the descriptions are debugged, and only then is it moved to production.
False positives, thresholds, and rule inheritance
Scanners on a live repository produce hundreds of findings, and some of them are noise from decisions the team made deliberately. If this is not managed, the report is read once and then stops being opened: the real signal drowns in volume.
That is why we immediately configure the trigger threshold by severity level — the build stops only on the class of findings that truly blocks release; the rest goes into the report as reference information.
Rule inheritance is built from a common policy set: the base profile lives in a separate module and is connected to all repositories, while a project can override or weaken a specific rule — with explicit recording in code.
We document exceptions in rules not by disabling the check but by a targeted skip indicating the reason and owner: Checkov and tfsec allow such a skip to be set next to the resource, and at review it is clear who added it and why.
Finding triage follows one scheme: confirmed vulnerabilities go into the prioritized fix queue, false positives go into the exception list with justification, and disputed ones go to the resource owner. The report stops being a dump and turns into a task list that actually gets closed.
The scanner produces hundreds of findings on existing infrastructure — where to start?
We start with a baseline: record the current state, enable the threshold only for new changes, and gradually work through the backlog. As noise decreases, the strict profile is extended to the entire repository — without stopping the teams' work.
Let's discuss the task and come back with an estimate and work plan
To make the conversation substantive, it is worth gathering a minimal slice in advance: where the infrastructure code lives, who changes it and how, and which environments live separately.
Based on this slice, we will estimate the scope of Terraform scanning and come back with an estimate and work plan — with stages and timelines, not general words.
It is useful to prepare the following for the task discussion:
- access to repositories — read access is enough to see the module structure and edit history;
- a list of environments and how they are separated: development, pre-production, production;
- current check rules — which checks are already enabled and what needs further configuration.
Based on this slice, we will come back with a work plan and timeline by stage.







