Airoverflow logo
Back to Blog
Blog2026-07-17AirOverflow

How Not to Write Insecure Infrastructure-as-Code

Congratulations, you've automated your own downfall

Infrastructure-as-Code promised us something beautiful: no more clicking around the AWS console like cavemen, no more "works on my staging environment" folklore, no more tribal knowledge locked in one engineer's head. Just write the desired state, commit it, and let the tool reconcile reality with your intentions.

It delivered on that promise. It also delivered a brand new way to compromise an entire organization with a single bad pull request.

Here's the thing nobody puts on the recruiting poster: IaC isn't configuration. It's code that happens to hold the keys to your entire kingdom. A bug in your application crashes a service. A bug in your Terraform module can quietly open your database to the internet, hand a CI pipeline AdministratorAccess, or leak your cloud credentials to whoever's watching the build logs. Same blast radius as a breach, minus the part where anyone actually broke in.

This post is a guide to not becoming next quarter's cautionary Hacker News post: what IaC actually is, how the major tools treat "infrastructure" as code in the first place, how it should be written and stored, and which tools will yell at you (kindly, in CI, before production) when you get it wrong.

Meet the cast: a field guide to IaC tools

Before we get into what not to do, a quick tour of who's doing it, because "IaC" isn't one tool, it's an entire ecosystem with strong opinions and occasional sibling rivalry.

The provisioning engines, the tools that actually stand infrastructure up:

  • Terraform, the reigning king. HCL syntax, provider agnostic, does everything, occasionally throws a tantrum about state file locking.
  • OpenTofu, Terraform's rebellious open-source sibling who moved out after a licensing disagreement in 2023 and has been doing just fine on its own.
  • Pulumi, for developers who looked at HCL and said "no thanks, I'll just use Python, TypeScript, or Go like a normal person."

The cloud-native control planes, the providers' "we'll just build our own, don't worry about it" offerings:

  • AWS CloudFormation and its developer-friendly cousin CDK
  • Azure ARM / Bicep
  • Google Cloud Infrastructure Manager

The Day-2 crew, configuration management, showing up after provisioning to argue about YAML indentation:

  • Ansible (agentless, Red Hat), Chef, Puppet, SaltStack

The orchestration overachievers:

  • Nomad job specifications, Kubernetes manifests and Helm charts, and Crossplane for teams who wanted a Kubernetes-native control plane over their cloud resources too.

The strict parents, governance and orchestration layers making sure everyone else behaves:

  • Terragrunt and Spacelift, for policy enforcement and drift detection at scale across whatever mix of the above you're running.

Each of these speaks a different language (HCL, YAML, JSON, or an actual general-purpose language), and that matters, because it determines what kind of mistakes are even possible. A YAML indentation error is an Ansible problem. Plan-time code execution through a malicious data source is a Terraform problem. Know your tool's failure modes.

The Ten Commandments of Not Shooting Yourself in the Foot

This is the part that should be boring. Boring is good. Boring means nobody's writing an incident postmortem about you. Consider these commandments, not suggestions:

  • Thou shalt not commit secrets to git, yes, even in a "private" repo. Nothing is private, it's just less public.
  • Thou shalt encrypt thy state file, for it knoweth all thy secrets. Terraform and OpenTofu state files can contain plaintext values of resources, sometimes including credentials. Use a remote backend, encrypt it at rest, and lock it during writes.
  • Thou shalt not grant *:* IAM permissions "just to get the pipeline working" and then forget about it for two years. Scope CI/CD service accounts tightly, and prefer short-lived OIDC-issued credentials over long-lived static keys.
  • Thou shalt pin thy module versions, lest latest become latest-disaster. Pin exact versions for third-party modules and audit anything you pull from a public registry before it touches anything that matters.
  • Thou shalt review infra PRs like they're a bomb disposal manual, because sometimes they are. A typo in an application PR might break a feature. A typo in an infra PR might delete a database or expose a subnet to the internet.
  • Thou shalt separate dev, staging, and prod credentials. Sharing them is basically infra polyamory, and it never ends well.
  • Thou shalt detect drift, because someone WILL SSH in and "just quickly fix something" outside the pipeline, and now your code no longer describes reality.

Hall of shame: mistakes we've all made (or watched someone else make)

