--- 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." tags: ["security", "ai-agents", "architecture", "zero-trust", "homelab"] draft: false hero: "/blog/broker-pattern.webp" heroAlt: "An untrusted agent reaches through a sealed gate to ask; on the far side, a guardian holds a ring of keys the agent can never touch." --- 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. ![The two-tier broker flow: capability in, action out, credential never exposed](/diagrams/broker-pattern.svg) ## 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.*