copy pass: straighten quotes and arrows, thin the dash asides, drop the app-count caption from the constellation
build-and-deploy / build (push) Failing after 13m18s

This commit is contained in:
2026-08-17 23:12:42 +10:00
parent 78e8740408
commit 3cfd59a464
23 changed files with 257 additions and 271 deletions
+13 -13
View File
@@ -1,7 +1,7 @@
---
title: "An agent should never hold the key it's using"
date: 2026-07-03
summary: "You want an AI agent that can actually do things call APIs, touch real data. You also don't fully trust it. The resolution isn't a better sandbox; it's making sure the agent never possesses a credential at all. A broker holds the keys, mints short-lived capabilities, and gates every write behind a human. Here's the pattern."
summary: "You want an AI agent that can actually do things - call APIs, touch real data. You also don't fully trust it. The resolution isn't a better sandbox; it's making sure the agent never possesses a credential at all. A broker holds the keys, mints short-lived capabilities, and gates every write behind a human. Here's the pattern."
tags: ["security", "ai-agents", "architecture", "zero-trust", "homelab"]
draft: false
hero: "/blog/broker-pattern.webp"
@@ -11,8 +11,8 @@ heroAlt: "An untrusted agent reaches through a sealed gate to ask; on the far si
The previous post put untrusted code in a hardware-isolated VM, and ended on a caveat: isolation contains an
*escape*, but it does nothing about an agent **misusing a tool it was legitimately given**. If you hand an AI
agent a database credential so it can be useful, a single bad decision a prompt injection, a confused chain
of reasoning spends that credential. The sandbox did its job perfectly and you still got robbed, through the
agent a database credential so it can be useful, a single bad decision - a prompt injection, a confused chain
of reasoning - spends that credential. The sandbox did its job perfectly and you still got robbed, through the
front door you built.
So the real question isn't "how do I isolate the agent?" It's "how does the agent get work done *without ever
@@ -22,13 +22,13 @@ holding a key*?"
The pattern is to put every credential, every tool, and every model endpoint **behind a broker**, and give the
agent only a *capability to ask*. The agent never sees a token. It calls the broker; the broker holds the real
credential, decides whether the request is allowed, and if it is performs the action itself and returns the
credential, decides whether the request is allowed, and - if it is - performs the action itself and returns the
result. The key never leaves the broker.
That one inversion changes the threat model completely. A fully compromised agent can now do exactly one thing:
**ask**. And asking is answered by something it can't reach, can't impersonate, and can't bypass.
<!-- DIAGRAM: untrusted sandbox cred-broker (mint capability) mcp-broker (verify + run tool with held cred) human-approval gate downstream. Agent never touches the credential. -->
<!-- DIAGRAM: untrusted sandbox -> cred-broker (mint capability) -> mcp-broker (verify + run tool with held cred) -> human-approval gate -> downstream. Agent never touches the credential. -->
![The two-tier broker flow: capability in, action out, credential never exposed](/diagrams/broker-pattern.svg)
## Two clusters, two brokers
@@ -36,39 +36,39 @@ That one inversion changes the threat model completely. A fully compromised agen
The trust boundary is physical, not just logical. The **untrusted executor** (the Kata sandboxes) lives on one
cluster; the **trusted tier** (the brokers, the model gateway, the real credentials) lives on a *separate*
cluster. A total compromise of the executor still can't reach the brokers' secrets except across a policed
network link there's no shared kernel, no shared API server, nothing to pivot through.
network link - there's no shared kernel, no shared API server, nothing to pivot through.
On the trusted side there are two brokers, deliberately split:
- A **credential broker** validates the sandbox's identity (a short-lived, signed token unique to the task) and
mints a **capability** a cryptographically signed, scoped, single-use, expiring grant. Not a credential. A
mints a **capability** - a cryptographically signed, scoped, single-use, expiring grant. Not a credential. A
*permission to ask for one specific thing*.
- A **tool broker** takes that capability, verifies the signature, the scope, and the one-time nonce, and only
then runs the requested tool using a credential *it* holds. The result comes back; the credential doesn't.
then runs the requested tool - using a credential *it* holds. The result comes back; the credential doesn't.
## A human gates every write
Reads are one thing. For anything that *changes the world* creating, deleting, sending the broker doesn't
Reads are one thing. For anything that *changes the world* - creating, deleting, sending - the broker doesn't
just decide on policy. It **stages** the action and pings a human: an Approve/Deny prompt on my phone, carrying
a one-time token bound to the exact task, method, and arguments. Tap approve and it executes; tap deny, or
ignore it, and it doesn't. The gate is **fail-closed**: a timeout is a denial, a replayed token is rejected, an
unknown method is rejected. The default, always, is *no*.
The load-bearing principle underneath all of it: **enforcement that has to survive a compromised agent lives at
the broker, never at the agent or the orchestrator.** Approval, scope, budgets, write-authority none of it
the broker, never at the agent or the orchestrator.** Approval, scope, budgets, write-authority - none of it
lives anywhere the agent's reasoning can touch. The agent can be wrong, jailbroken, or outright hostile, and the
worst-case is still just *a request that gets refused*.
## Useful and safe at the same time
It's tempting to think you have to choose: give the agent real power and accept real risk, or lock it down so
hard it can't do anything. The broker pattern is how you get both. The agent is *useful* it can call real
tools against real systems. It's *safe* it never holds a key, every write waits on a human, and the moment
hard it can't do anything. The broker pattern is how you get both. The agent is *useful* - it can call real
tools against real systems. It's *safe* - it never holds a key, every write waits on a human, and the moment
something goes wrong, the blast radius is a denied request, not a spent credential.
Run agents like you'd run any other untrusted input: assume it's compromised, and make sure that assumption is
*boring*.
*Live as the trusted tier of a two-cluster AI-agent platform: a credential broker and a tool broker holding the
keys, a phone-based human-approval gate on every write, and an agent that by construction never sees a
keys, a phone-based human-approval gate on every write, and an agent that - by construction - never sees a
secret.*