DEV Community

Myroslav Vivcharyk
Myroslav Vivcharyk

Posted on • Originally published at devgeist.com

AI Agents Should Propose Infrastructure, Not Mutate It

Most major cloud vendors now ship an MCP server that can alter your infrastructure. This is easy to build and it demos well.

It also throws away the governance properties that a decade of infrastructure-as-code was built to provide, at the moment they matter most.

You have probably seen the incident reports already, so links only, as a recap:

  • Replit's agent deleted a production database during an explicit code freeze.
  • A compromised release of the Amazon Q extension for VS Code shipped with an injected prompt telling the agent to wipe local filesystems and cloud resources through the AWS CLI. The payload reportedly never ran — a syntax error, not a control, stopped it.

The first is an agent doing its own job badly. The second is an agent doing a job an attacker gave it.
The mistake is the same in both: destructive authority reachable from probabilistic execution, with credentials.

Mutations become emergent

"Agent XYZ, create me a database" is not the demo case that matters. That's a
voice-activated console: a human forms the intent, a model transforms it into the
command. You could have built it with Alexa many years ago.

The more interesting question is what day-2 operation looks like.

A developer asks an agent for a new feature. The agent splits the work
across sub-agents, and one of them decides the service needs a database
user, ACLs, and a topic with specific retention. Or: "my queries are slow" —
and three reasoning steps later, an agent concludes the instance is
undersized and bumps it a tier.

In these chains, the infrastructure mutation is a side effect of reasoning.
It's emergent — several layers removed from the request that triggered it.

The arithmetic breaks. Agents reduce the cost of producing
a change, so mutation volume grows with whatever agent
throughput an organization buys. Review capacity doesn't grow with it (yet?): it is
still humans, at human cadence. The governance models we relied on
assume those two rates are matched. When mutation volume outruns review
capacity, the review either becomes the bottleneck or stops being real.

Status quo

The common chain today runs on a developer's machine:

  1. The agent's host launches the vendor's MCP server as a local process and gets the tools list: create_database, update_acl, delete_topic — each with a JSON schema for its arguments.
  2. The model picks a tool mid-task: it decides it needs a database, finds create_database in the catalog, and fills in the arguments from whatever context it has.
  3. The MCP server translates the call to the cloud API — using the credentials it inherited from the developer's environment. The API executes. The resource now exists.
  4. The result returns to the model as a tool result.

Note which identity did the mutating: the developer's. The agent has no
principal of its own, so the audit trail shows a human, and no IAM rule
can even tell the agent's calls from the developer's. The host asks for
per-call confirmation — until the human clicks "always allow."

AWS, Google and Azure now host remote MCP endpoints behind OAuth and IAM: the agent gets
its own identity, every call is authorized — may this principal use the
protocol, may it touch this resource — and everything appears in the audit
log. Request-level governance exists.

The regression

An agent calling cloud APIs mutates infrastructure the way an engineer
clicking through a console did in 2014.

That model was retired because it didn't survive scale.
Infrastructure-as-code introduced a declarative, versioned, checkable
artifact between intent and effect. Those properties did not become
acceptable to lose because the caller is now an agent.

The thesis

Separate the powers. The authority to propose an infrastructure
change, the authority to judge it, and the authority to execute it must
belong to different actors — and the agent holds only the first.

This is constitutional design, applied to machines.
Power is split this way for the old reason: no single
actor can be trusted to be right every time, so the guarantee is placed
in the structure, not in the actor's character.

The alternative introduces no new capability. It's the same governance IaC
already gave us, rebuilt for a caller that is fast and concurrent:

  1. The agent produces intent, not API calls. "I need a Postgres instance for service X."
  2. A server-side layer turns the intent into a declarative artifact — Terraform/OpenTofu HCL — based on the organization's existing estate and approved modules.
  3. The artifact is planned against current state, and the plan is checked against organizational policies (OPA).
  4. Denial is a negotiation. Policy violations come back to the drafting step as structured, machine-readable hints. The artifact is regenerated and resubmitted until it passes or gets detectably stuck. Which violations are self-fixable and which need a human is declared in the policy's metadata.
  5. The actor that proposes and the actor that applies are separate. The drafter can only produce an artifact. Only the gate, after a passing check, can run the plan and apply it.
  6. Every step is audited — intent, generated artifact, violations, fixes, outcome — append-only log.

The guard layer: agent intent enters a server-side, non-bypassable layer; a credential-less sandboxed drafter turns it into HCL; a deterministic policy gate negotiates violations or escalates to a human; only the plan/apply stage holds credentials and mutates the cloud, feeding state back into the estate; every step lands in an append-only audit log.

One mutation, traced

The derived intent arrives over the transport:

intent: service payments-recon needs a production Postgres instance
Enter fullscreen mode Exit fullscreen mode

The drafter, working from the estate snapshot and the module
catalog, emits a first draft:

