From 474191f4e55400b599d842393a255c2072a0f371 Mon Sep 17 00:00:00 2001 From: NNIIOVO Date: Thu, 5 Nov 2026 21:06:23 -0600 Subject: [PATCH] docs: accept Chat Wallets (ADR-0011), not built yet The owner accepted a narrow route by which an agent in the separate agent account can ask the app to use separate small-amount keys on Base: six fixed requests, plain transfers only, owner limits and recipient list, and Touch ID plus the Master Password on every send. The agent never sees a key, and Seed Phrases and Private Keys never take part. Adds ADR-0011, amends ADR-0007, adds a related note to ADR-0004, the Chat Wallet glossary entry, an AGENTS.md rule, and one line each in the README, PRODUCT.md and the guide. Docs only: no code changes. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 1 + CONTEXT.md | 4 ++ PRODUCT.md | 2 + README.md | 1 + .../0004-wallet-kinds-under-a-separate-key.md | 2 + ...007-agents-use-separate-restricted-keys.md | 2 + docs/adr/0011-chat-wallets.md | 71 +++++++++++++++++++ docs/guide.md | 2 +- 8 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 docs/adr/0011-chat-wallets.md diff --git a/AGENTS.md b/AGENTS.md index 3f405d3..f3a16e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,6 +11,7 @@ A personal app for storing secrets, encrypted and saved only on this computer. - 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 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). +- Chat Wallets (ADR-0011, accepted, not built) are the only route by which an agent may ask the app to use a key. Keep that route to its six requests, only from the separate agent account, behind a grant. It returns no key, ordinary Note, metadata, Wallet Kind, Master Password or Recovery Key, signs only plain Base transfers after Touch ID and the Master Password, and shares nothing with `companion/`. Never let a Seed Phrase or Private Key sign or become a Chat Wallet, and never let the route change limits or recipients. - 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 4531d83..771bebd 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -66,6 +66,10 @@ _Avoid_: sharing an owner key, agent access to the Vault 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)). _Avoid_: agent bridge (that's the agent account's side), Vault connector, plugin +**Chat Wallet**: +A planned **Kind** (accepted, not built) 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)). +_Avoid_: agent wallet, hot wallet, using a **Seed Phrase** or **Private Key** from chat + ### Screen privacy **Capture Hiding**: diff --git a/PRODUCT.md b/PRODUCT.md index b82ad86..6cb5183 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -10,6 +10,8 @@ An individual owner keeping personal secrets on their own Mac or Windows compute The optional Agent Companion serves a separate workflow: an owner gives tools in a distinct standard Mac account one restricted OpenAI text operation using a separately created Agent Key. An agent has no app interface to the Vault. The first private Agent Key entry flow is Mac only. +Planned, accepted, not built: Chat Wallets ([ADR-0011](docs/adr/0011-chat-wallets.md)). The owner stores and manages separate small-amount keys by chatting with an agent. The agent asks, the app shows what will happen in its own native panel, and the owner approves every send with Touch ID and the Master Password. The agent never sees a key. + ## Product Purpose Keep Notes encrypted locally and make access, locking, saving and recovery understandable. The Vault and its WebView remain offline. The optional companion's fixed network operation belongs to its separate sandbox and never receives Vault content or metadata. diff --git a/README.md b/README.md index 11930ca..638cedf 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ These are instructions for AI coding agents working in this repository, in [AGEN - Never open, list, copy, decrypt, delete, move, overwrite or clean a real Vault, Backup or Safety Copy, or the app's data folders, 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. - Launch a release build only with a test identifier, never the one that opens your real Vault folder. - Keep the Vault core and WebView offline: no HTTP clients, updaters, telemetry, crash reporters or analytics. The only exception is the separately sandboxed Agent Companion helper ([ADR-0009](docs/adr/0009-optional-agent-companion-helper.md)), which never receives Vault content. +- Chat Wallets ([ADR-0011](docs/adr/0011-chat-wallets.md)) are accepted but not built. They will be separate keys at addresses holding small amounts, which an agent can ask the app to use, with your Touch ID and Master Password for every send. Keep that route narrow, and never let a Seed Phrase or Private Key from your Vault take part. - Keep keys and decrypted Notes in the Rust core. The WebView receives only one Hidden Field at a time. - Add dependencies only when clearly needed, pin exact versions, and explain why in the PR or local issue. 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 38bfb3e..7d3c111 100644 --- a/docs/adr/0004-wallet-kinds-under-a-separate-key.md +++ b/docs/adr/0004-wallet-kinds-under-a-separate-key.md @@ -1,5 +1,7 @@ # Wallet Kinds are encrypted under a separate key that unlocking doesn't open +> **Related: [ADR-0011](0011-chat-wallets.md) (2026-11-05, not built yet).** The app will sign transactions for one new Kind, the Chat Wallet, under its own key, separate from the Wallet Key. **Seed Phrases** and **Private Keys** are unchanged: they never sign, and never become Chat Wallets. + 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. ## Consequences diff --git a/docs/adr/0007-agents-use-separate-restricted-keys.md b/docs/adr/0007-agents-use-separate-restricted-keys.md index ebb13cf..c8b10d2 100644 --- a/docs/adr/0007-agents-use-separate-restricted-keys.md +++ b/docs/adr/0007-agents-use-separate-restricted-keys.md @@ -1,6 +1,8 @@ # Agents use separate restricted Agent Keys; the owner Vault stays out of reach > **Amended by [ADR-0009](0009-optional-agent-companion-helper.md) (2026-10-26):** the owner approved one exception to "no helper": the optional Agent Companion helper, bundled in its own sandbox and managed from Settings. It still has no route to the Vault, its Notes, their metadata, Wallet Kinds, the Master Password or the Recovery Key. Everything else here stands. +> +> **Amended by [ADR-0011](0011-chat-wallets.md) (2026-11-05, not built yet):** the owner approved a second exception, the Chat Wallet route. 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. Status: Accepted by the owner, 2026-10-21 ([issue #25](https://github.com/nniiovoo/encrypted-note/issues/25)). diff --git a/docs/adr/0011-chat-wallets.md b/docs/adr/0011-chat-wallets.md new file mode 100644 index 0000000..864f96b --- /dev/null +++ b/docs/adr/0011-chat-wallets.md @@ -0,0 +1,71 @@ +# Chat Wallets: separate keys an agent can ask the app to use, with the owner's approval + +Status: Accepted by the owner, 2026-11-05. **Not built yet.** Until it is, the app still gives agents no interface to the **Vault**, and the README, guide and security model describe the app as it is today. + +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. + +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. + +## 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: + + | 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 | + | 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. +- **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. +- **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)). + +## Consequences + +- **The worst case for each Chat Wallet is whatever sits at its address.** A tricked agent, a careless approval, or malware that has captured the **Master Password** and can reach the Touch ID prompt can empty it. The limits, the panel and the 24-hour wait make losses smaller, slower and less likely. They don't make them impossible. +- **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; + - 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. +- **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. + +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. + +## Considered Options + +- **The agent sees or receives the key**, by pasting it into chat or through a tool result. Rejected: the key reaches the AI company. +- **The agent uses Seed Phrases or Private Keys already in the Vault.** Rejected: the worst case becomes everything on that seed, on every chain. +- **A separate pot, approved on a hardware wallet.** Stronger, because the approval doesn't depend on the Mac, but nothing is stored or used through the app. It stays the route for larger amounts. +- **Touch ID alone, or Touch ID only for small sends.** Rejected by the owner: Touch ID is easy to give without reading. +- **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. diff --git a/docs/guide.md b/docs/guide.md index 58ee13b..2ceb09f 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -38,7 +38,7 @@ Time Machine or other full-computer backups may also copy these folders. Your Va ## AI tools on this computer -encrypted-note gives AI agents no interface to your Vault. That limits the app, not your computer. Apart from the optional [Agent Companion](agent-companion.md), it has no tool, server or file for them, and the Companion's one text tool can't open your Vault. That doesn't stop an AI tool that runs in your own account on this computer, such as Claude Code, Codex or Cursor. It works as you, so it may be able to: +encrypted-note gives AI agents no interface to your Vault. (Chat Wallets, accepted in [ADR-0011](adr/0011-chat-wallets.md), will add one narrow route for separate keys holding small amounts. They aren't built yet.) That limits the app, not your computer. Apart from the optional [Agent Companion](agent-companion.md), it has no tool, server or file for them, and the Companion's one text tool can't open your Vault. That doesn't stop an AI tool that runs in your own account on this computer, such as Claude Code, Codex or Cursor. It works as you, so it may be able to: - **Copy your Backups.** They stay encrypted, but whoever has a copy can keep guessing passwords on it for as long as they like, and it keeps opening with the Master Password and Recovery Key it was saved with. - **Copy your Vault folder.** The copy is encrypted, like a Backup. For the built Mac app on macOS 14 or later, macOS should ask before Terminal or another app opens that folder. If you allow Terminal, every tool you run in it gets that access until Terminal quits, and an app with Full Disk Access is never asked. On macOS 13, and for a release build that isn't sandboxed or a test run (Vault in `~/Library/Application Support`), nothing asks.