< psritej.com / blog />

A Comparative Evaluation of AI Coding Assistants in Infrastructure-as-Code Workflows

Sritej Panchumarthi · Published: May 5, 2026 · Updated: August 8, 2026 · Technical Report · 60 min read

Abstract
Large Language Models have permeated software development, yet their efficacy in specialized domains like DevOps and cloud security remains under-explored. This study benchmarks leading AI assistants against real-world Infrastructure-as-Code tasks: Terraform module generation, IAM automation, and Kubernetes policy authoring. We document a significant "Confidence-Competence Gap" — a high incidence of hallucinated resource arguments and insecure default configurations delivered with uniform fluency. We then present the complete countermeasure: an L3 governance architecture that wraps assistants in mechanical validation, corrected reference implementations for every failure we found, constraint-based prompt patterns, and a reusable evaluation harness so teams can benchmark assistants against their own stack rather than trusting anyone's leaderboard.
Key takeaway: AI assistants are excellent accelerators for boilerplate, examples, and review checklists. They are not reliable authorities for provider-specific infrastructure behavior, IAM blast radius, or production security decisions unless paired with automated validation and human review. The premium skill has shifted from writing code to auditing it.

1. Introduction

The promise of AI-assisted coding is increased velocity. However, in the context of DevSecOps, velocity without verification is a liability. Unlike general-purpose application logic — where a bug throws an exception in staging — errors in Infrastructure-as-Code can mean an internet-exposed database, a leaked credential in build logs, or a five-figure cost overrun, all deployed at automation speed.

This report documents a six-month longitudinal study evaluating the reliability of AI assistants on staff-engineer-level infrastructure problems, followed by the governance architecture we now use to capture the productivity while containing the risk.

2. Methodology

We defined three task categories, each run repeatedly across assistants and scored on four axes — syntactic validity (does it parse?), functional validity (does it apply/deploy?), security posture (what would a pentester say?), and self-awareness (did the model flag its own uncertainty?):

  • Task A (IaC): Generate a multi-AZ AWS EKS cluster in Terraform with IRSA (IAM Roles for Service Accounts) enabled.
  • Task B (Security): Write a Python/boto3 script to rotate IAM access keys for users older than 90 days.
  • Task C (Orchestration): Create a Kubernetes NetworkPolicy isolating a frontend namespace.
TaskSyntactic validityFunctional validitySecurity postureFlagged own uncertainty?
A — Terraform EKS + IRSAHighLow (hallucinated arguments)Medium (public endpoint defaults)Never
B — IAM key rotationHighHighLow (secret logging)Never
C — NetworkPolicyHighHighHighN/A

3. The "Confidence-Competence" Gap

The biggest risk with AI assistants isn't that they are wrong — it's that they are confidently wrong. A human expert hedges when uncertain; a model delivers a hallucinated provider argument in the same authoritative tone as a textbook answer. Fluency stops being a signal of correctness, which quietly breaks the heuristics human reviewers have relied on for their entire careers.

Fig 1. Observed Assistant Reliability by Task Type — the Gap Widens with Context Depth
 Reliability
 (functional + secure)
   ▲
   │ ██ Boilerplate Python / Bash          ◄─ stable syntax,
   │ ██ Unit test scaffolding                 massive corpus
   │ ██ SQL queries · regex · conversions
   │ ▓▓ Kubernetes manifests               ◄─ stable schema,
   │ ▓▓ Dockerfiles                           well documented
   │ ▒▒ Terraform (current providers)      ◄─ corpus spans many
   │ ▒▒ Helm charts w/ deps                   incompatible versions
   │ ░░ IAM policies (blast radius)        ◄─ requires reasoning
   │ ░░ Cross-service security wiring         about YOUR context,
   │ ░░ Multi-resource failure modes          which model cannot see
   │ ·· Production debugging
   │ ·· Cost/perf tradeoff decisions
   └────────────────────────────────────────────────► Task depth
        ▲                                    ▲
        │ SAFE: generate, then lint          │ DANGER ZONE: generate,
        │                                    │ then EXPERT review +
        │                                    │ mechanical validation
   Confidence expressed by model: ████████ UNIFORM across all rows —
   that flat line IS the confidence-competence gap.

4. Case Study A: Terraform Hallucinations

Objective: Create an AWS EKS cluster module with IRSA enabled.

Observation: Assistants repeatedly emitted arguments that were deprecated or nonexistent in AWS Provider v4.0+:

# AI-generated (hallucination)
resource "aws_eks_cluster" "main" {
  name = "my-cluster"
  # This argument does not exist on this resource!
  enable_irsa = true
}

