Repository navigation
feat(keys): keep agents' keys out of the chat - #267
Merged
Merged
Conversation
First slice (K1) of spec 32. The owner saves a key for one agent with `keys.set` (name, value, description); `keys.list` gives the names back, never a value, and `keys.delete` removes one. The value lives in secrets\keys\<bot_id>.json, readable only by the current user; the database keeps the name and description. The agent restarts, resuming its conversation, with the key as a variable of its process, and its rules (.claude/rules/keys.md, or AGENTS.md for the other engines) list the names with the rule never to print a value. Names the system, Botloft or an engine use are refused. Every text the daemon keeps about the agent (replies, tool calls and results, approval requests, messages it gets or sends, notices) has the agent's key values replaced by `[chave NAME]` before it is stored. Deleting the agent deletes its keys.
The ask_key tool opens a key_request card in the agent's chat and returns at once. The owner answers with keys.set and the request id, which saves the key, restarts the agent and tells it the key is in its environment, or with keys.dismiss, which tells it the key did not come. The request is the chat item itself, so there is no key_requests table (spec 32.9 updated). At most 5 open requests per agent; the phone never gets them.
The key_request card shows what the agent needs and a hidden field with an eye, then shrinks to "You saved the key NAME" or "You did not give the key NAME". The agent's details get a Keys part: each key with what it is for, when it changed and "Value missing" after a restore, with replace, delete and add. After the owner pastes a saved key in the chat, the composer says the agent got its name instead. The fake daemon swaps saved values for names as the real one does, and the preview seeds a request and two keys. Texts in English, Portuguese and Spanish.
keys.scan looks through every text Botloft keeps (messages, chat items, approval requests, questions, task results) for known key prefixes and JWTs, and returns each value once with only its start and end, the agents it showed up for and how many places. keys.scrub keeps a hit as an agent's key, which restarts it with the variable, and swaps the value for [chave NAME] everywhere, or swaps it for [chave removida]; the chat search follows through its triggers. Settings gets a Security page with the search, keep and delete, and the reminder to revoke the key at its service.
…aits Found with the real Claude Code: an agent asked for a key and the owner saved it while its turn was still running. The restart waited for the turn, but the note saying the key was in its environment went to the old process first, which then ran without the variable. While a restart for new settings is pending, write_message now refuses, so the courier defers and the message reaches the new process. The spec records the check in section 19 and the rule in 32.4.
httpsphl
enabled auto-merge
October 26, 2026 15:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Agents' keys (spec 32): an agent gets a token or password for another service as an environment variable, by name, without the value going through the chat.
What changes
keys.list/set/delete. Values live insecrets\keys\<bot_id>.json(user-only ACL), never in the database, logs, backups or the cloud; the database keeps names, descriptions and when they changed. The agent restarts with--resumeand gets each key in its environment, in all three engines, plus.claude/rules/keys.md(or the end ofAGENTS.md) with the names and the rule to never print a value. Whatever the daemon stores (chat items, approvals, messages) has saved values swapped for[chave NAME]. Reserved names (PATH,ANTHROPIC_*,BOTLOFT_*...) are refused.ask_key {name, why}. It returns at once and opens akey_requestchat item.keys.setwithrequestIdsaves the key and closes the request;keys.dismisssays "not now". Either way the agent gets a message from Botloft. At most 5 open requests per agent; the phone never gets them.keys.scan/keys.scruband a new Settings page, Security. It finds known key prefixes and JWTs in every text Botloft keeps and shows only their start and end. "Keep as a key" saves the value for an agent and swaps it for[chave NAME]; "Delete from Botloft" swaps it for[chave removida]. The chat search follows through its triggers.write_messagenow refuses, so the courier defers and the next message goes to the new process. Before this, a key saved mid-turn was announced to the old process, which ran without the variable.The spec is updated in the same commits: section 32, the tool table in 10, and a row in 19.
Tests
cargo fmt,clippy -D warnings,cargo test --workspaceandpnpm checkpass locally.tests/agent_keys.rs,tests/key_requests.rs(including a key saved mid-turn),tests/key_scan.rs,key_scan.rsin core and store,features/keys/*.test.tsx.Manual check with the real Claude Code
Done on 2026-10-26 with Claude Code 2.1.309, Haiku and Manual mode, on a separate test daemon (spec 19):
DEMO_TOKENwithask_keyand end its turn. It loads the tool throughToolSearchand a card shows in its chat.$env:DEMO_TOKEN.Lengthis right.Write-Output $env:DEMO_TOKEN. It refuses by its rule; once allowed, the output shows[chave DEMO_TOKEN]in the chat.secrets\keysand in Claude Code's own session history, as spec 32.6 says.Still to come: the request in the island (spec 31.5), together with the island window.