AI-Written Terraform: Plan Diff Gates Before Any Apply Runs

AI-Written Terraform: Plan Diff Gates Before Any Apply Runs

Coding agents that can terraform apply are one prompt injection away from opening your account. The unfair advantage is not “better prompts for HCL” — it is plan-diff gates: every model-proposed change must produce a plan artifact, pass OPA/Conftest, and get human approval before apply. Never auto-apply IAM, network, or KMS from an unattended agent.

⚡ TL;DR: Agent writes .tf → CI runs terraform plan -out → OPA checks the JSON plan → humans approve high-risk deltas → only then apply. Ban apply from agent shells. Combine with Claude Code hooks for risky shell and Cursor CDK wildcard rules.

Force plan artifacts, not vibes

# modules/ai_gate/main.tf — illustrative boundaries
resource "aws_iam_policy" "svc" {
  name   = var.policy_name
  policy = data.aws_iam_policy_document.svc.json
}

# ❌ Agent anti-pattern
# resource "aws_iam_policy" "svc" {
#   policy = jsonencode({
#     Statement = [{ Effect = "Allow", Action = "*", Resource = "*" }]
#   })
# }
# ci/terraform-agent-pr.sh
set -euo pipefail
terraform init -backend-config=backend.hcl
terraform plan -input=false -out=tfplan.bin
terraform show -json tfplan.bin > tfplan.json

# ✅ OPA on the plan, not on HCL text alone
conftest test tfplan.json -p policy/terraform

# ❌ terraform apply in the agent tool allowlist

OPA policies that catch AI favorites

# policy/terraform/no_wildcards.rego
package terraform.aws

deny[msg] {
  some change in input.resource_changes
  change.type == "aws_iam_policy"
  after := change.change.after
  contains(after.policy, `"Action":"*"`)
  msg := sprintf("wildcard Action in %s", [change.address])
}

deny[msg] {
  some change in input.resource_changes
  change.type == "aws_security_group_rule"
  change.change.after.cidr_blocks[_] == "0.0.0.0/0"
  change.change.after.to_port == 22
  msg := "SSH open to world"
}

deny[msg] {
  some change in input.resource_changes
  change.type == "aws_kms_key"
  change.change.actions[_] == "delete"
  msg := "KMS key deletion requires break-glass label"
}

✅ Plan JSON is the source of truth for policy.
❌ Regex on .tf files that miss jsonencode and modules.

Human approval for blast-radius classes

Change class Auto-merge after green plan? Required
Tags / dashboards Yes Plan + OPA
IAM / SG / NACL No +2 reviewers
KMS / Org SCPs No Break-glass + change ticket
State rm / destroy No Explicit DESTROY_OK label
# .github/workflows/tf-agent.yml
jobs:
  plan:
    if: contains(github.event.pull_request.labels.*.name, 'ai-terraform')
    steps:
      - run: ./ci/terraform-agent-pr.sh
      - uses: actions/upload-artifact@v4
        with: { name: tfplan, path: tfplan.bin }
  apply:
    needs: plan
    environment: production-terraform  # ✅ GitHub env protection rules
    if: github.event.review.state == 'approved'
    steps:
      - run: terraform apply -input=false tfplan.bin

Hook agent shells so terraform apply never runs locally in CI agents — see Claude Code hooks.

Closing checklist

✅ Dos
– ✅ Always plan -out + show -json for agent PRs
– ✅ OPA/Conftest on plan JSON for IAM, network, KMS
– ✅ Environment-protected apply jobs
– ✅ Label-gated destroy and state surgery
– ✅ Ban apply from agent tool allowlists

❌ Don’ts
– ❌ Don’t auto-apply from unattended coding agents
– ❌ Don’t trust HCL lint alone for wildcards
– ❌ Don’t let agents hold long-lived cloud admin keys
– ❌ Don’t skip human review on IAM/SG/KMS deltas
– ❌ Don’t ignore AI coding VPC hygiene for plan runners

Related reading

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

1 Comment

Leave a Reply