A few classics, offered with love and zero judgment, because everyone's done at least one of these:

  • The security group that opens port 22 to 0.0.0.0/0, copy-pasted from a Stack Overflow answer, "temporarily," in 2021.
  • The .tfvars file with a hardcoded API key that has quietly outlived several employees.
  • The public storage bucket, inherited from a tutorial module, that nobody ever went back to lock down.
  • The CI pipeline so trusting it will happily execute code from a PR titled "fix typo."
  • Running terraform apply without reading the plan output first, also known as vibes-based infrastructure management.

None of these require a sophisticated attacker. They just require nobody looking closely enough, for long enough, which is exactly the failure mode good tooling exists to close.

Case studies: when it happens for real

The mistakes above are the everyday kind, the ones that sit quietly until an audit finds them. But 2026 has already given us several examples of IaC and pipeline trust being turned into full-blown compromises. Worth studying, because none of these required a nation-state budget.

The poisoned Terraform binary

Sometime in 2026, an attacker got hold of a legitimate Terraform binary used for infrastructure deployment and modified it, keeping its normal behavior intact while quietly embedding logic that reached into AWS IAM and STS to grab credentials. That altered binary was then re-uploaded to an internal Nexus artifact repository, replacing the real one. From that point on, every CI/CD pipeline pulling the "Terraform" dependency as part of normal build resolution fetched the poisoned version instead, because nothing about the request looked unusual: it was still the trusted artifact, just not the trusted code. Once executed, the binary quietly captured the AWS access token from the Terraform execution context and shipped it out to infrastructure the attacker controlled.

The uncomfortable part of this one is how ordinary it looked from the inside. No phishing email, no obviously malicious pull request, just a dependency resolving exactly the way it was supposed to. This is the case that most directly justifies pinning exact versions and verifying checksums on anything your pipeline treats as trusted, including the provisioning tool itself.

Read more: CI/CD Security: Supply chain attack from a compromised developer, RiskInsight

The Trivy to KICS cascade

Between March 19 and 21, 2026, attackers used a compromised credential to force-push malicious code into Trivy's GitHub Action tags and related pipeline integrations. Because Trivy is embedded in so many CI/CD pipelines as an early scanning step, that tampered artifact moved straight through trust boundaries nobody had reason to question and executed inside downstream builds, harvesting and exfiltrating whatever credentials and tokens those runners had access to. The attackers didn't stop at code: they also stole npm publishing tokens, letting them push further malicious packages while looking like legitimate maintainers, which is what let the campaign spread on its own rather than needing to be re-launched by hand.

The credentials pulled from that Trivy compromise were then used to authenticate as a maintainer of KICS, the IaC-focused scanner from Checkmarx that exists specifically to catch misconfigurations in Terraform, CloudFormation, and similar formats. The same force-push technique was applied to KICS and stayed live for close to four hours before a user noticed something was wrong and filed a GitHub issue that triggered a takedown.

Sit with that for a second: a tool whose entire job is to secure your infrastructure-as-code became, for a few hours, part of the attack surface. If your security scanning pipeline runs with any elevated trust, that trust is now something an attacker will go after directly rather than trying to route around.

Read more: Trivy Supply Chain Attack Triggers Self-Propagating CI/CD Compromise, IANS Research and TeamPCP: Cascading Supply Chain Assault via Developer Security Tooling, Cloud Security Alliance

The over-permissioned pull request

Researchers at Novee Security disclosed a pattern, codenamed Cordyceps, describing CI/CD workflows that grant pull requests more privilege than a PR should ever have. Because an untrusted PR can trigger a privileged workflow, something as small as a comment on a pull request was enough, in one documented case on a major software company's repository, to run anonymous attacker code inside that company's CI and steal a long-lived GitHub App key that never expired. The scan behind this research found the same class of flaw across hundreds of high-impact repositories, at organizations most engineers would assume have this figured out.

What makes this pattern hard to catch with a scanner is that every individual piece of the workflow is doing exactly what it was configured to do. The vulnerability lives in the composition, an untrusted input crossing a trust boundary that nobody sat down and audited on purpose. It's a strong argument for why "least privilege for CI/CD identities" belongs on every checklist in this post, not just the ones about Terraform state and IAM roles.

Read more: Cordyceps CI/CD Flaws Expose 300+ GitHub Repositories to Supply-Chain Attacks, The Hacker News

The toolbox: scanners, linters, and other things that judge your code

The good news: you don't have to catch all of this by eye. There's a solid ecosystem of tools built specifically to scan IaC before it ever reaches apply.

Static and config scanners (pre-deployment)

