Problems we solve

Assess, fix, keep it that way

EdgeAssessEdgeResolveEdgeAssure

For investors

EdgeSignalAll services
EdgePQ Checktfimpact

Also from InfraEdge

CloudRegionCostAll tools
CompanyHow it worksCase studiesSecurityFor delivery partners
Find your path Request a 20-minute triage call
Free tool · open source

What will this Terraform plan actually do to AWS?

tfimpact reads the plan JSON Terraform already writes and tells you which change destroys data, which one restarts something, which rename quietly recreates a bucket, and which policy just got wider. It runs on your CI runner or your laptop and sends nothing to us. No account, no cloud access, no LLM.

Apache-2.0. Works with Terraform and OpenTofu plan JSON.

The problem

Forty-one resources change, and three of them matter.

Terraform counts changes by action, and a replacement counts twice, as an add and a destroy. It does not say what any of them costs you. This is the example plan bundled with tfimpact, generated with real Terraform against a local AWS mock.

THE EXAMPLE PLAN BUNDLED WITH TFIMPACT · ONE PLAN, TWO READINGS TERRAFORM SAYS Plan: 1 to add, 40 to change, 1 to destroy. 41 RESOURCES CHANGE, COUNTED ALIKE A REPLACEMENT IS AN ADD AND A DESTROY TFIMPACT 1 change destroys data CRITICAL · PLAN-DATA-002 aws_dynamodb_table.sessions: replaced, every item lost 1 change widens exposure CRITICAL · PLAN-NET-001 aws_security_group.bastion: SSH open to 0.0.0.0/0 1 change interrupts service HIGH · PLAN-DISRUPT-001 aws_instance.bastion: user_data change stops and starts it 38 routine ROUTINE 10 LOG GROUPS · 24 SSM PARAMETERS · 3 QUEUES · 1 LOAD BALANCER
What tfimpact says about the example plan
What tfimpact saysResourceWhat happensSeverityRule
1 change destroys dataaws_dynamodb_table.sessionsDestroyed and recreated because hash_key cannot change in place. Every item in the table is lost. Deletion protection and point-in-time recovery are both off.CriticalPLAN-DATA-002
1 change widens exposureaws_security_group.bastionGains ingress for SSH, tcp port 22, from 0.0.0.0/0.CriticalPLAN-NET-001
1 change interrupts serviceaws_instance.bastionListed by Terraform as an update in place. A user_data change stops and starts the instance, which gets a new public IP unless it has an Elastic IP.HighPLAN-DISRUPT-001
38 routine10 log groups, 24 SSM parameters, 3 queues, 1 load balancerRetention and timeout changes, tags and subnets, one line per group.RoutineNone

The dangerous change does not scroll off the screen behind thirty-eight routine ones.

What it looks for

Every rule, with the ID you will see in a finding.

Rule IDs never change meaning. A rule that needs different semantics gets a new ID, so a suppression written today still means the same thing next year.

The rules tfimpact applies to a plan
RuleWhat it saysSeverity
PLAN-DATA-001A resource that holds data is destroyed. Names what is lost and whether a final snapshot, deletion protection, point-in-time recovery or a recovery window softens it.High to critical
PLAN-DATA-002A resource that holds data is destroyed and recreated. Names the attribute that forced the replacement, with its before and after values.High to critical
PLAN-RENAME-001A resource was renamed in code without a moved block: the plan destroys one address and creates another with the same name.High to critical
PLAN-REPLACE-001Anything else replaced, with the attribute that forced it and the service effect: a new DNS name, a new IP, dependent resources.Medium to high
PLAN-DISRUPT-001An in-place update that still interrupts service: an instance class change, a user_data change that stops and starts the instance, an RDS identifier rename that changes the endpoint.Low to high
PLAN-IAM-001A policy, trust policy or resource policy got wider: wildcard actions, Resource = "*", Principal = "*", a removed or loosened condition, a federated trust with no sub.Low to critical
PLAN-NET-001Ingress opened to 0.0.0.0/0 or ::/0, with the port and service named.High to critical
PLAN-EXPOSE-001A protection turned off or an exposure flag turned on: public access block, encryption, deletion protection, logging, publicly_accessible.Low to critical
PLAN-DELETE-001Other deletions, raised when the resource is protective: a trail, a flow log, a public access block.Low to high

Everything else is listed as routine in one line per group, so forty tag changes take one line. Tag-only updates, no-ops and data source reads are counted, never raised. Moves made with a moved block are listed as address changes. Drift is listed under “Changed outside Terraform”.

In a pull request

One comment on the pull request, kept up to date.

The action writes the report to the job summary and keeps one comment on the pull request current as the branch changes. With fail-on set, the step exits 1 when an unsuppressed finding is high or critical, so the check fails on the pull request itself.

