blog: republish 2 edited posts, dossier thumbnails 4-6, new post broker-pattern
Content: shipping-this-site + init-gating-gpu-readiness get their edited-workshop bodies (from the
NAS Published/ pass) - site frontmatter (hero/heroAlt) kept, only prose replaced.
Dossiers 004-006 (gpu-as-code, global-infra-modernisation, network-automation-fleet) were all
rendering the same generic pipeline thumbnail - added 3 tailored DossierThumb motifs (gpu die +
passthrough lanes, region globe, switch-fleet grid) via a new diagram: frontmatter field. Also
closed the dead-space gap above the tag row on short-argument cards with a faint on-theme grid
fill (::before, masked fade) instead of a flat void.
New post: broker-pattern ('An agent should never hold the key it's using') - SECURITY SERIES 02,
between secret-zero and workload-least-privilege. Hero de-watermarked from the NAS source (resized
to the calibrated 1600x1073 coordinate space, Jacobi-diffused out the Nano Banana sparkle at its
known center) via scripts/dewatermark-broker-pattern.mjs (reusable for the next 3 drafts). New
diagram/broker-pattern.svg in the house light-card palette. Inline top-of-body hero stripped per
the astro-static-site skill guard (frontmatter hero: is now the only render path).
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
---
|
||||
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.
|
||||
|
||||
<!-- DIAGRAM: untrusted sandbox → cred-broker (mint capability) → mcp-broker (verify + run tool with held cred) → human-approval gate → downstream. Agent never touches the credential. -->
|
||||

|
||||
|
||||
## 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.*
|
||||
Reference in New Issue
Block a user