Root cause analysis: The training corpus mixes outdated Terraform (0.11/0.12 era), the popular community module terraform-aws-modules/eks (where enable_irsa is a valid input), and raw provider documentation. The model statistically conflates the module's interface with the raw resource's schema — producing code that is syntactically fluent and functionally invalid. terraform validate catches it in two seconds; a tired reviewer at 5 p.m. does not.

The corrected reference implementation — what IRSA actually requires (an OIDC provider bound to the cluster's issuer URL):

# Correct: IRSA = OIDC provider wired to the cluster issuer
resource "aws_eks_cluster" "main" {
  name     = "my-cluster"
  role_arn = aws_iam_role.cluster.arn
  version  = "1.29"

  vpc_config {
    subnet_ids              = module.vpc.private_subnets
    endpoint_public_access  = false          # AI default was true
    endpoint_private_access = true
  }
}

data "tls_certificate" "oidc" {
  url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}

resource "aws_iam_openid_connect_provider" "eks" {
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [data.tls_certificate.oidc.certificates[0].sha1_fingerprint]
  url             = aws_eks_cluster.main.identity[0].oidc[0].issuer
}

# Per-service role example: scoped trust to ONE service account
data "aws_iam_policy_document" "app_trust" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type        = "Federated"
      identifiers = [aws_iam_openid_connect_provider.eks.arn]
    }
    condition {
      test     = "StringEquals"
      variable = "${replace(aws_eks_cluster.main.identity[0].oidc[0].issuer, "https://", "")}:sub"
      values   = ["system:serviceaccount:app:app-sa"]
    }
  }
}

Note what the hallucination concealed: IRSA is not a boolean — it is an OIDC federation design with a per-service trust condition. The one-line fake answer didn't just fail to work; it hid an entire security architecture the engineer needed to understand.

5. Case Study B: Security & IAM Risks

Objective: Write a Python script to rotate IAM access keys.

Critical finding: A leading assistant generated a script that logged the newly created secret key to stdout "for verification purposes":

# AI-generated (dangerous)
response = iam.create_access_key(UserName=user)
print(f"New Key Created: {response['AccessKey']['SecretAccessKey']}")  # ← SECURITY RISK

Security implication: Executed in CI (Jenkins, GitLab), the credential persists in plaintext build logs — readable by anyone with project read access, shipped to log aggregators, retained by backup systems. One generated line converts a routine rotation into a multi-system credential-exposure incident.

The corrected reference implementation — secrets flow only into Secrets Manager, with dry-run and old-key lifecycle handling:

import boto3
from datetime import datetime, timezone, timedelta

iam = boto3.client("iam")
secrets = boto3.client("secretsmanager")
MAX_AGE = timedelta(days=90)

def rotate(user: str, dry_run: bool = True) -> dict:
    keys = iam.list_access_keys(UserName=user)["AccessKeyMetadata"]
    actions = []
    for key in keys:
        age = datetime.now(timezone.utc) - key["CreateDate"]
        if age < MAX_AGE:
            continue
        actions.append(f"rotate {key['AccessKeyId'][:8]}… (age {age.days}d)")
        if dry_run:
            continue

        new = iam.create_access_key(UserName=user)["AccessKey"]
        # secret goes to the vault — NEVER to stdout, logs, or return values
        secrets.put_secret_value(
            SecretId=f"iam/{user}/access-key",
            SecretString=(
                '{"AccessKeyId":"%s","SecretAccessKey":"%s"}'
                % (new["AccessKeyId"], new["SecretAccessKey"])
            ),
        )
        # deactivate (don't delete) the old key: consumers get a grace
        # window to pick up the new secret; deletion is a later, separate job
        iam.update_access_key(UserName=user,
                              AccessKeyId=key["AccessKeyId"],
                              Status="Inactive")
    return {"user": user, "dry_run": dry_run, "actions": actions}

Every difference between the two versions is a security decision the assistant silently made wrong: where secrets go, whether old keys die instantly (breaking running consumers) or gracefully, and whether the operator can preview actions before execution.

6. Case Study C: Kubernetes Manifests — Where Models Shine

Objective: Create a NetworkPolicy denying all ingress except from the "frontend" namespace.

Result: High proficiency across assistants — correct use of matchLabels, namespaceSelector, and the subtle deny-by-default semantics (an empty ingress array vs. an absent one). The lesson generalizes: AI models excel at declarative configuration with stable, versioned, well-documented schemas. The Kubernetes API changes deliberately and is documented exhaustively; Terraform providers change monthly across a fragmented corpus. Schema stability predicts assistant reliability better than task "difficulty" does.