IN CI · ONE PLAN, ONE COMMENT, ONE EXIT CODE terraform planSAVES THE PLANTO A FILE terraform show -jsonTHE PLAN AS JSON,WITH DETAIL tfimpactREADS THE PLANNO CALL TO AWS Pull requestONE COMMENT,KEPT UP TO DATE AND THE REPORT IN THE JOB SUMMARY FAIL-ON: HIGH EXIT 1 FAILS THE CHECK EXIT 0 OTHERWISE · EXIT 2 ON BAD INPUT
  1. terraform plan -out=tfplan saves the plan.
  2. terraform show -json tfplan > plan.json writes the plan JSON with attribute detail.
  3. tfimpact reads plan.json on the runner. It makes no call to AWS.
  4. One comment on the pull request, edited in place on every push, and the report in the job summary.
  5. Exit 0, or exit 1 when fail-on is reached, or exit 2 on bad input.

On your laptop first

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
tfimpact explain plan.json

Accepting a finding

Put a .tfimpact.toml next to where you run it. A suppression needs a reason and may expire. Suppressed findings stay in the report under their own heading and do not count towards fail-on.

In GitHub Actions

permissions:
  contents: read
  pull-requests: write

steps:
  - uses: actions/checkout@<sha>
  - uses: hashicorp/setup-terraform@<sha>
  - run: terraform init && terraform plan -out=tfplan && terraform show -json tfplan > plan.json
  - uses: infraedgecloud-tools/tfimpact@<sha>   # pin to a release tag or commit
    with:
      plan: plan.json
      version: v0.1.0     # pin the tfimpact release too; the default is latest
      fail-on: high        # exit 1 when an unsuppressed finding is high or critical

GitLab CI and Azure DevOps snippets are in the repository under docs/ci.

What it does not do

Where it stops, in the tool’s own words.

  • It is not a scanner of your .tf files. It reads the plan, so it only sees what is about to change. Pair it with tflint, Checkov or Trivy for static policy.
  • It knows AWS. Other providers are listed as plain changes.
  • It does not estimate cost, and it does not call AWS to look at what is deployed.
  • It needs the saved-plan route (plan -out then show -json). The streaming terraform plan -json output has no attribute detail.
  • It has no AI mode. The explanations are templates filled from the plan, so the same plan always gives the same words.

Tested with Terraform 1.15 and OpenTofu 1.13 plan JSON (format 1.2). The format is covered by Terraform’s 1.x compatibility promise; unknown fields are ignored.

How it is built

Plain enough to read before you run it.

Deterministic

The same plan gives the same findings in the same words, every run. Nothing is sampled and nothing depends on the time of day.

No AI mode

Each explanation is a template filled from the plan. There is no model in the path to guess at what a change means.

Knowledge as data

What it knows about AWS lives in one TOML file per resource type. Adding a resource is a data change plus a fixture, not Go. tfimpact knowledge lists the types it knows.

Fixtures from real Terraform

Every test plan is generated by real Terraform against a local moto mock of AWS. No plan.json in the repository is edited by hand.

Sensitive values stay hidden

It honours Terraform’s sensitive marks and never prints attributes whose names suggest secrets, such as password, token or user_data, marked or not. --no-values prints attribute names only.

Apache-2.0

Read it, fork it, run it inside your own pipeline. A knowledge file for a resource type you know well is the fastest useful contribution.

Questions

What engineers ask before they run it.

Does tfimpact send my plan anywhere?

No. It reads the plan file on the machine it runs on, needs no account, and does not call AWS. The report goes to your terminal, a file, or your own pull request.

Does it replace tflint, Checkov or Trivy?

No. Those read your .tf files for static policy. tfimpact reads the plan, so it only sees what is about to change. Run them together.

Which Terraform output does it need?

The saved plan as JSON: terraform plan -out=tfplan, then terraform show -json tfplan. The streaming terraform plan -json output has no attribute detail. OpenTofu plan JSON works the same way.

Will it print secrets from my plan?

No. It honours Terraform’s sensitive marks and also hides attributes whose names suggest secrets, marked or not. The plan JSON itself still holds sensitive values in plain text, so treat that file as a secret.

Does it use AI?

No. The explanations are templates filled from the plan, so the same plan always gives the same words.

Can I accept a finding and move on?

Yes. A suppression in .tfimpact.toml needs a reason and may expire. The finding stays in the report under its own heading and no longer counts towards fail-on.

Want it across every pipeline you run?

In the paid work an engineer wires tfimpact into each repository’s pull requests, runs it across your existing plans, and fixes what it finds: the missing moved blocks, the backups and deletion protection a replacement needs, the policies that grew too wide. The fixes arrive as pull requests in your repository for your team to review. Twenty minutes on a call tells you whether that is worth doing.