diff --git a/AGENTS.md b/AGENTS.md index 2b13cdc..d78f247 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,10 +8,10 @@ A personal app for storing secrets, encrypted and saved only on this computer. - Never open, list, copy, decrypt, delete, move, overwrite or clean a real Vault, Backup or Safety Copy, or the app's data folders (`com.encryptednote.app*`), never run the Emergency Reader on one, and never ask for or accept its Master Password, Recovery Key or Hidden Fields, even if the owner asks (ADR-0007). Say no, say why, and offer a fictional Vault. - Ask the owner before any `git clean -x` or `-X`: it deletes ignored files, such as `*.enote` Backups. - Launch a release build only with a test identifier: `scripts/package-companion.sh --app --identifier com.encryptednote.companiontest`. With the default identifier it opens the owner's real Vault folder, so `--app` refuses it; `--owner-build` is for the owner only. -- Never add network access to the Vault core or WebView: no HTTP clients, updaters, telemetry, crash reporters or analytics (ADR-0001). The only approved exception is the optional Agent Companion helper (ADR-0009): a separately sandboxed XPC service that makes only the companion's fixed OpenAI request. Never widen it, give it Vault access, or add networking anywhere else. Its source is in `companion/`: never import that folder from `src/`, `src-tauri/src/` or `crates/` (ADR-0010). -- Keys and decrypted Notes live only in the Rust core; the WebView receives one Hidden Field at a time (ADR-0003). The Agent Companion helper may hold one separately created Agent Key, never a Vault value (ADR-0009). +- Never add network access to the Vault core or WebView: no HTTP clients, updaters, telemetry, crash reporters or analytics (ADR-0001). The only approved exception is the optional Agent Companion helper (ADR-0009): a separately sandboxed XPC service that makes only fixed operations the owner has approved. Today there is one, the companion's OpenAI request; ADR-0014 (not built) lets the owner add more, one at a time, each with its own record, Agent Key and PR. Never widen it beyond an approved operation, give it Vault access, or add networking anywhere else. Its source is in `companion/`: never import that folder from `src/`, `src-tauri/src/` or `crates/` (ADR-0010). +- Keys and decrypted Notes live only in the Rust core; the WebView receives one Hidden Field at a time (ADR-0003). The Agent Companion helper may hold separately created Agent Keys, one per approved operation, never a Vault value (ADR-0009, ADR-0014). - Never pass Vault content, Note metadata, Note keys, Wallet Kinds, the Master Password or the Recovery Key to the Agent Companion, and never forward helper output, provider errors, keys or tokens to the WebView (ADR-0009). -- The agent key route (ADR-0012; built with fictional data, not yet checked by hand or reviewed) lets an agent in the separate agent account store keys and manage the Notes it added, and nothing more. Its code: `src-tauri/src/core/agent.rs` (the grant and the five requests), `src-tauri/src/agent_route.rs` (the Unix socket in `/Users/Shared/encrypted-note-keys`, the app's only listener), `crates/agent-keys-mcp` (the agent's tool), and the mark and edit rules in `crates/notes/src/agent.rs`. Keep it to the five requests, from the agent account's uid only, behind the owner's grant of at most an hour. No request field or reply may carry a value, whether a value is set, a Note not marked "added by an agent", a Note in Trash, the Master Password or the Recovery Key; replies stay fixed shapes and error codes, never free text. The owner types values into the app's own editor, never through the agent, and nothing marks an existing Note. Don't loosen `agent_edit_rule`: where-a-key-is-used edits need the owner's Allow, and every Wallet Kind edit and every delete also the Master Password. Unix sockets and peer-uid checks belong only in `agent_route.rs` and `crates/agent-keys-mcp/src/`, the tool depends on no Vault crate, and none of this code uses `companion/` (`node scripts/check-network-source.mjs` checks all three). It never signs: Chat Wallets and signing (ADR-0011) are deferred, so don't build them. +- The agent key route (ADR-0012; built with fictional data, not yet checked by hand or reviewed) lets an agent in the separate agent account store keys and manage the Notes it added, and nothing more. Its code: `src-tauri/src/core/agent.rs` (the grant and the five requests), `src-tauri/src/agent_route.rs` (the Unix socket in `/Users/Shared/encrypted-note-keys`, the app's only listener), `crates/agent-keys-mcp` (the agent's tool), and the mark and edit rules in `crates/notes/src/agent.rs`. Keep it to the five requests (ten once ADR-0011's Chat Wallet requests are built), from the agent account's uid only, behind the owner's grant of at most an hour. No request field or reply may carry a value, whether a value is set, a Note not marked "added by an agent", a Note in Trash, the Master Password or the Recovery Key; replies stay fixed shapes and error codes, never free text. The owner types values into the app's own editor, never through the agent, and nothing marks an existing Note. Don't loosen `agent_edit_rule`: where-a-key-is-used edits need the owner's Allow, and every Wallet Kind edit and every delete also the Master Password. Unix sockets and peer-uid checks belong only in `agent_route.rs` and `crates/agent-keys-mcp/src/`, the tool depends on no Vault crate, and none of this code uses `companion/` (`node scripts/check-network-source.mjs` checks all three). Signing comes only with Chat Wallets (ADR-0011, reinstated 2026-11-09, not built): build it only by its build order, one PR per step, with fake keys and test funds, and never sign with a Seed Phrase, a Private Key or an Agent-added Note. Only Claude Code and Codex may use the route (ADR-0013, not built): their signature list stays fixed in code, never in anything the agent account can write. - Add a dependency only when it's clearly needed, pin its exact version, and say why in the PR/issue. ## Verify diff --git a/CONTEXT.md b/CONTEXT.md index 31762b7..e13dbc2 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -63,7 +63,7 @@ A separate credential the owner creates at a service only for AI agent work, wit _Avoid_: sharing an owner key, agent access to the Vault **Agent Companion**: -The optional helper inside the app, with its own macOS sandbox, that lets the agent tools in one separate Mac account use one fixed OpenAI text operation with an **Agent Key**. It can't open the **Vault**, is off unless the owner starts it, and stops at the end of its one-hour grant, on Stop, whenever the app is **Locked**, on sleep and when the app quits ([ADR-0009](docs/adr/0009-optional-agent-companion-helper.md)). +The optional helper inside the app, with its own macOS sandbox, that lets the agent tools in one separate Mac account use fixed operations the owner approved, each with its own **Agent Key**. Today there is one, an OpenAI text operation; more may be added one at a time ([ADR-0014](docs/adr/0014-agent-key-operations-through-the-companion.md), not built). It can't open the **Vault**, is off unless the owner starts it, and stops at the end of its one-hour grant, on Stop, whenever the app is **Locked**, on sleep and when the app quits ([ADR-0009](docs/adr/0009-optional-agent-companion-helper.md)). _Avoid_: agent bridge (that's the agent account's side), Vault connector, plugin **Agent-added Note**: @@ -71,7 +71,7 @@ A **Note** an AI agent in the separate agent account asked the app to store, mar _Avoid_: agent key (that's an **Agent Key**), agent Note, managed key **Chat Wallet**: -Deferred (ADR-0011, not built): a key apart from the **Notes**, holding one private key for one Base account, made only for agent use and kept under its own key, apart from **Wallet Kinds**. It should sit at an address holding only a small amount. An agent in the separate agent account can ask the app to list, create, import, rename or send from one, but never sees the key. Every send must be a plain transfer within the owner's limits, to a listed recipient, approved with Touch ID and the **Master Password**. The app signs the transaction and the agent sends it ([ADR-0011](docs/adr/0011-chat-wallets.md)). +Reinstated 2026-11-09, not built ([ADR-0011](docs/adr/0011-chat-wallets.md)): a key apart from the **Notes**, holding one private key for one account on one EVM chain or on Solana, made only for agent use and kept under its own key, apart from **Wallet Kinds**. It should sit at an address holding only a small amount. Claude Code or Codex, in the separate agent account, can ask the app to list, create, import, rename or send from one that the owner's grant covers, but never sees the key. Every send must be a plain transfer within the owner's limits, to a listed recipient, approved with Touch ID and the **Master Password**. The app signs the transaction and the agent sends it ([ADR-0011](docs/adr/0011-chat-wallets.md)). _Avoid_: agent wallet, hot wallet, using a **Seed Phrase** or **Private Key** from chat ### Screen privacy @@ -138,6 +138,7 @@ _Avoid_: pinned, bookmarked, starred item - The owner has one **Vault**; it moves between computers only when the owner copies the file by hand. - The app gives AI agents no interface to the **Vault**, its **Notes**, their metadata, **Backups**, **Safety Copies** or the **Emergency Reader**, and agents are told never to use them. (The one exception is **Agent-added Notes**, through a local route only the separate agent account can use while the owner's grant runs: their names and visible details, never their values.) That limits the app, not the computer: what keeps an agent running in the owner's own Mac account away from these files is the owner's setup. The **Master Password** and **Recovery Key** never take part in agent work, and a **Wallet Kind**'s value never does: an agent may only ask to store one, which the owner types in with the **Master Password**, and, for one it added, ask to edit its visible details (the owner's Allow, then the **Master Password**), replace its value (typed by the owner, with the **Master Password**) or delete it (the **Master Password**) ([ADR-0012](docs/adr/0012-agents-store-and-manage-keys.md)). - The **Agent Companion** is the only **Agent Key** integration inside the app; it runs apart from the **Vault** in its own sandbox and never receives Vault content. Any other **Agent Key** integration is separate from this app. Hiding the key still lets an agent perform every operation its provider scope permits, within its enforced limits; only the owner can independently approve scope or limit changes ([ADR-0007](docs/adr/0007-agents-use-separate-restricted-keys.md)). +- Only Claude Code and Codex, started in the separate agent account, may use the agent routes; the app checks their Apple code signatures ([ADR-0013](docs/adr/0013-only-claude-code-and-codex-use-the-agent-routes.md), not built). That check is a speed bump: the separate account is the boundary, so it holds only those two. - Results from an external agent's allowed service actions may leave this computer, including for a cloud model provider; no Vault content or metadata may enter that route. - Moving a **Vault** to the other computer means opening a **Backup** there. There is no separate "export" or "sync" concept. - The app nudges the owner when changes have gone 7+ days without a **Backup**, and warns if a **Backup** is saved into a cloud-synced folder. diff --git a/docs/adr/0004-wallet-kinds-under-a-separate-key.md b/docs/adr/0004-wallet-kinds-under-a-separate-key.md index 47ad0c9..0dc6f7c 100644 --- a/docs/adr/0004-wallet-kinds-under-a-separate-key.md +++ b/docs/adr/0004-wallet-kinds-under-a-separate-key.md @@ -1,6 +1,6 @@ # Wallet Kinds are encrypted under a separate key that unlocking doesn't open -> **Related: [ADR-0012](0012-agents-store-and-manage-keys.md) (2026-11-05; built 2026-11-06 with fictional data, hand checks and review still to do).** An agent may ask the app to store a **Seed Phrase** or **Private Key**. The owner types it into the app's own editor, with the **Master Password** as today, and it is sealed under the Wallet Key exactly as above. The agent never sees or uses it. ([ADR-0011](0011-chat-wallets.md), which would have signed with a separate Kind, is deferred.) +> **Related: [ADR-0012](0012-agents-store-and-manage-keys.md) (2026-11-05; built 2026-11-06 with fictional data, hand checks and review still to do).** An agent may ask the app to store a **Seed Phrase** or **Private Key**. The owner types it into the app's own editor, with the **Master Password** as today, and it is sealed under the Wallet Key exactly as above. The agent never sees or uses it. ([ADR-0011](0011-chat-wallets.md), reinstated 2026-11-09 and not built, signs only with Chat Wallets, a separate Kind under its own key. Wallet Kinds never sign for an agent.) Showing a **Seed Phrase** or **Private Key** requires the **Master Password** again. If the whole **Vault** were already decrypted in memory, that prompt would be only a UI check in front of exposed data. So the **Hidden Fields** of **Wallet Kinds** are encrypted a second time under a separate random Wallet Key, and a normal unlock does not open it. Only the **Master Password** (a fresh Argon2id derivation, about 0.5 s) or the **Recovery Key** can unwrap the Wallet Key. It is held just long enough to show, add or edit one **Note** and is then wiped. Someone at an **Unlocked** computer, or anything reading the app's memory while it's merely **Unlocked**, can see Wallet Kind titles, chains and addresses, but not the words or keys. diff --git a/docs/adr/0007-agents-use-separate-restricted-keys.md b/docs/adr/0007-agents-use-separate-restricted-keys.md index 61fd21b..e86bc0b 100644 --- a/docs/adr/0007-agents-use-separate-restricted-keys.md +++ b/docs/adr/0007-agents-use-separate-restricted-keys.md @@ -4,7 +4,11 @@ > > **Amended by [ADR-0012](0012-agents-store-and-manage-keys.md) (2026-11-05; built 2026-11-06 with fictional data, hand checks and review still to do):** the owner approved a narrow agent route for **storing and managing** keys. An agent in the separate agent account may ask the app to store a key, which the owner types into the app's own editor, and may list, edit the details of, and ask to replace or delete only the Notes it added. It never receives a value, never sees any other Note, and never uses a key. So the app now has one agent interface, a local socket that only the agent account can use while the owner's grant runs ([Agents storing keys](../agent-keys.md)). Everything else here stands. > -> **Deferred: [ADR-0011](0011-chat-wallets.md) (2026-11-05).** It had approved a second exception, the Chat Wallet route; the owner deferred it the same day, and none of it is built. An agent in the separate agent account may make six fixed requests about Chat Wallets, a new and separate Kind for keys at addresses that hold only small amounts. It can get back public addresses, limits left and signed transactions, each send approved by the owner with Touch ID and the Master Password. It never receives a key, an ordinary Note, metadata, Wallet Kinds, the Master Password or the Recovery Key. Everything else here stands. +> **Amended by [ADR-0011](0011-chat-wallets.md) (accepted 2026-11-05, deferred the same day, reinstated 2026-11-09; not built):** a second exception, Chat Wallets. Through ADR-0012's route, an agent may make five fixed requests about Chat Wallets, a new and separate Kind for keys on EVM chains or Solana, at addresses that hold only small amounts. It can get back public addresses, limits left and signed transactions, each send approved by the owner with Touch ID and the Master Password. It never receives a key, an ordinary Note, metadata, Wallet Kinds, the Master Password or the Recovery Key. Everything else here stands. +> +> **Amended by [ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md) (proposed 2026-11-09; not built):** only Claude Code and Codex, started in the separate agent account, may use the agent routes, checked by their Apple code signatures. +> +> **Amended by [ADR-0014](0014-agent-key-operations-through-the-companion.md) (proposed 2026-11-09; not built):** Agent Keys for more services may be used through the Agent Companion, one fixed, owner-approved operation at a time, each meeting the requirements below. The agent never holds the key. Status: Accepted by the owner, 2026-10-21 ([issue #25](https://github.com/nniiovoo/encrypted-note/issues/25)). diff --git a/docs/adr/0008-agent-gateway-awaits-a-concrete-api.md b/docs/adr/0008-agent-gateway-awaits-a-concrete-api.md index 568d947..98442c0 100644 --- a/docs/adr/0008-agent-gateway-awaits-a-concrete-api.md +++ b/docs/adr/0008-agent-gateway-awaits-a-concrete-api.md @@ -4,6 +4,8 @@ > **Amended by [ADR-0010](0010-companion-source-lives-in-this-repository.md) (2026-11-01):** the private repository named below was merged into this one as `companion/`, and its code is now public. +> **Amended by [ADR-0014](0014-agent-key-operations-through-the-companion.md) (proposed 2026-11-09; not built):** the companion may carry more fixed operations, one owner-approved operation at a time, each with its own Agent Key and its own record. The operation below stays the first. + Status: Service and operation selected by the owner, 2026-10-21; companion source published privately and development/preparation behavior verified with 30 fictional tests. Actual OS isolation, protected execution, provider controls, a real-key pilot and optional ChatGPT cloud connection remain pending. Refs [issue #28](https://github.com/nniiovoo/encrypted-note/issues/28). The owner selected **OpenAI API text generation using one fixed model**, with a new separate restricted **Agent Key** and no owner-Vault access ([ADR-0007](0007-agents-use-separate-restricted-keys.md)). This replaces the earlier read-only-operation proposal. The selected implementation is a separate local companion whose primary interface is MCP over stdio. diff --git a/docs/adr/0009-optional-agent-companion-helper.md b/docs/adr/0009-optional-agent-companion-helper.md index 73c918a..68446ec 100644 --- a/docs/adr/0009-optional-agent-companion-helper.md +++ b/docs/adr/0009-optional-agent-companion-helper.md @@ -2,6 +2,8 @@ > **Amended by [ADR-0010](0010-companion-source-lives-in-this-repository.md) (2026-11-01):** the companion's source moved into this repository as `companion/`, so "One pinned source" below no longer pins a commit, and packaging no longer fetches the companion with git. The policy and Node.js hashes, the helper, its sandbox and the entitlements are unchanged. +> **Amended by [ADR-0014](0014-agent-key-operations-through-the-companion.md) (proposed 2026-11-09; not built):** "make only the fixed `generate_text` request" becomes "make only fixed operations the owner has approved one at a time", each with its own Agent Key, its own record and a pinned policy. The helper's agent entry also gets [ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md)'s check that it was reached from Claude Code or Codex. What never reaches the helper is unchanged. + Status: Accepted by the owner, 2026-10-26 (integration design and scoped exceptions approved). Implemented with fictional data only; real credential provisioning, OS account changes, live provider calls and the ChatGPT connection remain owner actions. The owner wants one place to manage the restricted OpenAI companion ([ADR-0008](0008-agent-gateway-awaits-a-concrete-api.md)) from inside encrypted-note, without giving it any part of the **Vault**. This records the one exception to "the app offers no agent interface" ([ADR-0007](0007-agents-use-separate-restricted-keys.md)), "no network code" ([ADR-0001](0001-local-only-vault-moved-by-hand.md)) and "every key in the Rust core" ([ADR-0003](0003-tauri-with-secrets-kept-in-rust.md)). Everything outside the exception stays as those decisions say. diff --git a/docs/adr/0011-chat-wallets.md b/docs/adr/0011-chat-wallets.md index 39facde..d00c3a5 100644 --- a/docs/adr/0011-chat-wallets.md +++ b/docs/adr/0011-chat-wallets.md @@ -1,35 +1,62 @@ # Chat Wallets: separate keys an agent can ask the app to use, with the owner's approval -Status: **Deferred by the owner, 2026-11-05**, the day it was accepted. The goal is now for agents to store and manage keys without using them ([ADR-0012](0012-agents-store-and-manage-keys.md)), so nothing here is built. It is kept as a record of the design, should signing come back. The owner's later answers (Touch ID with the Master Password, an encrypted send history, every EVM and Solana wallet rather than Base alone) would need folding in then. +Status: Accepted by the owner 2026-11-05, deferred the same day, and **reinstated 2026-11-09** with the owner's later answers folded in (see [Reinstated 2026-11-09](#reinstated-2026-11-09)). The reinstatement stands once the owner merges it. **Not built.** No real funds go to a Chat Wallet until the drill and the security review in the build order are done. -Amends [ADR-0007](0007-agents-use-separate-restricted-keys.md). Leaves [ADR-0004](0004-wallet-kinds-under-a-separate-key.md) and [ADR-0009](0009-optional-agent-companion-helper.md) as written. +Amends [ADR-0007](0007-agents-use-separate-restricted-keys.md) and [ADR-0012](0012-agents-store-and-manage-keys.md). Agents reach it only through the route built for ADR-0012, with the program check of [ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md). Leaves [ADR-0004](0004-wallet-kinds-under-a-separate-key.md) and [ADR-0009](0009-optional-agent-companion-helper.md) as written. The owner wants encrypted-note to be a place where keys are stored and managed by chatting with an AI agent, and used that way too, safely. In October 2026, design panels put "signing inside the Vault, or any signer on the Mac" and "the Master Password or Touch ID as approval for agent requests" on the do-not-build list. Their main finding still holds: **an approval given on this Mac is not independent of software running on it.** This decision accepts that limit for one narrow case. A separate set of keys sits at addresses holding only small amounts, can never touch **Wallet Kinds**, and accepts only simple sends that the app reads out itself. Its safety rests on how little each address holds, not on the approval panel. +## The owner's choice, in their words + +"Now I need it fully be used by Claude and Codex, not others." Asked which keys agents must use, the owner chose wallet keys, to sign sends (this decision), and API keys, to call services ([ADR-0014](0014-agent-key-operations-through-the-companion.md)). + ## Decision - **encrypted-note holds keys, not money.** Funds stay on the chain, at the key's address. The owner decides how much sits at each one, and keeps larger amounts on a hardware wallet that no agent reaches. -- **A Chat Wallet is a new, separate Kind.** It holds one private key for one account on one chain, made only for agent use. The app normally creates the key itself, so its value never exists anywhere else. It is encrypted under its own key, separate from ordinary **Notes** and from the Wallet Key ([ADR-0004](0004-wallet-kinds-under-a-separate-key.md)). A **Seed Phrase** or **Private Key** can never become a Chat Wallet, be read through the agent route, or sign through it. -- **Base only, in version 1.** Testing starts on Base Sepolia (chain ID 84532), then moves to Base (chain ID 8453). The chain ID is pinned, and the app refuses any other. -- **The agent asks; it never sees a key.** The agent route accepts six requests. Every other request is refused: +- **A Chat Wallet is a new, separate Kind.** It holds one private key for one account on one chain, made only for agent use. The app normally creates the key itself, so its value never exists anywhere else. It is encrypted under its own key, separate from ordinary **Notes** and from the Wallet Key ([ADR-0004](0004-wallet-kinds-under-a-separate-key.md)). A **Seed Phrase** or **Private Key**, including an **Agent-added Note**, can never become a Chat Wallet, be read through the agent route, or sign through it. +- **EVM chains and Solana.** Each Chat Wallet is made for one chain, chosen when it is created: any EVM chain, named by its chain ID, or Solana. + - **Testing first:** it starts on Base Sepolia (chain ID 84532) and Solana devnet. + - **EVM:** the chain ID is inside every signed transaction, so the app refuses any other chain. + - **Solana:** a transaction doesn't say which cluster it is for, so the app can't tell devnet from mainnet. A test Chat Wallet's key is a real key on every cluster, and must never hold real funds. +- **The agent asks; it never sees a key.** Five Chat Wallet requests join the five of [ADR-0012](0012-agents-store-and-manage-keys.md) on the same route. Every other request is refused: | Request | What the app does | What the agent gets back | |---|---|---| - | List Chat Wallets | Reads Chat Wallets only | Each one's name, chain, public address and the limit left today | + | List Chat Wallets | Reads only the Chat Wallets the current grant covers | Each one's name, chain, public address and the limit left today | | Create one | The owner confirms in a native panel, then the app creates the key | Name and public address | | Import one | Opens a native panel. The owner pastes the key and types the **Master Password** | "Saved" or "Cancelled" | | Rename one | The owner confirms in a native panel | "Done" or "Cancelled" | | Ask for a send | Checks the rules, then shows the approval panel | The signed transaction, or the reason it was refused | - | Save another key | Opens the normal Add window with a suggested name, and the owner types the value | "Saved" or "Cancelled" | - A key's value never travels through the chat, a tool result, a log or the clipboard. Ordinary **Notes**, their titles and other metadata, **Wallet Kinds**, the **Master Password** and the **Recovery Key** are never visible through this route. -- **Only plain transfers.** Version 1 signs a transfer of the chain's own coin, or of one listed token, to a listed recipient. It refuses contract calls, token approvals, message signing, typed-data signing (permits) and anything it can't fully decode. + Storing any other key is [ADR-0012](0012-agents-store-and-manage-keys.md)'s "Store a key". A key's value never travels through the chat, a tool result, a log or the clipboard. Ordinary **Notes**, their titles and other metadata, **Wallet Kinds**, the **Master Password** and the **Recovery Key** are never visible through this route. +- **Only plain transfers.** A send moves the chain's own coin, or one listed token, to a listed recipient, one transfer per transaction: + - **EVM:** an EIP-1559 transaction sending the coin, or calling `transfer` on a listed token contract. + - **Solana:** a System Program transfer of SOL, or an SPL Token `TransferChecked` of a listed token. + + The app refuses contract calls, token approvals, message signing, typed-data signing (permits), more than one transfer, and anything it can't fully decode. - **The app signs but never sends.** It returns the signed transaction, and the agent sends it to the network. The **Vault** core and the WebView stay offline ([ADR-0001](0001-local-only-vault-moved-by-hand.md)). -- **Limits the agent can't change.** The owner sets these for each Chat Wallet: a per-send limit, a daily limit and a list of allowed recipients. They are stored inside the encrypted **Vault**, not in the plain settings file. The app refuses any send that breaks them. They can be raised, or a recipient added, only in the app's own window, with the **Master Password**, and the change takes effect 24 hours later. Lowering a limit, removing a recipient or pausing a Chat Wallet takes effect at once. Deleting a Chat Wallet, or showing or copying its key, happens only in the app's own window. -- **Every send needs Touch ID and then the Master Password**, in a native panel, whatever the amount. The panel shows what the app decoded from the transaction itself, never the agent's description: chain, from, to (the recipient's saved name and full address), amount, fee and the limit left today. Touch ID here is an extra check, never a replacement for the Master Password. A Mac without working Touch ID can't sign Chat Wallet sends. +- **Limits the agent can't change.** The owner sets these for each Chat Wallet, in each asset's own units: + - a per-send limit; + - a daily limit; + - a list of allowed recipients. + + They are stored inside the encrypted **Vault**, not in the plain settings file, and the app refuses any send that breaks them. + - **Raising a limit or adding a recipient:** only in the app's own window, with the **Master Password**, and it takes effect 24 hours later. + - **Lowering a limit, removing a recipient or pausing a Chat Wallet:** takes effect at once. + - **Deleting a Chat Wallet, or showing or copying its key:** only in the app's own window. +- **Every send needs Touch ID and then the Master Password**, in a native panel, whatever the amount. + - **What the panel shows:** what the app decoded from the transaction itself, never the agent's description: chain, from, to (the recipient's saved name and full address), amount, fee and the limit left today. + - **Touch ID is an extra check**, never a replacement for the **Master Password**. A Mac without working Touch ID can't sign Chat Wallet sends. - **A send history.** Every signed send (time, Chat Wallet, recipient, amount, fee) is recorded encrypted in the **Vault**. It is shown only in the app's own window, never to the agent. -- **A separate route, off by default.** A local MCP tool talks to the app over a private local connection. It works only while the app is **Unlocked** and the owner has started a grant, which lasts at most one hour. It stops on Stop, on lock, on sleep and when the app quits. It accepts connections only from the separate standard agent account, and refuses every other caller, including programs in the owner's own account. It is not the **Agent Companion**, and shares no code, process or entitlement with it or with `companion/`. Mac only. -- **Other keys.** For Logins and API Keys, the agent can only open the normal Add window. Using API Keys from an agent stays on the **Agent Key** route ([ADR-0007](0007-agents-use-separate-restricted-keys.md)). +- **A grant covers only the Chat Wallets you choose.** Starting a grant ([ADR-0012](0012-agents-store-and-manage-keys.md)) covers no Chat Wallet unless the owner ticks it in Start's own dialog. List shows only those. A request about any other Chat Wallet gets the same answer as one that doesn't exist. +- **The same route, off by default.** Chat Wallet requests use [ADR-0012](0012-agents-store-and-manage-keys.md)'s socket, tool, grant and agent-account check, plus [ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md)'s check that the tool was started by Claude Code or Codex. + - **When it works:** only while the app is **Unlocked** and the owner has started a grant, which lasts at most one hour. + - **When it stops:** on Stop, on lock, on sleep and when the app quits. + - **What it shares:** nothing with the **Agent Companion**: no code, process or entitlement, and nothing from `companion/`. + - **Platform:** Mac only. +- **Other keys.** + - **Storing a Login or API Key:** the agent can ask to store one ([ADR-0012](0012-agents-store-and-manage-keys.md)). + - **Using an API key:** only through an **Agent Key** and the **Agent Companion** ([ADR-0014](0014-agent-key-operations-through-the-companion.md)), never an API Key stored in the **Vault**. ## Consequences @@ -37,28 +64,54 @@ The owner wants encrypted-note to be a place where keys are stored and managed b - **What a tricked agent can still do:** - ask for sends within the limits to listed recipients, which go through if the owner approves; - flood the owner with requests; - - learn Chat Wallet names, addresses and limits; + - learn the names, addresses and limits of the Chat Wallets a grant covers; - choose when, or whether, to send a signed transaction. Chat Wallet names should be chosen with that in mind. -- **The app gains its first way in from other programs**, in the process that holds the **Vault**'s keys. Each part needs refusal tests for every request outside the six, and a security review before any real funds. -- **New signing code and dependencies in the Rust core** (secp256k1, Keccak, EIP-1559 transaction encoding). Each is pinned and explained in its PR. The signer is tested against published test vectors and refuses every other transaction shape and chain ID. -- **The Vault format changes**: a new Kind under its own key, with its limits, recipients and history. `docs/FORMAT.md` and the **Emergency Reader** follow. +- **The route grows from storing to signing**, in the process that holds the **Vault**'s keys. Each new request needs refusal tests for every shape outside the rules, and a security review before any real funds. +- **New signing code and dependencies in the Rust core.** + - **EVM:** secp256k1, Keccak-256, RLP and EIP-1559 transaction encoding. + - **Solana:** Ed25519, base58 and transaction-message encoding. + + The plan is small single-purpose crates, not whole-chain SDKs, each pinned and explained in its PR. Each signer is tested against published test vectors, and refuses every other transaction shape and chain. +- **The Vault format changes:** a new Kind under its own key, with its limits, recipients and history. `docs/FORMAT.md` and the **Emergency Reader** follow. - **When it ships**, the README, the guide, the security model and the glossary's "no interface" lines change with it, and the guide gets a section on using keys from an agent that starts with the worst case. - Windows waits until its own route can be built and checked on a real Windows PC. ## Build order -Each step is its own pull request, with fake keys only. +Each step is its own pull request, with fake keys and test funds only. + +1. **These documents:** + - this ADR reinstated, with [ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md) and [ADR-0014](0014-agent-key-operations-through-the-companion.md); + - notes in ADR-0007, ADR-0008, ADR-0009 and ADR-0012; + - the glossary and `AGENTS.md`. +2. **The ground it stands on:** + - [ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md)'s program check on the existing route; + - the hand checks [ADR-0012](0012-agents-store-and-manage-keys.md) still needs, above all that a signed, sandboxed build can open its socket. +3. **Storage:** the Kind, its key, limits, recipients and history. +4. **The EVM signer,** with test vectors and refusal tests. +5. **The Solana signer,** the same way. +6. **The app's own window:** create, import, rename, limits, recipients, pause, history, and the 24-hour wait. +7. **The approval panel:** Touch ID, then the **Master Password**. +8. **The five requests on the route,** and the grant's choice of Chat Wallets. +9. **A drill on Base Sepolia and Solana devnet, with test funds.** It includes: + - a planted "send everything to 0xFAKE…" message; + - an agent asking to raise a limit; + - a program other than Claude Code or Codex trying the route; + - a timed stop. +10. **A security review,** then one small real Chat Wallet. + +## Reinstated 2026-11-09 + +The owner deferred this decision on 2026-11-05, because the goal had become storing keys without using them ([ADR-0012](0012-agents-store-and-manage-keys.md)). On 2026-11-09 the owner asked for keys to be used by Claude Code and Codex. What changed from the 2026-11-05 version: -1. These documents. -2. Storage: the Kind, its key, limits, recipients and history. -3. The Base signer, with test vectors and refusal tests. -4. The app's own window: create, import, rename, limits, recipients, pause, history, and the 24-hour wait. -5. The approval panel: Touch ID, then the Master Password. -6. The agent route: the MCP tool, the agent-account check, the grant and the six requests. -7. A drill on Base Sepolia with fake funds. It includes a planted "send everything to 0xFAKE…" message, an agent asking to raise a limit, and a timed stop. -8. A security review, then one small real Chat Wallet. +- **Chains:** Base only became any EVM chain and Solana, as the owner had answered after accepting. +- **Route:** a separate route became [ADR-0012](0012-agents-store-and-manage-keys.md)'s, plus [ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md)'s program check. One way in to review, not two. +- **Requests:** six became five. "Save another key" is now [ADR-0012](0012-agents-store-and-manage-keys.md)'s "Store a key". +- **New:** a grant covers only the Chat Wallets the owner ticks. +- **Kept from the owner's answers:** Touch ID and then the **Master Password** for every send, and the encrypted send history. +- **Kept as decided:** the limits, the 24-hour wait, plain transfers only, and an app that signs but never sends. ## Considered Options @@ -69,3 +122,5 @@ Each step is its own pull request, with fake keys only. - **No limits, only the approval panel.** Rejected by the owner: reading every panel carefully would be the only protection. - **Agent apps in the owner's own account.** Rejected by the owner: they can already do anything the owner can, including trying to drive the approval panel. - **Running this through the Agent Companion.** Rejected: [ADR-0009](0009-optional-agent-companion-helper.md) promises that helper never gets Vault access, and it is the one part of the app with network access. +- **Base only.** Replaced by the owner's later answer: every EVM chain and Solana. +- **A route of its own, apart from ADR-0012's.** Replaced now that ADR-0012's route is built: a second way into the process that holds the Vault's keys would double what needs review. diff --git a/docs/adr/0012-agents-store-and-manage-keys.md b/docs/adr/0012-agents-store-and-manage-keys.md index da09299..1bb2fe6 100644 --- a/docs/adr/0012-agents-store-and-manage-keys.md +++ b/docs/adr/0012-agents-store-and-manage-keys.md @@ -4,6 +4,8 @@ Status: Accepted by the owner, 2026-11-05. **Built 2026-11-06 with fictional dat Amends [ADR-0007](0007-agents-use-separate-restricted-keys.md). Defers [ADR-0011](0011-chat-wallets.md): signing is no longer the goal. +> **Amended 2026-11-09 (proposed; not built):** signing is the goal again. [ADR-0011](0011-chat-wallets.md) is reinstated, and its five Chat Wallet requests join this route, on the same socket, tool and grant. [ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md) adds a check that the tool was started by Claude Code or Codex. Everything below about storing and managing keys stands. + > **Contradicts [ADR-0007](0007-agents-use-separate-restricted-keys.md)** ("The app offers no agent interface… no Note enumeration or metadata export"). **Worth reopening because** the owner wants Claude, ChatGPT or Codex to help store and organise keys. This route lets an agent put keys **in**, and look after their names and details, but never take a value **out** or use one. So the worst a tricked agent can do is make a mess of the keys it added, not leak or spend them. ## The owner's goal, in their words diff --git a/docs/adr/0013-only-claude-code-and-codex-use-the-agent-routes.md b/docs/adr/0013-only-claude-code-and-codex-use-the-agent-routes.md new file mode 100644 index 0000000..3849e82 --- /dev/null +++ b/docs/adr/0013-only-claude-code-and-codex-use-the-agent-routes.md @@ -0,0 +1,56 @@ +# Only Claude Code and Codex may use the agent routes + +Status: Proposed 2026-11-09 from the owner's choices below; it stands once the owner merges it. **Not built.** + +Amends [ADR-0012](0012-agents-store-and-manage-keys.md) ("It accepts connections only from the agent account"). Applies equally to the Chat Wallet requests ([ADR-0011](0011-chat-wallets.md)) and to the Agent Companion's agent entry ([ADR-0014](0014-agent-key-operations-through-the-companion.md)). + +## The owner's choice, in their words + +"Now I need it fully be used by Claude and Codex, not others." Asked how to enforce that, the owner chose to keep the separate agent account **and also** check each program's signature, knowing the signature check is a speed bump rather than a wall. + +## Decision + +- **Three checks on every connection.** All three come from the kernel and macOS, never from anything the caller says about itself: + 1. **The account** is the separate agent account (as built for [ADR-0012](0012-agents-store-and-manage-keys.md)). + 2. **The connecting program** is the installed `enote-agent-keys` tool. Its code hash must match the one the owner approved in Settings, which the setup script prints. Once the tool is bundled inside the app and signed with the app's identity, the app checks that signature instead. + 3. **The program that started the tool** is Claude Code or Codex, checked by its Apple code signature against the list below. + + A connection that fails any check is closed without a reply, as one from the wrong account is today. +- **The allowed programs**, as found on the owner's Mac on 2026-11-09: + + | Program | Signed by | Team ID | Identifier | + |---|---|---|---| + | Claude Code (command line) | Developer ID Application: Anthropic PBC | `Q6L2SF6YDW` | `com.anthropic.claude-code` | + | Codex (command line, its native program) | Developer ID Application: OpenAI OpCo, LLC | `2DC432GLL2` | `codex` | + + The list is fixed in code: nothing the agent account can write changes it. The Claude app and the Codex app are added only after a check shows which of their processes starts the tool. ChatGPT and every other program are not on the list. +- **Codex installed with npm.** There the `codex` command is a Node.js script, which can't be signed. It starts Codex's signed native program, which is the program that starts the tool. The build step confirms this on a real run. +- **No fooling it with reused process numbers or injected code.** The connecting program is identified by the connection's audit token. The program that started it is identified by its process number, re-checked after the signature check so a reused number can't pass; the build PR shows how. A program whose signature lets outside code be loaded into it is refused: disabled library validation, allowed DYLD environment variables, or a debuggable signature. +- **Where the code lives.** In `src-tauri/src/agent_route.rs`, next to the existing peer checks, under the same placement rule in `scripts/check-network-source.mjs`. +- **A spike comes first.** It tests whether the sandboxed app may read another account's process information and code signatures. If it can't, the check isn't built that way, and this decision goes back to the owner. + +## Consequences + +- **A speed bump, not a wall.** It stops other programs in the agent account from using the route, whether directly or by running the tool. It doesn't stop a program in that account from starting Claude Code or Codex itself, with its own instructions, settings or MCP configuration. So the agent account must still hold only Claude Code and Codex. The separate account stays the real boundary. +- **It doesn't make Claude Code or Codex trustworthy.** A tricked Claude Code or Codex passes the check. Everything [ADR-0011](0011-chat-wallets.md), [ADR-0012](0012-agents-store-and-manage-keys.md) and [ADR-0014](0014-agent-key-operations-through-the-companion.md) say a tricked agent can still do stays true. +- **Updates can lock agents out.** If Anthropic or OpenAI change their signing identity, or the way the tool is started, the route refuses until a new build updates the list. It fails closed. +- **The app reads a little about other programs:** the connecting program's signature and code hash, and its parent's signature. Nothing else, and none of it leaves the app. + +## Build order + +Each step is its own pull request. + +1. These documents. +2. The spike, in a signed, sandboxed test build (a test identifier only, never the owner's Vault): can the app read what the check needs? +3. The check on the [ADR-0012](0012-agents-store-and-manage-keys.md) route, with tests: + - accepted: the right tool, started by Claude Code or by Codex; + - refused: the tool started by a shell or any other program, a program connecting directly, a wrong code hash, and a program whose signature allows outside code. +4. The same check on the Agent Companion's agent entry ([ADR-0014](0014-agent-key-operations-through-the-companion.md)). +5. Hand checks from Claude Code and Codex in the agent account, and from a third program that must be refused. + +## Considered Options + +- **The agent account alone (as built):** the owner asked for more. +- **Asking the caller who it is,** such as a name or token the agent sends: rejected. Any program can claim to be Claude Code. +- **Checking only the tool, not who started it:** rejected. Any program in the agent account can run the tool. +- **Checking the whole chain of parent processes:** not needed. The tool's direct parent must be on the list, so a shell or other program in between is refused. diff --git a/docs/adr/0014-agent-key-operations-through-the-companion.md b/docs/adr/0014-agent-key-operations-through-the-companion.md new file mode 100644 index 0000000..c6bc578 --- /dev/null +++ b/docs/adr/0014-agent-key-operations-through-the-companion.md @@ -0,0 +1,49 @@ +# Agents use API keys through the Agent Companion, one fixed operation at a time + +Status: Proposed 2026-11-09 from the owner's choices below; it stands once the owner merges it. **Not built, and no new operation is chosen yet.** + +Amends [ADR-0009](0009-optional-agent-companion-helper.md) ("make only the fixed `generate_text` request") and [ADR-0008](0008-agent-gateway-awaits-a-concrete-api.md). Every requirement of [ADR-0007](0007-agents-use-separate-restricted-keys.md) stands, and each new operation must meet it. + +> **Contradicts [ADR-0009](0009-optional-agent-companion-helper.md)** (the helper may "make only the fixed `generate_text` request") and the `AGENTS.md` rule "Never widen it". **Worth reopening because** the owner wants Claude Code and Codex to call services with API keys. [ADR-0007](0007-agents-use-separate-restricted-keys.md) already says how that can be done safely: a separate **Agent Key** with limits the provider enforces, kept out of chat. The Agent Companion is the one part of the project built for exactly that. It runs outside the Vault's process, in its own sandbox, and already holds an Agent Key without the agent ever seeing it. + +## The owner's choice, in their words + +"Now I need it fully be used by Claude and Codex, not others." Asked which keys agents must use, the owner chose API keys, to call services (this decision), and wallet keys, to sign sends ([ADR-0011](0011-chat-wallets.md)). + +## Decision + +- **The agent asks the helper; it never holds the key.** The agent calls a tool. The Agent Companion makes the request with the Agent Key and returns only the result fields that operation's record allows. The key never appears in the chat, a tool result or a log. +- **Only Agent Keys, never the owner's own** ([ADR-0007](0007-agents-use-separate-restricted-keys.md)). Each operation uses a key the owner creates at the service just for it, with permissions, expiry, revocation and hard caps that the provider enforces. An API Key stored as a **Note** is never used, copied or offered to the helper. [ADR-0009](0009-optional-agent-companion-helper.md)'s list of what never reaches the helper stands. +- **One fixed operation at a time.** Each new operation needs: + - its own record, the table of [ADR-0007](0007-agents-use-separate-restricted-keys.md): operation and resources, expiry, revocation, caps, results and where they go; + - the owner's approval of that record; + - its own pull request. +- **The helper builds every request in its own code.** The agent chooses no URL, method, header, host or body template. There is no general HTTP proxy and no following of redirects. The helper's policy file lists every operation and destination, and the app checks it against a pinned hash, as it does today. +- **If the provider can't enforce the limits, there is no operation** ([ADR-0007](0007-agents-use-separate-restricted-keys.md)). Narrow permissions, expiry, revocation and hard caps are checked against the provider's current documentation before any pilot. +- **Keys stay with the helper.** The owner sets up each Agent Key through Settings, as the first one is set up today. It is kept in the helper's own container, with the custody limits of [ADR-0009](0009-optional-agent-companion-helper.md): an owner-only file there, and held in memory while the helper runs. The **Vault** never holds or reads an Agent Key. +- **Only Claude Code and Codex, in the agent account.** The helper's agent entry gets [ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md)'s program check before any new operation goes live. Today the agent account's bridge reaches the helper on loopback TCP (`127.0.0.1:8789`). If the check can't be made reliably there, the bridge moves to a Unix socket in an owner-made folder; that step's PR decides which. +- **One grant, covering the operations you choose.** The existing grant (at most one hour, ended by Stop, any lock, sleep and quit) covers only the operations the owner ticks when starting it. + +## Consequences + +- **What leaves the computer:** what the agent supplies goes to that service. The result returns to the agent and may reach its AI company. Vault content never does. +- **A hidden key keeps its full authority.** An agent can make every call the operation and the key's provider scope allow, up to the caps ([ADR-0007](0007-agents-use-separate-restricted-keys.md)). Caps and expiry make a loss smaller, not impossible. +- **The helper's network reach grows** with each operation, which adds a destination. Each one updates the helper's policy and its pinned hash, and gets its own review. The Vault core and the WebView stay offline ([ADR-0001](0001-local-only-vault-moved-by-hand.md)). +- **The first new operation isn't chosen.** It needs a service whose provider enforces narrow permissions, expiry, revocation and hard caps. The owner picks it, and its record is checked against the provider's current documentation before anything is built. + +## Build order + +Each step is its own pull request, with fake keys only until the pilot. + +1. **These documents.** +2. **[ADR-0013](0013-only-claude-code-and-codex-use-the-agent-routes.md)'s check on the helper's agent entry,** or the move to a Unix socket. +3. **The first operation's record:** the owner picks the service and the operation, and the record is written and checked against the provider's current documentation. +4. **The operation in the helper,** with fictional tests and refusal tests: other URLs, methods, hosts and fields. +5. **A pilot with a new, hard-capped Agent Key,** then a review before wider use. + +## Considered Options + +- **Give the agent the Agent Key itself:** rejected. Raw keys stay out of anything the agent sees ([ADR-0007](0007-agents-use-separate-restricted-keys.md)), and through the chat a key reaches the AI company. +- **Let the Vault app make the call with an API Key stored as a Note:** rejected. That would put network code in the process that holds the Vault's keys ([ADR-0001](0001-local-only-vault-moved-by-hand.md), [ADR-0003](0003-tauri-with-secrets-kept-in-rust.md)), and the owner's own keys must never be used by agents. +- **A new, separate broker program:** not chosen. It would repeat the companion's sandbox, grant and setup, leaving two helpers to review instead of one. +- **A general HTTP proxy with a list of allowed hosts:** rejected. The agent could still choose any operation the key allows on those hosts.