TTaper Loading runtime…
Credential broker for AI agents · v0.2 · unaudited

The agent never holds the key. It asks the desk.

Coding agents keep deleting production because they're handed credentials. Taper is a locked box that holds the keys and only carries out the specific, narrow operations an agent is allowed to ask for — and a helper can always be given less, never more. As of v0.2 the token also says who it acts for, the database gets a say before a write, and Tower mints a sixty-second credential per operation instead of keeping one. This page runs the real library in your browser.

The token

A signed list of typed operations with constraints — ssh.exec on this host running this program, pg.query of this statement kind on these tables. It only ever narrows: a subagent gets a strictly smaller one, signed offline, and a widening block is rejected structurally rather than by policy.

The broker

A separate process under a different Unix user holds the real credentials. It checks proof of possession, validates the typed request, evaluates policy, and performs the operation itself. The credential is never in the agent's memory, environment, or shell.

The target speaks

Taper is never the only thing saying no. The database role can't ALTER; sshd runs a force-command. And before a write, the broker asks the database for its own invariants — no backup since Tuesday, three views depend on this — and proceeds only if the grant named each one. A wildcard never does.

Tower: clearance, not custody

The next track. Instead of holding a database password, the broker holds a CA and mints a sixty-second certificate per operation, with the human's name in it, only after re-verifying the chain itself. The database has no password to steal. Stage 1 shipped in v0.2; the design removes the vault entirely.

If you had to explain it at dinner

Today's agents get the hotel's master key: it opens every door, and if the agent is confused or tricked, the wrong door opens. Taper is the front desk. The agent never touches a key. It says "open room 412," the desk checks its list, and a staff member opens that one door. The agent can hand a helper a shorter list — just 412, just for five minutes — but can never write a longer one. Every request, allowed or refused, goes in a ledger where tearing out a page leaves a visible gap.

What it is not

Not a sandbox (it mediates the paths that go through it, nothing else), not a model guardrail (no LLM in the decision path), and not audited — about two thousand lines of Python by one person. The measured result so far is deliberately modest: in a rebuild of the PocketOS incident, an agent with a credential and an agent with a token both did the job, ten out of ten. Taper doesn't stop an agent working; it bounds what else it could have done.

Playground

Nine steps, one property each. Every button drives the unmodified taper-broker package — nothing leaves this tab. Two steps need a database to answer, and a stand-in plays that one part; the page says so where it happens.

1Write a policy and mint the root token

A policy is an operation plus a constraint per field. This is the widest this token will ever be — everything downstream is a subset of it.

Do: read the policy (five operations, each with typed constraints), optionally edit it, then click Mint root token. The token names who it acts for — a subject, signed by the root, that every narrowing inherits and none can change. Try the three forgeries afterwards. Notice an unknown constraint kind is refused rather than ignored, and that every any is called out: that is policy pressure, made visible at the moment it is written.

2–3Narrow it for a subagent, then try to widen it

The helper gets one host, one program, one argument, five minutes — and pg.query disappears entirely. No server is contacted: the parent signs a new block with an ephemeral key.

Do: click Narrow. Open "effective capabilities" on the result and compare it with the root token above. Then, for step 3, click Try to widen it — the subagent asks for a second host it never had.

4–5Make a request, attack, then steal the token

Order matters: proof of possession first, then the typed schema, then policy. A refusal quotes the constraint that refused it. An allowed request shows the exact argv the broker would execute.

Do: click Send request and open "what the broker would run". Then click any attack — they are the payloads from validate/redteam.py, including the four that got through the first time. For step 5, untick with proof of possession and send again: that's someone who copied the token text out of a log.
Attack payloads — each fills the request above and sends it

6Revoke, expire, tamper

Revocation matches any id in the chain, so revoking the root kills the subagent too. TTLs only shrink down the chain. And the audit log on the right is hash-chained — delete a record and it tells you where.

Do: click Revoke root token and read the subagent's refusal. Click Advance clock to see the 300-second token expire. Then, in the audit panel, click × on any record.

7The target speaks

A token can be exactly right and the write still wrong, because only the database knows whether this table has a backup from this morning. Before a write, the broker calls a function the database owns and proceeds only if the grant names every invariant it raises. The agent has no say; a wildcard never counts.

Do: the root token's pg.migrate grant names production in its invariants. Click Migrate with the database raising only production: the write proceeds and the override is on the tape. Then tick stale backup so it also raises no_recent_backup, which the grant does not name, and click again. The database here is a stand-in that answers the invariants probe; the broker, executor, refusal and audit record are the real code.

8Read the refusals

taper audit --refusals sorts every denial in the log by kind. The policy bucket — well-formed requests the grant did not cover — is the policy-pressure metric from the design document. Attacks are counted apart, so the report can never argue for widening a grant to admit one; refusals by the target are apart again, because the answer to those is "read what it said".

Do: click Summarize. If you ran the attacks in step 4 and both migrations in step 7, you will see all three kinds, and the policy bucket grouped by the field and the value it wanted.

9Mint a clearance — Tower, stage 1

A credential that exists because a decision caused it to. The tower re-verifies the chain and the proof with its own state, refuses a decision about any other chain or subject, and mints a client certificate valid for sixty seconds: CN the database role, OU the human, serial derived from the clearance. The executor connects with it — and with no password, because there is none — and it is gone.

Do: click Clear and connect. Read the certificate's subject and lifetime, then look at the tape: a clearance record sits between the decision and the result, and the tower holds nothing afterwards. Try it again after step 6's revoke. The certificate and its issuance are real; the database it connects to is the stand-in.