7. The Governance Framework — L3 View

Based on these findings, we operate AI assistants inside a layered control architecture. The insight: you cannot review your way out of hallucinations at scale — you must make validation mechanical, and reserve human attention for what machines cannot check (blast radius, intent, context).

Fig 2. L3 Governance Architecture — AI-Generated Code from Prompt to Production
 ┌─ POLICY BOUNDARY: what may enter a prompt ──────────────────────┐
 │  ✗ secrets/tokens   ✗ PII   ✗ internal network maps             │
 │  ✓ enterprise instance · retention opt-out · audit logging      │
 └──────────────────────────────┬──────────────────────────────────┘
                                ▼
        Developer + AI assistant (IDE / chat)
        constraint-rich prompt (see §8): pinned versions,
        security requirements, demanded validation steps
                                │  generated code
                                ▼
 ┌─ TIER 1 · MECHANICAL (pre-commit, seconds) ─────────────────────┐
 │  terraform validate · tflint --minimum-failure-severity=error   │
 │  kubeconform -strict · pylint/ruff · gitleaks                   │
 │  CATCHES: hallucinated args, schema errors, planted secrets     │
 │  (Case A dies here, 2 s after being written)                    │
 └──────────────────────────────┬──────────────────────────────────┘
                                ▼
 ┌─ TIER 2 · SEMANTIC (CI, minutes) ───────────────────────────────┐
 │  checkov / tfsec  → public exposure, unencrypted storage        │
 │  semgrep custom rules → secret-logging patterns:                │
 │     "print.*SecretAccessKey" · "console.log.*password"          │
 │  (Case B dies here — mechanically, every time)                  │
 │  OPA policies → org rules AI cannot know (tagging, regions,     │
 │     approved module registry only)                              │
 └──────────────────────────────┬──────────────────────────────────┘
                                ▼
 ┌─ TIER 3 · HUMAN (MR review, focused) ───────────────────────────┐
 │  Reviewer attention is now SPENT ONLY on what machines can't:   │
 │  · blast radius: "what can this role/SG actually reach?"        │
 │  · intent match: "is this what the ticket asked for?"           │
 │  · context: "does this respect OUR failure domains?"            │
 │  MR template flags: [x] contains AI-generated code              │
 │  → routes to reviewer with domain expertise, not rubber stamp   │
 └──────────────────────────────┬──────────────────────────────────┘
                                ▼
                    plan/apply · progressive rollout
                    (same pipeline as human-written code —
                     provenance recorded, no separate lane)
  1. Zero-trust code adoption: AI-generated code undergoes the same rigor as code from a public forum. It is not "correct by default" — it is plausible by default, which is worse.
  2. Data-leakage prevention: strict prohibition on secrets, PII, or internal network maps in prompts; enterprise instances with retention opt-outs.
  3. Automated validation: linters and policy scanners in pre-commit and CI catch hallucinated syntax and insecure patterns mechanically (Tiers 1–2 above).
  4. The "junior engineer" heuristic: treat output as the work of a talented but inexperienced engineer — syntactically fluent, zero security intuition, no memory of your environment.

8. Prompt Patterns That Work Better for DevOps

The highest-quality outputs came from prompts that force the assistant to operate inside explicit constraints: provider versions, security requirements, failure criteria, and validation commands. A vague prompt samples from the model's entire mixed-quality training distribution; a constrained prompt collapses that distribution toward current, secure, reviewable output.

Weak promptBetter prompt
Create an EKS cluster in Terraform.Create an EKS cluster using AWS provider v5, no public endpoint, IRSA enabled through an OIDC provider (raw resources, not the community module), managed node groups with IMDSv2 required, and include validation commands with terraform validate and tflint.
Write an IAM rotation script.Write a boto3 IAM access-key rotation script that never logs secrets, stores the new secret only in Secrets Manager, deactivates (not deletes) old keys, supports dry-run mode, and emits CloudWatch metrics.
Make a Kubernetes NetworkPolicy.Generate a deny-by-default NetworkPolicy and a separate allow rule from namespace label app=frontend, then explain how to test it with a temporary pod.
Review checklist for AI-generated infrastructure code:
  • Pin provider and module versions before trusting syntax.
  • Run linters and schema validators before reading the code deeply — don't spend human attention on what a machine checks in seconds.
  • Search for accidental secret logging, wildcard IAM permissions, and public network exposure.
  • Ask the model for failure modes, then verify them independently.
  • Treat generated explanations as hypotheses, not documentation.

9. Hands-On Practical: Build Your Own Evaluation Harness

