I use Claude to review Terraform before every apply — it finds what I miss
← Back
April 4, 2026Claude6 min read

I use Claude to review Terraform before every apply — it finds what I miss

Published April 4, 20266 min read

I ran terraform apply on a plan that had been reviewed by two engineers. Fifteen minutes later I realized I had created an S3 bucket with public read access. The plan clearly showed acl = "public-read" but we had both missed it in a 400-line diff. Now I run a Claude review before every non-trivial apply and it catches exactly these kinds of issues.

The review script

bash
#!/bin/bash
# tf-review.sh — run before terraform apply

set -e

PLAN_FILE="${1:-tfplan}"

# Generate plan if not provided
if [ ! -f "$PLAN_FILE" ]; then
  echo "Generating plan..."
  terraform plan -out="$PLAN_FILE"
fi

# Convert plan to human-readable JSON
terraform show -json "$PLAN_FILE" > /tmp/tfplan.json

# Run Claude review
echo "Running security review..."
cat /tmp/tfplan.json | python3 - <<'EOF'
import sys
import json
import anthropic

plan = json.loads(sys.stdin.read())
client = anthropic.Anthropic()

# Extract relevant changes
changes = []
for change in plan.get("resource_changes", []):
    if change.get("change", {}).get("actions", []) != ["no-op"]:
        changes.append({
            "resource": change["address"],
            "type": change["type"],
            "actions": change["change"]["actions"],
            "before": change["change"].get("before"),
            "after": change["change"].get("after"),
        })

if not changes:
    print("No infrastructure changes detected")
    sys.exit(0)

changes_json = json.dumps(changes, indent=2)

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=2000,
    system="""You are a cloud security engineer reviewing Terraform plans.

Check for:
1. SECURITY: Public access on S3/storage, overly permissive security groups (0.0.0.0/0),
   IAM policies with * actions, missing encryption, public RDS instances
2. RELIABILITY: Missing deletion_protection on databases, no backup retention,
   single-AZ deployments for critical resources
3. COST: Unexpectedly large instance types, resources missing auto-scaling
4. DESTRUCTIVE: Resources being destroyed (especially databases, S3 buckets with data)

Format findings as:
## [SEVERITY: CRITICAL/HIGH/MEDIUM/LOW] Resource: Issue
What: specific misconfiguration
Risk: what could go wrong
Fix: the exact Terraform change needed

If no issues found, say "No issues found — safe to apply."
""",
    messages=[{
        "role": "user",
        "content": f"Review this Terraform plan for issues:

{changes_json[:30000]}"
    }]
)

print(response.content[0].text)
EOF

Adding it to your workflow

bash
# Makefile
plan:
	terraform plan -out=tfplan

review: plan
	./scripts/tf-review.sh tfplan

apply: review
	@echo "Press Enter to apply, Ctrl+C to cancel"
	@read
	terraform apply tfplan

What it catches that humans miss

Over six months of using this, here are the real findings Claude caught before apply:

  • S3 bucket with block_public_policy = false — intended for static assets but the ACL was wrong
  • Security group opening port 22 (SSH) to 0.0.0.0/0 for "temporary" debugging
  • RDS instance without deletion_protection = true on a production database
  • Lambda with IAM role containing "Action": "*" — way more permissions than needed
  • ElastiCache without encryption at rest — left from a dev environment copy-paste

None of these were caught in human review. They were all obvious once flagged. The human review was not negligent — it was just fatigued.

GitHub Actions integration

yaml
# .github/workflows/tf-review.yml
name: Terraform Review
on:
  pull_request:
    paths: ['terraform/**', 'infra/**']

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      
      - name: Terraform Plan
        run: terraform plan -out=tfplan
        working-directory: terraform/
        
      - name: AI Security Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          terraform show -json tfplan > /tmp/plan.json
          python3 scripts/tf-review.py /tmp/plan.json > review.md
        working-directory: terraform/
        
      - name: Post Review
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const review = fs.readFileSync('terraform/review.md', 'utf8');
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: '## Infrastructure Security Review

' + review
            });

Every Terraform PR now gets an automatic security review as a comment. CRITICAL and HIGH findings block the merge via branch protection rules. Medium and low findings are informational — the engineer decides whether to address them before merging.

Share this
← All Posts6 min read