RELEASE #076 · AUG 06, 2026 GRC ENGINEERING GRC AS A PRODUCT SYSTEMS THINKING · 14 MIN READ

⚙️ The Definitive GRC Engineering Guide to Infrastructure as Code

Your terraform repo is the best auditor you never hired. What it can prove, where it lies, and how to read it without losing the platform team.

Last week I promised you denominators: coverage needs a true asset inventory, and the inventory has to come from somewhere the control can't edit.

Watch what happens when an auditor asks a scale-up for its asset inventory. The CMDB says 1,400 assets. The CSPM says 2,300. The finance tagging report says 1,900. Then a platform engineer shares a repo URL and says: "everything we run is declared in here, every change went through a pull request, and the pipeline refuses anything that doesn't."

The repo's number is probably the smallest of the four, because nothing lands in it without being declared. It is still the artifact that settles the argument: continuously maintained, reviewed on every change, versioned with a tamper-evident history, and the only one of the four that records intent. It is also the only one GRC never reads.

That repo is the closest thing your company has to a machine-readable statement of intent for its infrastructure. This issue is about what that intent is worth, what it can never tell you, and how to put your hands on it without becoming the team everyone routes around.

In this guide:

  1. The 101, for readers who have never opened a terraform file

  2. Four evidence classes you get for free

  3. The declaration ladder, where the repo stops telling the truth

  4. Four enforcement layers and the tools that run them

  5. Whether this applies at your stage, and what AI agents change

  6. How to do all of it without losing the platform team

The 101: infrastructure became text

Live in terraform already? Skip ahead.

Before infrastructure as code, infrastructure lived in two places: the cloud console where someone clicked it into being, and the memory of whoever clicked. The estate was running state plus tribal knowledge.

