An agent should never hold the key it's using

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 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 holding a key?”
The agent holds a capability, not a credential
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 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.
Two clusters, two brokers
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.
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 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.
A human gates every write
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 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 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 secret.

