Give Tools Calls, Not Keys

A compromised tool should have nothing to leak — because it was never handed the secret in the first place, only the call.

· 5 min read · updated

agents security isolation capabilities

the map · part 5 of 8

cloister · part 1 of 3


Most agent setups run on an arrangement nobody quite decides to make: you give a tool your API key, and from then on you’re trusting it. The weather lookup holds a token, the deploy step holds a token, the scraper holds a token — each one now carries a live secret, and the only thing between you and trouble is that none of them misbehaves or gets compromised.

Prompt injection turns that arrangement into a liability. A tool that reads untrusted input — a web page, an issue comment, a document it was asked to summarize — can be instructed by that input. If your secret is sitting in the tool’s own memory, “send the key somewhere” is one well-crafted sentence away, authorized not by you but by whatever the tool happened to read.

The tool never needed the secret

Watch what a tool does with your API key: it makes a call and uses what comes back. It never needs to hold the key, only to have the key used on its behalf. A tool that holds a key can lose it; a tool that only ever receives results has nothing to lose.

So don’t give it the key. Give it the call.

cloister keeps your credentials in a locked box, makes the outbound request for the tool, and returns only the result. The tool says “call this API”; cloister supplies the credential at the boundary and passes back the response. The secret never enters the tool — not in its memory, not handed over and trusted to be forgotten.

So a leaky or compromised tool has nothing to hand over. You can prompt-inject it as hard as you like and it still can’t produce a key it was never given. cloister ships that as a test suite rather than a claim. A simulated compromised bundle tries to read credentials outside its grant and gets denied. The denial response itself is checked for leaked credential bytes. Injected identities stuffed with control characters, null bytes, and wildcards are rejected at the gate. Even the entries at rest are sealed — the raw database bytes contain no plaintext, and a wrong key decrypts nothing. Every attack in the suite dies the same death, because there is no credential inside the isolate to pry loose.

Structure, not promises

What makes this hold is that the boundary is built into the runtime, not agreed to by the tool. Each tool runs in its own workerd isolate — the isolate technology behind Cloudflare Workers — and can reach the credential vault only across a service binding it has no way to forge. Plaintext credential bytes never cross that binding; the vault makes the call and returns output. A per-credential allowedSubs list decides which identities may use which secret, checked at the seam rather than carried in a token the tool could replay.

The same discipline goes all the way down. The master key that anchors the vault lives in a separate helper process that exposes exactly one operation — sign — so even the component that uses the key never holds it.

A boundary a tool agrees to respect lasts until something inside the tool decides otherwise. A boundary the runtime enforces is a fact about where the bytes are — and prompt injection can rewrite a tool’s intentions, but not its address space.

Everything on the record

Keeping a tool away from secrets is one half of the problem; knowing what it actually touched is the other. Every state-changing call through cloister leaves a signed, tamper-evident receipt you can verify afterward, offline — a signature chain rather than a log you’re asked to believe. The chain is content-addressed and hash-chained, and not loosely: cloister pins the hashing code from that substrate by exact revision, so the bytes doing the chaining here are the same bytes doing it everywhere else in the stack.

So the boundary runs both ways. Going in, a tool can’t reach a secret it wasn’t granted. Coming out, it can’t change cluster state without leaving something you can check. Neither direction depends on the tool behaving.

The rest of the bundle

The credential story is one slice of a larger system. Around it sit the declarative manifest that wires a whole bundle of tools together behind a single endpoint, the kernel confinement that bounds the whole process, not just its credentials, the attestation spec with its own cross-language test vectors, and the fact that the same config runs on your laptop with no cloud account and deploys to Cloudflare Workers untouched. Each of those is worth its own post. None of them changes the shape of this one: the tool gets a call, never a key.

A narrower promise

This is the boundary layer of the map — the part that answers what an agent could touch. The usual answer is “whatever its tools’ credentials can reach, assuming the tools behave.” cloister’s is narrower and more demanding: a tool touches what it was granted a capability to touch, the secrets that would let it reach further are never in its possession, and every reach it does make is recorded.

You still can’t know what a tool will do once someone feeds it a hostile prompt. What you can arrange is that whatever it does, it does without your keys, because the keys were never handed out to begin with.

Open source: github.com/agentic-research/cloister. The security claims come with tests and stated caveats.