Every IaC scanner I have used is great at one thing and useless at the next. It tells you what is wrong. Then it walks away.
You run it, 200 findings land in a report, a ticket gets filed, and three sprints later the same misconfiguration is still in production. Two gaps stay open the whole time. Nobody understands why finding number 147 actually matters, and even when the fix is obvious, somebody still has to write it and prove it closed the hole.
I wanted a tool that closes both gaps without sending my infrastructure code to anyone. So I built one, and last week I shipped v0.2.0.
The loop
policy-as-code-ai runs a closed loop over an Azure Terraform plan:
terraform plan -> OPA/Rego scan -> local LLM explains each violation
-> deterministic fix -> re-scan proves 0 violations
Every step is boring on its own. Put together, they turn a raw policy failure into a verified fix, and the last step is the one I care about most: the tool does not just claim it fixed things, it re-runs the same gate on the patched code and shows you the count drop to zero.
Step one: find the problem, in plain Rego
The detection is Open Policy Agent. Each rule is a small Rego policy that reads the plan and emits a structured violation. Here is the one for a storage account left open to the public internet:
# Rule AZ-STORAGE-003: deny public network access
deny contains {
"resource": resource.address,
"rule": "AZ-STORAGE-003",
"severity": "high",
"message": "Storage account has public network access enabled. Set 'public_network_access_enabled' to false."
} if {
some resource in storage_accounts
resource.change.after.public_network_access_enabled == true
}
There are 21 of these across 10 Azure services: Storage, NSG, Key Vault, SQL, App Service, Managed Disk, Cosmos DB, AKS, Container Registry, Log Analytics.
Step two: explain the risk, locally
A finding like AZ-STORAGE-003 means nothing to someone shipping a feature under a deadline. So a language model turns each violation into two or three sentences of real-world risk plus a verification hint, and it runs on my machine through Ollama. Nothing about the plan leaves the box.
That last part is the whole point. Your Terraform encodes your network topology, your resource names, your security posture. I did not want any of that going to a third-party API just to get a sentence of explanation back. The backend is pluggable if you disagree (Azure OpenAI and Anthropic are one env var away), but the default keeps everything private, and there is no per-review token bill.
Step three: fix it, without hallucinating
Here is the line I hold firmly: the model explains, it never writes the fix. Remediation comes from a deterministic map, one attribute change per rule, applied only inside the resource that was actually flagged.
{
"id": "AZ-STORAGE-003",
"severity": "high",
"fix": { "attribute": "public_network_access_enabled", "value": "false" },
"doc": "https://learn.microsoft.com/azure/storage/common/storage-network-security"
}
That is worth pausing on. If an LLM writes your HCL, you get plausible code that might be subtly wrong, and you have to review every line as if a junior did it at 3am. A lookup table cannot hallucinate a subnet. It covers the unambiguous cases (set TLS to 1.2, disable public access) and stays out of the ones that need judgment. An NSG rule that allows any inbound source has no single correct CIDR, so the fix there is fail-closed: set it to Deny and let a human scope it properly.
Step four: prove it
This is the part most tools skip. After the fix is written, the patched Terraform gets re-scanned against the same policies:
$ make verify
Verifying remediated Terraform against the policy gate...
✓ Proof: remediated Terraform passes the policy gate with 0 violations.
A clean re-scan is evidence, not a vibe. It is the difference between "the AI says it fixed it" and "the gate that failed a minute ago now passes." If you read my last piece on a supply-chain pipeline that refuses to run code it can't prove it built, you will recognize the pattern. I keep coming back to the same idea: do not trust the change, prove it.
What v0.2.0 actually changed
The first version proved the concept. This release is the unglamorous work that makes it usable.
One source of truth. A rule used to be defined in five places: the Rego policy, the remediation map, the SARIF exporter, the docs links, and the CI comment. They drifted. Now every rule lives once in rules.json, everything else derives from it, and a test fails CI the moment a policy and the catalog disagree. Adding a rule is two edits, not five.
Fixes that stay in their lane. The old remediation matched an attribute name anywhere in the file. If two storage accounts shared a setting, it could patch the wrong one. Now it parses the block and only touches the resource that was flagged.
Try it in 30 seconds, no cloud account. There is an offline mode that scans a bundled plan with just OPA:
$ make scan-offline
Offline mode: scanning examples/insecure_plan.json (no terraform, no Azure).
Summary: 21 violation(s).
No terraform, no az login, no Azure subscription. Clone and run.
CI that keeps the promise
The GitHub Action stays cloud-free and LLM-free on purpose. It scans a committed plan fixture, uploads the violations as SARIF so they show up in the repo's Security tab with real line numbers, and posts a PR comment listing each violation and its deterministic fix. The AI explanations remain a local step, so the "your code never leaves your machine" guarantee holds even in CI.
What I took away
The satisfying part was not the AI. It was drawing a hard line around it. The model does the one thing it is genuinely good at, explaining, and a deterministic engine does the thing you cannot afford to get wrong, changing your infrastructure. The proof step ties it together and makes the whole thing something you can actually trust in a pipeline.
It is MIT. Clone it, run make scan-offline, and tell me where it breaks.
Top comments (0)