IaC replaces the clicking with text. An engineer describes the desired infrastructure in configuration files (terraform's language is called HCL), and a tool compares that description with what actually exists, then changes reality until the two match. The loop has four beats, and they recur through this piece:

  1. Declare: write the desired state in code, in a git repo like any other software

  2. Plan: the dry run. The tool prints a diff of what it would create, change, or destroy

  3. Review: the plan goes into a pull request (a PR), where a colleague approves or objects

  4. Apply: the tool executes the plan, and reality matches the declaration again

Two more terms complete the vocabulary. The state file is terraform's private ledger of what it currently manages. Drift is what happens when reality changes without the code changing, usually because someone clicked.

That mechanical loop carries the whole reason this guide exists: infrastructure became text. Text is reviewable, diffable, greppable, and version-controlled. And GRC is a discipline that has always known how to read.

Four kinds of evidence, already version-controlled

You don't have to build anything to start. Reading access to the infrastructure repo gives your program four evidence classes on day one:

Evidence class

What the repo gives you

What it replaces

Asset inventory

The declared estate: every resource, owner, environment, continuously maintained

The CMDB reconciliation project that never ends

Change management

Every PR: diff, reviewer, approval, timestamp, CI results

Screenshot archaeology at audit time

Control configuration

Encryption, network exposure, retention, IAM boundaries, logging, all declared in code

The questionnaire where someone attests from memory

Drift signal

Divergence between declared and actual state, as a diffable event

The annual finding that a control quietly died in March

The second row is special. A merged PR is a direct observation of change control operating: real reviewer, real diff, real timestamp. Most evidence attests that a process exists. This row shows one running, thousands of times, with receipts.

Your change policy probably describes tickets and approval boards. Engineering already runs a stricter process than the policy requires. Policy-FROM-code starts exactly here: recognize the change process the repo already enforces, and write the policy from it.

And the denominator problem from last week dissolves for the managed estate. Every count you run against the repo is a denominator the control did not produce. Compliance becomes a query on data engineering already collects.

The declaration ladder

Now the honest part, because this is where most -as-code enthusiasm dies of overreach.

Declared is not applied. Applied is not running. Running is not effective.

A terraform file is a claim about intent. Between that claim and a working control sit three gaps, and each one leaks:

The leaks are measurable

The leaks are not hypothetical. Datadog's State of DevSecOps report (2024) measured CloudTrail telemetry across its own AWS-using customer base and found that at least 38% of organizations performed manual sensitive actions in production within a single 14-day window, including organizations that also run IaC. Firefly's State of IaC 2025 survey puts roughly 40% of infrastructure outside IaC management entirely, with fewer than a third of teams monitoring drift continuously. Vendor survey caveats apply to the second number. The first is observational, and it should end any fantasy that the repo describes reality.

One more leak hides in plain sight: the state file. terraform state rm makes a resource vanish from drift detection while it keeps running, and import retroactively blesses a console creation. The repo is independent of the control's runtime, but it is written by the control's owners. Which is why the actual-state reading comes from the cloud API instead of terraform's own bookkeeping.

Evidence is two readings

Rank the sources honestly: on coverage and freshness the cloud API wins, and always will, because it sees the ClickOps estate too. The repo wins on exactly one axis, intent, and intent alone proves nothing. Evidence of a managed control is the product of two readings: what the repo declares, times what the cloud API observes. The gap between them is your finding, and it arrives as a diff instead of an annual surprise. This also answers the auditor's completeness question better than any CMDB attestation, and better than the 0.07% sample your certification calls coverage: the declared-versus-observed diff is the completeness check. The gates themselves, branch protection, CODEOWNERS, bypass lists, are declared configurations you can read. Evidence about the evidence pipeline.

That is why detection survives full declaration: detection audits the declaration. Even with everything declared and every gate perfect, you still need the outside reading, because the threat model includes the pipeline itself. And configuration diffing is only one sense of detection. Credential use, data-plane access, and runtime behavior never appear in a plan file; that layer of detection watches what declaration cannot even express.

Where you can put your hands on it

Requirements go into the toolchain at four layers, and the layers are not interchangeable:

Layer

Fires

Can stop

Cannot see

Module standards

authoring time

insecure defaults ever being typed

anything built outside the module

Plan-time policy

on the PR

violations before they exist

drift, out-of-band change

Apply-time gates

at deployment

unapproved applies

console changes

Runtime detection

continuously

nothing, it observes

intent behind the change

The named tools, so you can map your company's stack onto the layers:

Tool

Layer

Worth knowing

Sentinel

plan-time

native to HCP Terraform, easiest in an all-HashiCorp shop

OPA / Rego

plan-time

vendor-neutral, runs in most pipeline orchestrators

Checkov

PR scanning

static rules out of the box, no policy language needed

Trivy

pre- and post-deploy

absorbed tfsec in 2023

Drift detection

runtime

orchestrator-native or via CSPM

The two moves worth making first

The highest-ROI move is the first row of the layers table. A hardened module is a control engineers want, because it is less typing than the insecure version:

module "artifact_store" {
  source  = "registry.internal/platform/s3-hardened/aws"
  version = "~> 3.0"

  # encryption, versioning, access logging, deny-public:
  # inherited from the module, not remembered by the engineer
  owner      = "payments"
  data_class = "customer"
  retention  = "365d"
}

A hardened module without a named owner and an upgrade cadence decays into insecure-by-staleness, and the git blame points at you.

The second-highest is plan-time policy, where the control test runs before the risk exists:

package terraform.tagging

deny contains msg if {
  some r in input.resource_changes
  some action in r.change.actions
  action in {"create", "update"}
  r.type == "aws_s3_bucket"
  not r.change.after.tags_all.owner

  # the deny message is a UX surface: name the control,
  # the reason, and the fix, or engineers route around you
  msg := sprintf(
    "%s: no owner tag. Unowned assets have no denominator. Add owner = team.",
    [r.address],
  )
}

In English: anyone creating or changing a bucket without an owner tag gets told no, and told why. Input shape varies by harness and this is OPA v1 syntax, so check yours before copying. Note the rule reads tags_all, the merged view, because provider-level default tags never land in tags. And pick your battles from provider reality rather than framework language. The classic example, denying unencrypted S3 buckets, is a trap in 2026: AWS has encrypted new buckets by default since 2023, and from provider v4 onward the encryption config moved to a separate resource (exclusively so in v5) where your naive rule will never look. That is the top-down antipattern wearing new clothes: policy written from the framework's mental model of a cloud that no longer exists.

What you can and cannot embed

What can you embed this way? Tags and ownership, encryption, region residency, retention, network exposure, IAM boundaries, logging, backup policy. What can you not embed? Data classification correctness, whether the alert gets triaged, human process quality, the SaaS estate, and everything on a laptop. Write both lists down for your stack. The second list is your detection budget.

Does this apply to your company?

Stage

IaC reality

Your play

Startup

Partial IaC, ClickOps everywhere

Don't demand coverage. Seed one hardened module, with an owner.

Scale-up

Core infra is IaC, shadow estate growing

Read access, plan-time gates on the top 3 requirements, drift review with platform

AI-native

Agents write and apply infrastructure

The policy layer becomes your primary reviewable surface. Invest there first.

The third row deserves its own paragraph. Agents now generate terraform faster than humans can review it, and the vendors are moving past the repo entirely: Spacelift's Intent provisions infrastructure from natural language with no .tf files (a toolchain I work with daily, which is how I watch this up close), and HashiCorp's Project Infragraph builds a live graph of actual infrastructure state alongside the code, which I read as a second source of truth competing with the repo. When the artifact thins out, the evidence moves into orchestrator event logs, and the review moves up one level.

You stop reviewing changes and start reviewing the policies that review the changes.

Your plan/apply pipeline becomes the harness the agent runs inside, and the policy code is the part a human still reads. Almost nobody in GRC is writing about this shift. Your infrastructure colleagues are living it.

Working with the people who own the repo

Everything above fails if you show up as the team that serves the audit. And understand the loop this play lives or dies on. The repo is only a control-independent denominator because engineers maintain it for their own reasons: it is how they ship. The moment you gate on declaration and report coverage numbers upward, declaration acquires a compliance tax, and you have created the incentive to route around the very artifact your evidence depends on. The number to protect is the trend of the declared-to-observed delta, reviewed jointly with platform before it ever appears in a deck. If that delta widens after you show up, your program is manufacturing the blind spot it was built to close.

The repo tells you the truth exactly as long as nobody is punished for what it says.

One boundary keeps all of this healthy: reading the repo does not make you the control owner. Platform owns the controls and the fixes; risk owners own the decisions. Your layer is what makes their decisions possible: what the evidence means, which obligation it maps to, which gap actually matters. Start rewriting their terraform with a tenth of their context and you have stopped doing GRC to do worse engineering. The whole play runs behind security, not parallel to it: one pipeline, theirs, with GRC semantics added. You still work in GRC.

Four practices keep you welcome:

  1. Ask for read access, not control. Usually the easiest ask you will make, though secrets hygiene or restricted monorepos can complicate it. Enforcement comes later, through their pipeline.

  2. Open PRs, not tickets. A requirement that arrives as a pull request is a contribution; the same requirement in a spreadsheet is homework.

  3. Treat policy code as production code. Tests against fixture plans, a warn-mode soak before any deny, a named owner for the 2am page when a provider upgrade changes the plan shape. If you can't staff that, contribute rules to the platform team's policy repo under their review.

  4. Put exceptions in code too. A waiver with an expiry date beats an approval buried in email. And cap the open count, because a pipeline that is always red is a pipeline nobody reads.

Start this week

  • Get read access to the main infrastructure repo. This is the whole ask.

  • Count the declared estate: terraform state list per workspace gives the declared-side number (state access is a bigger ask than repo read, so the plan output works too). Compare it against CMDB and CSPM counts, and show the three-way delta to platform first and leadership second, never as a per-team scoreboard.

  • Read one PR end to end, including the plan output and review comments. That is your change management fieldwork.

  • List which of your policy requirements are already declared in code somewhere. You will be surprised in both directions.

  • Ask the platform team what percentage of infrastructure lives outside the repo. Whatever the answer, unknown is a finding.

  • For every repo win you log, take one action on the undeclared estate. The repo makes half your world measurable. Don't let it become the whole map.

The repo will not walk you to the top of the declaration ladder. Effectiveness still takes the five metrics running against reality. What the repo gives you is the piece every metric was missing: the intent half of a diff whose observation half you already pay for. It is free to read, and only to read: the moment you gate, engineering pays, so spend that budget like it's yours.

Read the repo before you buy the platform. Intent is the cheapest data you will ever collect.

That’s all for this week’s issue, folks!

Next releases

Don't inherit someone else's guardrails.

ONE RELEASE A WEEK · FREE · NO VENDOR FLUFF