Terraform review
Review infrastructure as code. Prefer static analysis and terraform plan. Never apply, destroy, or force-unlock state unless the user explicitly authorizes the exact command.
Purpose and scope
In scope
- Terraform
*.tf,*.tfvars(redacted), modules, and version constraints terraform plan/terraform showoutput the user provides- Security issues: public exposure, IAM wildcards, unencrypted storage, missing logging
- State backend, locking, workspace, and drift process (how to detect, not silent repair)
- Cost smells: oversized instances, unused NAT, untagged resources, overly broad replication
Out of scope
- Authoring a full greenfield AWS account architecture (hand off to an AWS review Skill)
- Kubernetes workload crashes (use
kubernetes-troubleshooting) - Running
terraform applyorterraform destroyas part of the review - Inventing plan output, state serials, or remote backend contents
When to use
Use this Skill when the user asks for a Terraform code review, IaC security review, plan interpretation, drift explanation, state lock help, or cost reduction on Terraform-managed resources.
Do not use it to debug GitHub Actions YAML or to run an incident command structure.
Inputs
- Root module path (default: current directory)
- Cloud provider(s) in use
- Whether this is a PR review, a local plan, or a production change
- Plan file (
tfplanorterraform show -json) when available - Backend type (S3/GCS/azurerm/remote/local) if known
If there are no .tf files and no plan output, stop and ask. Do not invent a module tree.
Workflow
- [ ] 1. Inventory modules and providers
- [ ] 2. Run read-only scans
- [ ] 3. Review security and IAM
- [ ] 4. Review state, locking, and blast radius
- [ ] 5. Interpret plan if present
- [ ] 6. Cost and operability
- [ ] 7. Report findings with severity
- [ ] 8. Stop before apply
1. Inventory
find . -name '*.tf' -not -path '*/.terraform/*' | sort
terraform version
terraform fmt -check -recursive
Read required_version, required_providers, backend block, and the graph of module sources. Pin versions; flag latest and unpinned git modules.
2. Read-only scans
If the helper is available:
python3 scripts/scan-terraform.py .
The scanner is heuristic. Treat hits as leads, not proof. Also run, when Terraform is installed and the user wants a plan without apply:
terraform init -backend=false
terraform validate
terraform plan -no-color -input=false
Use -backend=false only when you cannot reach the backend and the user agrees that providers/modules may still need network. Prefer a normal init (no apply) when credentials for read exist.
Do not pass -auto-approve. Do not run terraform apply. Do not run terraform destroy. Do not run terraform force-unlock without explicit approval.
3. Security and IAM
Walk references/review-checklist.md. High-severity patterns:
0.0.0.0/0on SSH (22), RDP (3389), or databases- S3/GCS buckets with public ACL or
ignore_public_aclsdisabled plus public policy - Missing encryption (
encrypted = false, no KMS) - IAM
Action = "*"withResource = "*" - Security groups referenced but never attached
- Secrets in
.tfor committed.tfvars(password,secret_key, private keys)
Redact secret values in the report. Point to the file and resource address only.
4. State, locking, and blast radius
- Local state (
terraform.tfstatein git) is a critical finding. - Remote backends need locking (S3 + DynamoDB / native lock, GCS, azurerm).
-targetin history or docs is a smell; call out partial apply risk.terraform state rm/mvare mutating; describe the procedure, do not execute.- Drift:
planshowing unexpected in-place or destroy/create. Ask what changed out of band. Do not refresh-only against prod without approval (terraform apply -refresh-onlyis still an apply).
5. Interpret the plan
Classify each change:
| Plan verb | Review focus |
|---|---|
+ create | Cost, exposure, naming, tags, dependencies |
~ update in-place | Downtime, IAM widening, public access flips |
- destroy | Data loss, state orphans, DNS |
-/+ replace | Replacement triggers (force_new), disks, IPs |
Count resources. A 200-resource replace in prod is an incident-in-waiting; recommend split or targeted (still gated) rollout.
If there is no plan, say plan not provided and limit claims to static code.
6. Cost
Flag: t2/t3.2xlarge without justification, untagged resources, NAT Gateway per AZ without need, aws_eip unattached, RDS Multi-AZ on a sandbox, verbose CloudWatch logs with no retention, unused load balancers in code that always create them.
Do not quote a dollar figure unless the user supplied pricing data.
7. Report
Use the output template. Sort by severity. Every finding needs file path + resource address + why + fix.
8. Approval gate
Never terraform apply without explicit user authorization of the exact command line, workspace, and target environment.
Best practices
- Prefer
terraform plan -out=tfplanthen a reviewedapply tfplan(user-run). prevent_destroyon state buckets, databases, and KMS keys.- Restrict
provideraliases; accidental alias mix-ups create resources in the wrong account. - Use
terraform fmtandvalidatein CI; this Skill does not replace CI. - Modules from the public registry need pinned versions, not
version = ">= 0". for_eachover counted resources when identities matter (IAM, security groups).- Separate plan permissions from apply permissions in CI roles.
- Do not store production state in developer laptops.
Commands and examples
Static review only
python3 scripts/scan-terraform.py infra/live/prod
Example scanner finding (illustration):
HIGH examples/insecure-s3.tf:12 aws_s3_bucket.logs acl = "public-read"
HIGH examples/iam.tf:8 aws_iam_policy.too_open Action="*" Resource="*"
Plan review (example excerpt)
Plan: 1 to add, 1 to change, 0 to destroy.
~ aws_security_group.api
ingress.cidr_blocks: ["10.0.0.0/8"] => ["0.0.0.0/0"]
This is a high-severity exposure change. Do not apply.
State lock (example)
User sees Error acquiring the state lock. Collect lock ID and who holds it. Recommend waiting or contacting the holder. terraform force-unlock <ID> is last resort and approval-gated; unlocking under a live apply corrupts state.
Expected output
# Terraform review
## Scope
- Paths / modules:
- Terraform version:
- Backend:
- Plan provided: yes/no
- Environment assumption:
## Summary
- Blockers:
- High:
- Medium:
- Low:
## Findings
### [SEVERITY] short title
- Address / file:
- Evidence:
- Impact:
- Recommended change:
- Requires apply: yes/no
## Plan interpretation
- Creates / updates / destroys / replaces:
- Data-loss risks:
- Exposure changes:
## Cost notes
- Qualitative only unless prices were provided.
## Approval required
Exact mutating commands that remain blocked.
## Assumptions and limitations
What was not executed (no apply, maybe no init).
Error handling
| Situation | Action |
|---|---|
| No Terraform CLI | Static review + scanner only; say validate/plan were skipped |
init needs credentials | Do not paste access keys; ask the user to init locally and provide plan |
| Backend access denied | Review code; do not disable the backend to fake a plan |
Parse error in .tf | Quote the HCL error; do not "fix and apply" |
| Mixed Terragrunt | Review generated/used Terraform; do not run terragrunt apply |
| Scanner false positive | Keep the finding as informational if unsure; do not hide it |
Never create fake terraform plan results to fill the report.
Security and safety
- Redact
AWS_SECRET_ACCESS_KEY, tokens, private keys, andtfvarssecret values. - Do not commit or echo
.terraform/orterraform.tfstate. - Do not terraform apply, destroy, refresh-only apply, or force-unlock without explicit authorization.
- Do not broaden IAM (
*) or open0.0.0.0/0as a "temporary" debug step. - Do not switch
terraform workspaceon a shared backend without confirmation (wrong workspace apply is a classic outage). - Treat
local-execprovisioners as arbitrary code execution; flag them.
Additional resources
- Review checklist: references/review-checklist.md
- Heuristic scanner:
scripts/scan-terraform.py