module "payments_recon_db" {
  source              = "registry.internal/platform/postgres"
  version             = "3.2.1"
  service             = "payments-recon"
  environment         = "production"
  instance_class      = "small"
  deletion_protection = false
}
Enter fullscreen mode Exit fullscreen mode

The gate denies it — but the denial is structured:

[
  {
    "policy": "prod_deletion_protection",
    "message": "deletion_protection must be true in production",
    "self_fixable": true,
    "fix_hint": "set deletion_protection = true"
  },
  {
    "policy": "prod_instance_class",
    "message": "instance_class \"small\" is not allowed in production",
    "self_fixable": true,
    "fix_hint": "use one of: medium, large"
  }
]
Enter fullscreen mode Exit fullscreen mode

Both violations carry self_fixable: true — declared in the policy's
metadata, so the loop regenerates the
artifact: deletion_protection = true, instance_class = "medium",
resubmit. It passes, the plan is produced and checked, apply runs.

Now suppose the same intent trips a different rule:

{
  "policy": "data_residency",
  "message": "no residency classification for a new production data store",
  "self_fixable": false,
  "info_needed": "data residency classification from the service owner"
}
Enter fullscreen mode Exit fullscreen mode

self_fixable: false ends the loop: the job parks as
needs_input and escalates. No amount of regeneration fixes this one,
because the missing fact isn't in the machine's context.
Which violations converge and which escalate was decided when
the policy was written.

A few things to notice in the picture.

  • Two trust zones. The drafter proposes from a sandbox with no credentials. Only the plan/apply stage — on the far side of a passing check — can touch the cloud.
  • One door. The gate is not one mutation path among several — no side channel where one agent holds a CLI and another uses its own MCP server. Organization-level policy denies state-changing calls to every principal except the apply stage: agents, developers, and CI alike.
  • Providers encode the operational knowledge. Most Terraform providers are years of handling API quirks: retry semantics, dependency ordering, eventual consistency, idempotency, partial-failure cleanup. An agent calling cloud APIs rediscovers all of it at runtime. An agent emitting HCL inherits it.
  • Modules turn generation into vocabulary. A platform team has approved modules — its building blocks — and the drafter's job narrows to "instantiate an approved module with typed parameters." Smaller blast radius, and a place to encode some of your taste that policy alone can't express.
  • The existing estate is the context — and it compounds. Most organizations already have HCL repos and state describing what exists: naming conventions, existing networks, patterns in use, etc. And the layer feeds what it reads: every mutation it applies lands back in the estate, tied to the intent that asked for it. The more infrastructure goes through the loop, the richer the drafter's world gets. A direct-API agent can read the same repo — but its writes never land there. Every mutation it makes falls outside the declared estate, so the context it read is a little more wrong each time. One loop compounds its context; the other erodes it.
  • The drafter never sees state. It sees only the HCL declarations.

The gate checks twice

The policy gate in the picture is one box, but it runs two different checks to answer two different questions.

The first check runs on the generated HCL itself, before anything executes.
Terraform is an execution engine: init downloads provider
plugins — arbitrary binaries — and plan runs them; data sources fetch
over the network during planning, and an external data source executes
whatever local command the configuration names. A bad artifact doesn't
need to reach apply to do damage — plan is enough. So static
configuration policy pins the allowed providers and their versions,
restricts modules and providers to approved sources, and bans the escape
hatches. We check the artifact is even safe to run.

The artifact then gets an init and a plan under
read-only credentials, and the second check runs on the plan itself: the
resources that would result, the deletions and replacements, cost, regions,
encryption, quotas, and how the change relates to the estate that already
exists. This check answers "does this change comply" — the one
that can only exist because there is a declarative artifact to plan.

The exact saved plan that passed gets applied.
A saved plan is bound to the state version it was computed against, so if
another apply lands first, the stale plan is rejected and regenerated —
never applied against a state that has moved.

Notes

  1. Every agentic change has three layers. Task intent: "implement feature X" — stated by a human. Derived infrastructure intent: "feature X needs a topic and a database user" — inferred by an agent. Concrete mutation: this module with these parameters. The gate validates the third layer against policy. It cannot certify that the second was the right reading of the first — no infrastructure-level decision was ever stated for it to check against.

That is the precise limitation: the gate guarantees compliance, not intent-fidelity.
A plan can pass every rule and still be the wrong plan.

  1. None of this makes HCL sacred. Pulumi or another successor could substitute.
    The point is that the declarative-IaC ecosystem is where the
    industry already banked its operational knowledge, and the guard layer
    spends that balance instead of rebuilding it.

  2. This design governs desired-state mutations. Restarting an unhealthy service, forcing a failover,
    rotating an ephemeral credential are actions, and pushing them
    through a plan cycle would be theater. They need the same invariant — a
    server-side check nobody bypasses.

Prior art