Leaderboards measure other people's tasks. Your stack has its own providers, versions, and conventions — so benchmark assistants against it. This harness (~2 hours to stand up) scores any assistant on your representative tasks, mechanically:

#!/usr/bin/env bash
# eval-harness/run.sh — score AI-generated IaC on YOUR stack
# Layout:  tasks/<name>/prompt.md        (what you asked)
#          tasks/<name>/response/        (what the model produced)
#          tasks/<name>/expected.txt     (assertions, optional)
set -uo pipefail
declare -A SCORES

for task in tasks/*/; do
  name=$(basename "$task"); cd "$task/response"
  s=0

  # Gate 1 — parses at all (catches Case-A hallucinations)
  if ls *.tf &>/dev/null; then
    terraform init -backend=false -input=false &>/dev/null
    terraform validate &>/dev/null            && s=$((s+25))
    tflint --minimum-failure-severity=error   && s=$((s+15))
    checkov -d . --compact --quiet            && s=$((s+25))
  fi
  if ls *.yaml &>/dev/null; then
    kubeconform -strict -summary *.yaml       && s=$((s+40))
  fi
  if ls *.py &>/dev/null; then
    ruff check . &>/dev/null                  && s=$((s+20))
    # Gate 2 — the Case-B detector: secrets in output paths
    ! grep -rE "print.*(Secret|Password|Token)" . && s=$((s+20))
  fi

  # Gate 3 — task-specific assertions (e.g. "endpoint_public_access = false")
  if [[ -f ../expected.txt ]]; then
    while read -r assertion; do
      grep -rq "$assertion" . && s=$((s+5)) || echo "  MISS: $assertion"
    done < ../expected.txt
  fi

  SCORES[$name]=$s; cd - &>/dev/null
done

echo "== Assistant scorecard =="
for k in "${!SCORES[@]}"; do printf "%-40s %s/100\n" "$k" "${SCORES[$k]}"; done
  1. Write 5–10 prompt.md tasks that mirror your actual work (your provider versions, your module registry, your naming rules).
  2. Paste each prompt into the assistants you're evaluating; save outputs under response/.
  3. Add expected.txt assertions for the security properties that matter to you (endpoint_public_access = false, metadata_options, no 0.0.0.0/0).
  4. Run the harness. Re-run quarterly — assistants change under you without release notes.

Teams that run this exercise stop arguing about which assistant is "best" in the abstract and start knowing which one fails their Gate 2 least often — which is the only question that matters.

10. The Pros and the Cons — Comprehensive, With Evidence

Six months of scored tasks, three case studies, and a governance rollout produce a ledger that is more nuanced than either the vendor decks or the doomer threads. Every entry below cites where in this study the evidence lives, and — for the cons — which governance tier neutralizes it. That last column is the point: a con without a mitigation is a risk; a con with a mechanical mitigation is a line item.

10.1 The Pros

ProEvidence from this studyBoundary condition
Boilerplate velocity. Scaffolding, test skeletons, CI YAML, argument parsing — 30–60% cycle-time reduction on tasks whose shape is knownTask-category scores in §2; every syntactically-valid column is HighGains concentrate in the first 80% of a file; the last 20% (wiring into your system) is unchanged
Schema-stable generation is genuinely excellent. Kubernetes manifests, Dockerfiles, SQL, regex, jq — near-zero defect ratesCase C (§6): correct namespaceSelector and deny-by-default semantics across every assistant testedOnly holds where the schema is versioned, documented, and stable — the exact opposite of the Terraform provider ecosystem
Review-checklist and failure-mode brainstorming. "List ways this rollout can fail" reliably surfaces 1–2 items a tired human forgotUsed throughout §7's Tier-3 review guidance; the checklist in §8 was itself assistant-drafted, then human-verifiedOutput is a hypothesis generator, not an authority — every item needs independent verification
Format conversion and translation. JSON↔YAML↔HCL, bash→Python, docs→runbook — one-shot accuracy on mechanical transformsConversion tasks scored top-band reliability in Fig 1Semantic drift sneaks in on lossy conversions (comments dropped, defaults materialized) — diff before trusting
On-demand explanation of unfamiliar code. Pasting a gnarly IAM policy or awk one-liner yields a competent first-pass explanation in secondsUsed as the entry point for the §5 root-cause analysesExplanations are fluent even when wrong — treat as orientation, verify against provider docs
Leveling effect for juniors. Constraint-rich prompting (§8) plus mechanical validation gives early-career engineers senior-shaped output to studyGovernance framework adoption (§7) showed strongest per-engineer gains at the junior tierOnly with the guardrails; without them, juniors are the population least equipped to catch the failure modes

10.2 The Cons

ConEvidence from this studyMitigation (and where it lives)
Hallucinated provider arguments — fluent, plausible, nonexistentCase A (§4): enable_irsa on a raw resource, repeatedly, across assistantsTier 1 mechanical validation: terraform validate + tflint kill it in seconds (§7)
Silently insecure defaults — public endpoints, logged secrets, wildcard IAMCase B (§5): a new secret key printed to stdout "for verification"Tier 2 semantic scanning: checkov, semgrep secret-logging rules, OPA org policies (§7)
Uniform confidence regardless of correctness — fluency stops signaling competence, breaking reviewer heuristics§3: the flat confidence line across every reliability band in Fig 1; "flagged own uncertainty: Never" in §2's tableCannot be fixed at the model; must be absorbed by process — mechanical gates plus the junior-engineer heuristic (§7)
Training-data staleness — corpora lag fast-moving providers by months to years§4's root cause: three incompatible Terraform generations statistically blendedPin versions in prompts (§8); re-run the §9 harness quarterly because assistants change under you
Context blindness — the model has never seen your failure domains, naming rules, or compliance scopeFig 1's danger zone: reliability collapses exactly where organizational context is requiredTier 3 human review spends attention only here: blast radius, intent, context (§7)
Data-leakage surface — every prompt is an egress channel for secrets, PII, and network topologyPolicy boundary in Fig 2; the governance framework's first control (§7)Enterprise instances, retention opt-outs, prompt hygiene rules, audit logging
Review burden shifts, not shrinks — unaudited generation moves the cost from writing to reviewing at a worse exchange rateThe §2 scores imply it: high syntactic validity with low functional validity is precisely the most expensive code to reviewMake validation mechanical so human review is narrow (§7); measure gate throughput, not lines generated
Skill-atrophy risk — engineers who only audit eventually lose the ability to auditObserved qualitatively during the study; the §9 harness exists partly to keep evaluation skills sharpRotate engineers through from-scratch builds; require the corrected reference implementation, not just the fix

10.3 The Net Assessment

The ledger is asymmetric in an instructive way: every pro compounds with team maturity, and every con is amortized by it. A team with linting, policy scanning, and review culture already in place captures the velocity at close to zero marginal risk — the failure modes documented here die in machinery that already exists. A team without those controls gets the same fluent output and inherits every failure mode at production blast radius. The assistant is a multiplier on your existing engineering discipline; multiplying a negative makes it worse.

11. FAQ

Why do assistants hallucinate Terraform arguments?
The corpus blends incompatible generations of HCL, community modules, and provider versions; the model statistically merges them into fluent, invalid code. Schema-stable domains (Kubernetes) don't trigger this failure nearly as often.

What is the confidence-competence gap?
Model confidence is flat across task types while competence varies wildly — so fluency can't be used as a correctness signal. Validation must be mechanical.

What are assistants reliably good at?
Stable-schema declarative config, boilerplate, tests, SQL, conversions. Reliability collapses where current-version knowledge or your private context is required.

What's the core of the governance framework?
Three tiers: mechanical validation (seconds), semantic scanning (minutes), and human review focused exclusively on blast radius, intent, and context. Plus a policy boundary on what may enter prompts.

Do better prompts really help?
Measurably. Pinned versions, explicit security requirements, and demanded validation steps collapse the output distribution toward current and secure. §8 has the before/after patterns.

Does this make engineers less necessary?
It shifts the premium from writing to auditing. Generating a plausible module takes seconds; knowing its node group lacks IMDSv2 enforcement still takes an engineer.

What's the one-paragraph pros/cons verdict?
Pros: 30–60% faster boilerplate, near-perfect schema-stable output, instant checklists and conversions. Cons: hallucinated arguments, insecure defaults, flat confidence, staleness, context blindness, and a prompt-shaped leakage surface. The full evidence ledger is in §10 — the net is positive exactly in proportion to the guardrails you already have.

12. Conclusion

AI coding assistants represent a genuine paradigm shift in developer productivity — and in the infrastructure domain, an equally genuine shift in where risk concentrates. The failures documented here are not exotic: a hallucinated argument, a logged secret, an insecure default, each delivered with perfect confidence. The remedy is not abstinence but architecture: mechanical validation tiers that catch what machines can catch, human review spent only where judgment is irreplaceable, prompts engineered as constraint systems, and a standing evaluation harness that measures assistants against your stack instead of a leaderboard. Success requires the shift from "writing code" to "auditing code" — placing a higher premium than ever on the engineer's fundamental understanding of the underlying systems.

Related Writings