Safeguard
Developer Tools•

How do I check a Terraform plan with Open Policy Agent?

Save the plan, convert it to JSON with terraform show, then evaluate Rego rules against it. Steps, a sample rule and what to do with the result.

Safeguard Security Team
3 min read

Save the plan with terraform plan and an output file, convert it to JSON with terraform show and the json option, then evaluate Rego rules against that JSON using the Open Policy Agent command line. A rule that returns messages for forbidden changes can fail the pipeline before anything is applied.

Why check the plan, not just the code

A plan has resolved variables, module outputs and the actual actions Terraform intends: create, update or delete. Rules on raw code cannot see those. Rules on the plan can, for example "never delete a database" or "no changes to IAM without review". The Open Policy Agent documentation walks through this same flow.

The steps

  1. Create the plan. Save it to a file so the exact plan you checked is the plan you apply.
terraform init
terraform plan -out=tfplan.binary
  1. Convert it to JSON. OPA reads JSON, not the binary plan.
terraform show -json tfplan.binary > tfplan.json
  1. Write a rule. The JSON has a list called resource_changes. Each entry has a type and a change object with an actions list. This is a small example rule to adapt, not a complete policy:
package terraform.plan

import rego.v1

deny contains msg if {
  some rc in input.resource_changes
  rc.type == "aws_db_instance"
  "delete" in rc.change.actions
  msg := sprintf("Deleting %s needs a manual review", [rc.address])
}
  1. Evaluate it.
opa eval --format pretty --data policy.rego --input tfplan.json "data.terraform.plan.deny"
  1. Fail the build if the result is not empty. Wrap the command in a script that exits non-zero when it returns any message.

What the OPA tutorial's own policy does

The official walkthrough uses a different idea: it computes a blast radius score from weighted counts of creates, deletes and modifications by resource type, and authorises the plan only if the score is under a threshold and no IAM resources change. It notes that in practice you would vary the threshold by user. That pattern suits teams that want automatic approval for small changes and human review for large ones.

Things to watch

  • Treat the JSON as sensitive. It can contain secret values. See saved plan files contain secrets.
  • Test your rules. A rule that never fires is worse than none. Write unit tests with opa test.
  • Apply the saved plan. If you re-plan before applying, you are applying something nobody checked.

Try it yourself

Click through it at your own pace, or open it full screen.

Look at how a failing check blocks the merge. A non-empty deny result from your plan check should do the same job.

Quick check

Why convert the plan to JSON?

OPA evaluates structured data such as JSON. The binary plan file is not something it can read directly.

Is checking code enough without checking the plan?

Code checks are fast and early, but they cannot see resolved values or the actual actions. Plan checks catch what only appears once values are known.

Safeguard runs policies and gates on pull requests, so a failed rule can hold a merge, and its dependency and SBOM scanning covers the packages your infrastructure code pulls in. You can try a free scan at gold.safeguard.sh/scan.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.