While prototyping these ideas, I discovered — of course — that I was not
alone. AARM — Autonomous Action Runtime
Management
, a Cloud Security Alliance project that
defines runtime governance of agent actions: pre-execution interception,
bound to identity, checked against policy. If this article's premise holds
for you, the spec is worth reading.

Antitheses

1. "Isn't this just CI inside MCP? Have the agent open a pull request like
everyone else — the pipeline runs as usual."

Yes — and that's most of the point.

Build the pipeline version: a checkpoint no agent can mess with, violations
returned to the machine author in structured form, deterministic
escalation, apply on pass. That's this design, hosted in CI. The article
argues an invariant, not a deployment topology.

But the PR half smuggles back the human. A PR path
doesn't govern agents — it queues them behind a reviewer who is already
the bottleneck. Throttle agents down to human review cadence, and you've
killed the reason to use agents.

Plenty of pipelines already treat
authors as untrusted — fork PRs, plan/apply separation, restricted deploy
credentials. What none of them were built for is a machine author: the
red X replaced by a structured counter-proposal, the reviewer replaced by
deterministic escalation, generated HCL treated as hostile input.

2. "Scope the credentials tightly enough. The clouds already ship
server-side policy — SCPs and organization policies."

An SCP evaluates a call in its request context — principal, action, resource, conditions, tags.
It answers "may this principal call this API" — not "does this change
comply." The rule a platform team actually writes: production
databases use customer-managed keys, sit inside approved networks, keep
deletion protection, and are never replaced during a freeze window. That
rule is about the resulting change as a whole.

"Every Kafka topic has at least three
partitions." "Every database user gets a generated password that lands in
the secret store, never in config." Ordinary platform rules, and
request-level authorization has no vocabulary for them.

The SCP is repointed. The design keeps one
identity rule, aimed inward: deny state-changing calls to every principal
except the apply stage. Identity policy supplies the non-bypassability; plan policy supplies the
semantics.

3. "Intercept the API call and check it — that's
what runtime middleware is for."

Interception does check before execution — that's AARM-style middleware.
The difference is the action being checked. Policy
over one call sees the shape of a request. Policy over a plan sees the whole
proposed change against a snapshot of the estate. If the rules you need are
per-call, middleware is enough. The rules organizations write about
infrastructure often are not.

4. "Put the guardrails in the agent's instructions — system prompts,
hooks and skills."

A prompt is not a boundary. Client-side enforcement runs inside the thing
it's supposed to govern: any agent update replaces it silently, and any
successful injection may rewrite it. The Amazon Q incident is the case —
the instructions were the attack surface.
A checkpoint that sits server-side, outside every agent's control, is structural rather
than advisory.

5. "This adds a full plan cycle to every mutation, and Terraform state
serializes concurrent applies."

Conceded. A plan cycle per mutation is real latency, and state locking
serializes applies within a stack. How much it hurts depends on how the
estate is partitioned — many small states parallelize, one monolithic state
does not — but partitioning is mitigation, not absolution.

It is an optimistic-concurrency problem. That is the honest price of the check.

6. "Everything reduces to the quality of the policy. Who writes the
Rego?"

The organizations that can write policy to a high
standard are the ones least likely to hand agents raw credentials,
and the exposed ones can't staff it — so in practice a model will draft
the Rego: a probabilistic layer on top of a probabilistic layer, with
"deterministic" written on the box. The gate's
guarantee is exactly as good as the policy; everything the policy fails to
express is permitted.

But "a model will write the Rego" is not a regress, because the two
artifacts live in different volume regimes. Mutations arrive continuously,
at agent speed. Policies change rarely,
and are code: versioned, diffable, unit-testable.
A model drafts a rule; a human reviews it; that one review covers every mutation the rule ever gates.
The design doesn't remove the human from governance — it moves human review
from the layer where it can't keep up to the one where it can.

A prompt-injected agent can still use the loop as an
oracle and land the worst change the policy permits. The ceiling on that
damage is the policy's quality.

7. "How do you guarantee intent quality?"

You don't — the design already conceded this: the gate validates the
concrete mutation, not the reading of the task that produced it.

What the concession looks like in practice is compliant sprawl. A fleet of agents
passes every check while the estate fills with redundant, individually
compliant resources, because no rule expresses "should this exist at all."
The design bounds this failure; it doesn't solve it.

What this adds up to

Nothing in the mechanism is novel. Declarative artifacts, policy engines,
gates, audit trails — this is the reference
monitor
,
articulated in 1972.
The composition is the contribution: a machine author treated as
untrusted, denial made part of the machine's interface, desired state as
the representation of action, proposing separated from applying, and
scarce human review spent on policies and modules instead of individual changes.

The protocols are incidental. MCP is a transport and may be replaced.
Terraform is a substrate and could be, too. The whole argument compresses to one
sentence: nobody bypasses the check, and the artifact is checkable against
a declared picture of the world before it executes.
We spent years
learning why that sentence matters.

Top comments (0)