Read this in French.
An AI agent that builds, validates, and secures Terraform — with a safety layer it can't bypass.
tfforge writes Terraform code itself, validates it, scans it for security issues, and auto-corrects until it's clean — while every destructive action passes through policy-as-code guards, so the agent can help without being able to wreck production. It's built from scratch on the Anthropic Messages API (no agent framework), so the agent loop is fully visible.
Not another "LLM that writes HCL". The market is full of those. The value here is the safety, reliability, and auditability layer around an agent — the part real teams struggle with when putting agents in production.
Agentic AI in production is, above all, an infrastructure and orchestration problem: an agent that calls tools, handles errors, respects guardrails, logs every action, and whose cost you can see. That's DevOps. tfforge is a from-scratch demonstration of exactly that, on a task a DevOps engineer knows intimately — Terraform — with the security and LLMOps concerns that make an agent trustworthy.
you: "build a private, encrypted S3 bucket with least-privilege IAM,
scan it, and fix any findings"
│
1. GENERATE ──▶ the agent writes the Terraform itself (write_file)
│
2. VALIDATE ──▶ terraform_validate (won't scan code that doesn't parse)
│
3. SECURE ────▶ security_scan: checkov / trivy / tfsec + provider-aware
│ checks (wildcard IAM, public S3, missing encryption…)
│
4. AUTO-CORRECT ▶ findings? the agent edits (edit_file) and scans AGAIN
│ (the loop that makes it feel alive) — until clean
│
5. PLAN ──────▶ terraform_plan, rendered as a readable table
│
6. GUARD ─────▶ apply/destroy pass through policy — destroy on prod is BLOCKED
-
It adopts a repo you already have.
tfforge audit <repo>walks a whole existing Terraform repository — every module and environment — runs the deterministic analysis per directory, and prints a prioritized health report: the worst things first, across five categories (security, version/deprecation, best-practice, structure, variables), with a per-directory rollup of where the debt concentrates. No blank-file demo — this is the daily-work case (you inherit a repo and ask "where do I start?"). It costs zero tokens (no LLM), so it also gates CI, and it emits a self-contained, shareable HTML report — a tabbed deliverable (one tab per category, each counted) you can open in a browser or attach to a review:tfforge audit ./infra # prioritized text report tfforge audit ./infra --html --out health.html # a tabbed, shareable deliverable tfforge audit ./infra --json --fail-on high # gate CI (exit ≠ 0) tfforge audit ./infra --html --explain --out health.html # + AI-written fixes (opt-in, needs a key)
The report is multi-cloud aware: it auto-detects the providers in play (OpenStack/OVH, AWS, GCP, Azure, Cloudflare, Datadog, Kubernetes…) and shows them as chips in the header.
--explainis the optional AI layer: with a key it adds, per finding, a prose fix plus a before/after HCL diff — the current problematic code and the corrected version side by side, tailored to the repo's actual cloud (a Swift/OVH backend on an OpenStack repo, not an AWS S3 one). The "before" is your real code and the "after" is the fix applied to it — a faithful diff, not a generic example. The HTML footer shows the AI call's cost (model + tokens + ~$) for FinOps visibility. Privacy:--explainsends the relevant.tfto the API with secret values masked (password = "***") — nothing is sent at all without--explain(the scan is 100% local). Without a key it degrades cleanly and still writes the report. Deterministic detects, the AI explains. -
A from-scratch agent loop —
message + tools → tool_use → guard → run → result → loop, turn-bounded. Written on raw HTTP against the Anthropic Messages API, no framework — the ~100 lines that make an agent click. The model client is an interface, so the whole loop is unit-tested with a scripted fake (no API key, no tokens). -
It builds infrastructure.
write_filelets the agent generate and rewrite.tffiles (confined to the project). Security-by-default is baked into the system prompt: private S3 + public-access block + encryption, least-privilege IAM (neverAction "*"), encryption at rest, no hard-coded secrets. -
Works with any provider. The agent, the guard,
plan, and the external scanners (checkov/trivy) are provider-agnostic — use tfforge on AWS, GCP, Azure, or a private cloud (VMware, OpenStack…) all the same. On top of that, tfforge's own high-signal rules cover the three hyperscalers (AWS, GCP, Azure); everything else still gets checkov + the agent + the guard. -
It secures — and modernizes — what it builds.
security_scanuses the best installed scanner (checkov preferred, then trivy, then tfsec) plus a deterministic provider-aware pass that flags issues in five categories (auditsurfaces all five; the build loop focuses on the first three):- security — wildcard IAM, public S3 (ACL or a
Principal "*"bucket policy), missing encryption (S3, RDS/Aurora, Redshift, EBS, EFS), SSH/RDP open to the world (IPv4 and IPv6::/0), wide-open ingress/egress,iam:PassRoleon*, hard-coded secrets (attribute, JSON, or heredoc/ user_data — never printing the value). The rules are adversarially tested: a suite of tricky cases locks in both what must be caught and what must stay quiet (e.g.s3:*in aDenyis fine, a modernaws_s3_bucket_aclisn't a deprecation) — so there are no annoying false positives. The same rigor covers GCP (public GCS bucket, firewall open to the world, primitive IAM roles, long-lived SA keys) and Azure (open NSG rules, public blob, HTTPS off, old TLS); - version — deprecated syntax (inline S3
acl/versioning/encryption on the modern provider), an outdated AWS provider (v3 or older), a missing or pre-1.0required_version— so the agent modernizes code, not just secures it. On top of the built-in rules,security_scanruns tflint when installed, whose rules are maintained by HashiCorp's ecosystem — so tfforge keeps flagging new deprecations as Terraform evolves, without shipping new code; - best-practice — a hard-coded provider region or location (provider-
agnostic: it catches AWS
us-east-1, OVHGRA7, GCPus-central1, Azurewesteuropealike, not just AWS), missingrequired_providers, a root module with no remote backend; - structure — the same resource type copy-pasted ≥4× with no
count/for_each(repetition that wants afor_eachor an extracted module to stay DRY — e.g. 14 near-identicaldatadog_monitorblocks); - variables — declared variables with no
type(a bad value should fail fast at plan time) or nodescription(self-documenting inputs).
The agent scans, fixes (surgically with
edit_file, not a full rewrite), and re-scans until clean. - security — wildcard IAM, public S3 (ACL or a
-
The guard — the differentiator. The same policy-as-code idea as opsforge's shell guards, applied to the agent's actions: rules (
action × context → allow/warn/confirm/deny, first match wins). The default policy deniesdestroyon production and confirmsapplyon production — and it fails closed: a destroy on a context it can't prove is non-prod (it reads the Terraform workspace passively, not just the path) is blocked. Read-only actions bypass it; custom YAML policies are supported. -
Readable plans for big repos.
terraform_planparsesterraform show -jsoninto a colored table —+create / ~update / -destroy / ±replacecounts, destructive changes first, a ⚠ warning, capped with a by-type rollup for the long tail. The agent receives only a compact digest, so a 500-change plan doesn't blow the context or the token bill. Deterministic parsing counts; the LLM only explains — the pattern that scales. -
Audit + cost + budget (the LLMOps layer). Every turn and every guarded action is written to a JSONL audit log (
~/.local/state/tfforge/audit.jsonl) — a reviewable trail of what the agent did and what the guard blocked. Token usage is priced into an estimated cost, printed in a run summary. A budget (TFFORGE_MAX_COST) stops the run before it exceeds a ceiling — FinOps for agents. The agent is also tuned to spend fewer tokens: it edits surgically instead of rewriting files, plans once, and keeps prose short; andTFFORGE_MODEL=claude-haiku-4-5runs it ~3× cheaper. -
A CI mode — no LLM.
tfforge scan <dir> [--json] [--fail-on <sev>]runs the deterministic security analysis only (no API key, no tokens) and exits non-zero when findings meet the threshold — so the same security brain the agent uses can gate a pipeline.--jsonemits a machine-readable report; the provider-aware rules catch least-privilege and network gaps a coarse scanner passes (as3:*service wildcard, an account-wide S3 ARN, SSH open to0.0.0.0/0,iam:PassRoleon*, a publicly-accessible DB).
# macOS (Homebrew):
brew install mrg77/tap/tfforge
# Linux (Debian/Ubuntu/Alpine…) or macOS — the install script:
curl -fsSL https://raw.githubusercontent.com/Mrg77/tfforge/master/install.sh | sh
# or build from source:
go build -o tfforge .The script picks the right binary for your OS/arch and drops it in ~/.local/bin
(override with TFFORGE_INSTALL_DIR, pin a version with TFFORGE_VERSION=v0.1.0).
# 1. An Anthropic API key — billed per token, separate from a Claude subscription.
export ANTHROPIC_API_KEY=... # https://console.anthropic.com
# 2. Build (or use the brew install above)
go build -o tfforge .
# 3. Build + scan + auto-correct a secure S3 stack (the headline demo)
./tfforge "build a private, encrypted S3 bucket with least-privilege IAM in \
./examples/out, scan it for security issues, and fix anything the scan finds"
# 4. Watch the guard block a production destroy
./tfforge "destroy the infrastructure in ./examples/prod" # → BLOCKED by policy
# 5. Watch the scan + auto-correct fix deliberately-broken code
./tfforge "scan ./examples/insecure and fix every security finding, \
telling me what was wrong and what you changed"
# 6. CI mode — no LLM, no key. Exits non-zero on findings → gate a pipeline.
./tfforge scan ./examples/insecure --json --fail-on high
# 7. Adopt an EXISTING repo — prioritized health report (no LLM), then a
# shareable HTML deliverable you can open or attach.
./tfforge audit ./examples
./tfforge audit ./examples --html --out health.htmlEach run prints a summary: run summary · N turns · … tokens · M tool call(s) (K denied) · ~$cost. Optional scanners: opsforge install checkov (or trivy).
No API key or network needed — the model client is faked, terraform/policy/plan logic is exercised directly:
go test ./...Coverage includes: the agent loop (happy path, guard-deny blocks the tool, loop bound, unknown tool), the guard (deny prod destroy, fail-closed on an unknown context, warn-gated, empty-policy fallback), the provider-aware analyzer, the plan parser (replace both orders, big-plan truncation), and cost/audit.
- No framework, on purpose. The whole value is seeing the loop.
internal/agentis the small core that demystifies how an agent (like Claude Code) works. - Danger lives on the tool. Each tool declares read-only / mutating / destructive, so the guard gates by real blast radius, not by re-guessing intent.
- The guard fails closed. When it can't prove an action is safe, it refuses — the opposite of trusting a path name to contain "prod".
- Honest limits. The provider-aware checks complement (don't replace) checkov/trivy; the cost figure is an estimate; the guard is a strong safety net, not an absolute barrier — pair it with least-privilege cloud credentials (defence in depth).
| Env var | Effect |
|---|---|
ANTHROPIC_API_KEY |
required for the agent — the API key (billed per token). Not needed for scan or audit (only audit --explain uses it). |
TFFORGE_MODEL |
override the model (default claude-sonnet-4-5; claude-haiku-4-5 is ~3× cheaper) |
TFFORGE_MAX_COST |
stop the run before it exceeds this USD budget (e.g. 0.50) |
TFFORGE_AUDIT |
off to disable the audit file, or a path to redirect it |
NO_COLOR |
disable colored plan output |
tfforge scan <dir> [--json] [--fail-on info|low|medium|high|critical|none]
Deterministic, no LLM, no API key. Exits 1 when the worst finding is at or
above --fail-on (default high); --fail-on none is report-only. Example
GitHub Actions gate:
- run: go build -o tfforge . && ./tfforge scan ./infra --fail-on hightfforge audit <repo> [--json] [--html] [--out FILE] [--explain] [--top N] [--fail-on <sev>]
Walks the whole repo, analyzes every directory (module / environment), and produces a prioritized health report — worst-first, across five categories (security, version, best-practice, structure, variables), with a per-directory rollup and the detected cloud providers. Deterministic, no LLM, no key. Modes:
| Flag | Output |
|---|---|
| (none) | colored text report on stdout |
--json |
machine-readable, for CI (the full, uncapped finding list) |
--html [--out f] |
a self-contained, tabbed HTML report — one tab per category with counts, provider chips, light/dark theme, no external assets, no JS. Scales to big repos (caps ~50 findings per tab with an honest "top N of M — run --json for the full list" banner) |
--explain |
optional AI layer — per finding, a prose fix plus a real before/after HCL diff (your actual code → the fix), tailored to the repo's cloud. Sends the relevant .tf to the API with secrets masked; nothing is sent without --explain. Footer shows the call's token cost. Needs a key; degrades cleanly without one |
--top N |
how many findings to show in the text report (default 10) |
--fail-on <sev> |
exit 1 at/above a severity (default none — report-only) |
--explaincosts tokens (one batched API call for the whole report); every other mode is free.
# Fail the build only on High+ across the whole repo:
- run: go build -o tfforge . && ./tfforge audit ./infra --fail-on highPart of a DevOps portfolio, alongside opsforge (a policy-as-code DevOps workstation) and KubeForge (a local-first Kubernetes analysis app). MIT · © Mrg77.