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.
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.
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.
| What tfimpact says | Resource | What happens | Severity | Rule |
|---|---|---|---|---|
| 1 change destroys data | aws_dynamodb_table.sessions | Destroyed 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. | Critical | PLAN-DATA-002 |
| 1 change widens exposure | aws_security_group.bastion | Gains ingress for SSH, tcp port 22, from 0.0.0.0/0. | Critical | PLAN-NET-001 |
| 1 change interrupts service | aws_instance.bastion | Listed 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. | High | PLAN-DISRUPT-001 |
| 38 routine | 10 log groups, 24 SSM parameters, 3 queues, 1 load balancer | Retention and timeout changes, tags and subnets, one line per group. | Routine | None |
The dangerous change does not scroll off the screen behind thirty-eight routine ones.
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.
| Rule | What it says | Severity |
|---|---|---|
PLAN-DATA-001 | A 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-002 | A 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-001 | A 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-001 | Anything 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-001 | An 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-001 | A 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-001 | Ingress opened to 0.0.0.0/0 or ::/0, with the port and service named. | High to critical |
PLAN-EXPOSE-001 | A protection turned off or an exposure flag turned on: public access block, encryption, deletion protection, logging, publicly_accessible. | Low to critical |
PLAN-DELETE-001 | Other 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”.
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.
terraform plan -out=tfplan saves the plan.terraform show -json tfplan > plan.json writes the plan JSON with attribute detail.plan.json on the runner. It makes no call to AWS.fail-on is reached, or exit 2 on bad input.terraform plan -out=tfplan terraform show -json tfplan > plan.json tfimpact explain plan.json
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.
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.
.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.plan -out then show -json). The streaming terraform plan -json output has no attribute detail.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.
The same plan gives the same findings in the same words, every run. Nothing is sampled and nothing depends on the time of day.
Each explanation is a template filled from the plan. There is no model in the path to guess at what a change means.
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.
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.
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.
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.
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.
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.
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.
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.
No. The explanations are templates filled from the plan, so the same plan always gives the same words.
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.
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.