Give each agent its own token, and stop trusting the body (#34)
* Give each agent its own token, and stop trusting the body Calling a tool back used to need one deployment-wide secret, with the Bot and the person read out of the request body. Anything holding that secret could spend any Bot's grants and write any name into the audit trail. The trail is the product, and a forgeable trail is worse than none because it is believed. Two credentials now, because they answer different questions and one cannot answer both. The token says which agent is calling. Issued per agent, held by whoever runs it, and stored here only as a hash, so a database dump is not a set of working credentials. Issuing again rotates; revoking leaves the agent able to talk and unable to reach anything outside a conversation. Null is the default, which is what a URL somebody pasted should get. The run assertion says which Bot and which person. Signed by this deployment, ten minutes long, sent in forwardedProps and handed straight back. An agent is in no position to assert who it acts for, so it carries our note instead of making a claim. They are checked against each other: an agent may only act as the Bot its token was issued for, so an assertion seen once cannot be replayed by another credentialled agent. The deployment-wide token still authenticates the Bots that ship in the box, but it no longer asserts anything. Driven end to end, not just unit tested. A registered outside agent with its own token called a granted tool and the audit row named the visitor rather than "agent". The same assertion presented with a second agent's token was refused 403, no assertion at all 401, an unknown token 401. Also fixes a latent migration bug this work uncovered. drizzle.config.ts still named computer.ts, deleted when computer use went, and did not name the policy.ts that replaced it, so action_policy looked absent and the next generated migration opened with DROP TABLE "action_policy" CASCADE. It destroys the deployment's boundary. The config now names the right file, the new columns are folded into the single baseline this repo keeps rather than a second migration, and the baseline was verified by rebuilding the database from nothing. The demo seed also refuses with an explanation when the roster has not been written yet, instead of a foreign-key error naming a constraint. * Keep the surface's own tools out of the agent's loop Two things the merge dropped, both found by driving it rather than by any gate. The set naming which tools this deployment runs was not built, so the filter that reads it threw `ours is not defined` and the run died before it said anything. Green typecheck, green suite, dead Bot: the variable is only reached once a model actually asks for a tool. With it back, the reason it exists matters more than the crash. A run is offered two kinds of tool that no naming rule separates: the Bot's grants, which execute through the policy and the audit trail, and the components the surface draws. A call the surface owns now ends the run, which is how a browser tool is supposed to work, the surface draws it or puts the question to a person and starts the next run with the answer. Running the loop through it instead invents a result: the Bot apologises for a chart the person is looking at, and an approval card answered on its behalf waits for a click that can never land. * Stop the agent credential announcing the wrong product The token this deployment issues an agent was minted as `okai_agt_...`, which is the other product's prefix. The prefix exists so a leaked credential can be recognised on sight, in a log or by a secret scanner, and one naming a product this is not defeats the only job it has. It is `obot_agt_` now. The panel that issues it, and the code around it, still called a Bot a teammate. That rename happened everywhere else already. Found by opening the screen. The security work these sit on was proven through the API, which is the right place to prove a refusal, and it meant nobody had looked at the thing a person actually uses to mint one.
D
David McKay committed
3ccd16fb49c74f11c1a10c5e8f616421b0dcad06
Parent: afde20f
Committed by GitHub <noreply@github.com>
on 8/20/2026, 4:19:19 PM