Set up IaC scanning in four steps: scan the code on every pull request in report only mode, tune the noise, scan the rendered plan, then turn on blocking for a short list of rules. Adding a gate on day one is the usual way these rollouts get switched off.
The examples use Terraform because its docs are public and precise, but the order applies to any infrastructure as code tool.
Why does the order matter?
Static scanning of config files and scanning a plan answer different questions. The first reads what you wrote. The second reads what the tool will actually do after variables, modules and existing state are resolved. Starting with the first is cheap and safe. Starting with the second needs credentials and state access, which is a bigger decision.
Step 1: scan the files, report only
Run the scanner on pull requests against the directory with your infrastructure code. Do not fail the build yet. Output the results in a standard format such as SARIF so they appear next to the diff. Your goal this week is a baseline: how many findings, which rules fire most, which repositories are worst.
Also run the basic checks your tool provides. Terraform's validate command checks the syntax of the configuration files in a directory, which catches broken code before a scanner spends time on it.
Step 2: tune before you enforce
Look at the top five rules by count. For each, choose one of three outcomes: fix the code, change a shared module so every user benefits, or suppress with a written reason in a reviewed file. Suppressions that live in a reviewed file survive staff changes. Suppressions in a dashboard do not.
Wait until a typical pull request produces no findings the author calls wrong. That is your readiness test for blocking.
Step 3: scan the plan
terraform plan previews the changes without applying them. With -out it writes the plan to a file, which the docs describe as intended mainly for automation, so a later apply step executes exactly what was reviewed.
Two details from the documentation matter for CI.
- A saved plan file holds your full configuration and variable values, and sensitive data is stored in cleartext even though terminal output hides it. The docs say to treat saved plans as potentially sensitive artifacts. So do not upload them as ordinary build artifacts with wide read access.
- With
-detailed-exitcode, exit code 0 means success with no changes, 1 means error, and 2 means success with changes present. A pipeline that treats any non-zero code as failure will break on every real change. Handle 2 explicitly.
Plan scanning finds things files cannot, such as a public setting that comes from a variable default in a module. Run it where credentials already exist, usually the deploy pipeline and not the pull request job from forks.
Step 4: gate on a short list
Pick rules where a hit is almost always real and expensive: storage open to the public, rules that allow all traffic from anywhere, unencrypted databases, hard coded secrets. Block merges on those only. Leave the rest as warnings with an owner and a date.
Where policy gates live depends on your tool. With Safeguard's CLI, for example, the docs state safeguard scan exits 0 whenever the scan ran and gating is a platform policy, not a CLI threshold flag. Check how your own scanner behaves before you wire a pipeline to its exit code.
What to do
- Add a report only IaC scan to pull requests, output as SARIF.
- Record the baseline and the five noisiest rules.
- Fix, fix in a shared module, or suppress with a reason in a reviewed file.
- Add plan scanning in the deploy pipeline, and protect saved plan files.
- Handle exit code 2 from the plan step explicitly.
- Block merges on three to five high confidence rules.
- Review the list monthly and promote one rule at a time.
Quick check
Should the first IaC scan block pull requests?
No. Run report only first, build a baseline, tune noise, then block a short list of high confidence rules.
Is a saved Terraform plan safe to store as a build artifact?
Treat it as sensitive. Per the Terraform docs it can contain sensitive values in cleartext, so restrict access and retention.
What does exit code 2 mean from terraform plan?
With the detailed exit code option, 2 means the plan succeeded and changes are present. It is not a failure.
Sources
- terraform plan command reference, HashiCorp
- terraform validate command reference, HashiCorp
- SARIF v2.1.0, OASIS
- Safeguard CLI usage
Last reviewed: 18 October 2026.