Trivy started as a container and dependency scanner, then absorbed tfsec's entire rule library after tfsec's deprecation. It's now a genuine Swiss Army knife: Terraform, Kubernetes, CloudFormation, Bicep, Helm, secrets scanning, and container images, all in one tool. If you were on tfsec, this is where you migrate to.

scan-with-trivy.sh
# Scan a Terraform directory for misconfigurations
trivy config ./infrastructure/terraform

# Include secret scanning in the same pass
trivy fs --scanners config,secret ./infrastructure

Checkov is Python-based, with over 1,000 built-in policies covering CIS benchmarks, PCI DSS, and GDPR, across Terraform, CloudFormation, Kubernetes, ARM, Docker, and Ansible. It quietly checks you against half a dozen compliance frameworks while you weren't looking.

scan-with-checkov.sh
pip install checkov

# Scan a directory and fail CI on any HIGH or CRITICAL finding
checkov -d ./infrastructure/terraform --compact --quiet

KICS, from Checkmarx, is the completionist of the group, supporting 20+ IaC formats including Terraform, Pulumi, Ansible, CloudFormation, Kubernetes, Helm, and Docker Compose, with 2,400+ Rego-based queries. Its open-source release cadence has slowed, but it's still actively maintained as the engine behind Checkmarx One. Check the release history before betting a new pipeline on it.

scan-with-kics.sh
docker run -v "$(pwd)":/path checkmarx/kics:latest \
    scan -p /path/infrastructure -o /path/results

TFLint is the pedantic friend who won't let your Terraform syntax slide. Great paired with a broader scanner, less useful alone.

Terrascan is, unfortunately, retired. Tenable archived the project in November 2025. Don't build a new pipeline on a tombstone.

Snyk IaC is commercial, polished, with solid Terraform, Kubernetes, and CloudFormation coverage and good remediation guidance. It comes with a bill.

Policy-as-code and gating

  • OPA (Open Policy Agent) with Rego, the general-purpose policy engine several of the tools above are quietly built on top of.
  • HashiCorp Sentinel, plan-time policy enforcement that evaluates the actual planned changes rather than just the static config. Available on Terraform Cloud and Enterprise only.
  • Spacelift and Terragrunt, orchestration-layer tools for enforcing scan gates and drift detection across multiple IaC engines at once.

Live-environment checks (after the fact)

Prowler and Kubescape verify what actually got deployed against what you intended, since static analysis alone can't catch drift or runtime misconfiguration.

A practical starting point: pick one provisioning engine and pair it with one scanner as a baseline. Terraform plus Checkov is a common combo, or go with a KICS-based pipeline if you're running a mix of Terraform, Ansible, and Nomad together and want one tool covering all of it.

Building a pipeline that actually stops things

Scanning tools only help if they sit somewhere in the pipeline that can actually block a bad change:

  1. Pre-commit hooks: catch obvious mistakes before they even become a pull request.
  2. CI-time scanning: the bouncer at the door, blocking merges on high-severity findings.
  3. Plan-time policy enforcement: the bouncer who also checks your ID against the guest list, evaluating the actual planned infrastructure changes, not just the static file.
  4. Post-apply drift detection: the bouncer who notices someone snuck a friend in through the back door after last call.

Skip any one of these layers and you've got a gap that someone, or something automated, will eventually find.

Pre-flight checklist

Before you let a change to production infrastructure merge, verify each of these:

  • Everything infrastructure-related lives in version control
  • No secrets committed anywhere, ever, use a secrets manager instead
  • State files encrypted, remotely stored, and locked during writes
  • Least-privilege IAM for every CI/CD identity, short-lived over static
  • Module versions pinned, third-party modules audited before use
  • Infra PRs reviewed with the same rigor as a production database migration
  • Dev, staging, and prod credentials fully separated
  • Drift detection running on a schedule
  • A scanner (Trivy, Checkov, or KICS) gating CI before merge
  • Plan-time policy enforcement (Sentinel or OPA) for anything touching prod
  • Post-apply verification against what was actually deployed

If any box is unchecked, stop and fix it before the next apply.

Closing thought

IaC will do exactly what you tell it to, at scale, forever, without asking a single follow-up question. That's either the best or the worst thing about it, depending entirely on how well you worked through the checklist above.


Looking to get your infrastructure as code reviewed? Or just want to check if your infrastructure is secure? Set up a meeting with AirOverflow.

Get the next one the day it drops.

New research, advisories, and deep dives from the AirOverflow team, straight to your inbox. No spam.