docs: document two-key privilege model
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+52
-17
@@ -62,23 +62,49 @@ The binary is organized into independently testable packages:
|
||||
human-readable/TUI).
|
||||
|
||||
### Trust boundary
|
||||
- The agent invokes only the **agent commands** (Section 7.1).
|
||||
- `EMCLI_KEY` is supplied by the environment/orchestrator that launches `emcli`, never as
|
||||
an argument the agent constructs. The agent has no command that reveals secret values.
|
||||
- All policy decisions happen inside `emcli`; the agent cannot bypass them because it has
|
||||
no other path to the mail servers.
|
||||
|
||||
Two keys enforce a hard privilege split — this is not convention; it is structurally enforced by
|
||||
the DEK-wrapping scheme:
|
||||
|
||||
- **`EMCLI_ADMIN_KEY`** — base64-encoded 32-byte key held by the human operator. Authorises ALL
|
||||
commands. Admin commands unwrap the DEK from the `dek_wrap_admin` slot only; there is no fallback
|
||||
to the agent slot. A process holding only `EMCLI_KEY` cannot run admin commands.
|
||||
- **`EMCLI_KEY`** — base64-encoded 32-byte key supplied to the agent orchestrator. Authorises agent
|
||||
commands (`list`, `get`, `search`, `ack`, `send`, `doctor`) only. `EMCLI_ADMIN_KEY` is a superset:
|
||||
a process with only the admin key can also run agent commands.
|
||||
- Agent commands use `EMCLI_KEY`; if only `EMCLI_ADMIN_KEY` is set, they fall back to it.
|
||||
If neither key satisfies the required slot, `emcli` exits with:
|
||||
`emcli: this command requires EMCLI_ADMIN_KEY (admin privilege)`.
|
||||
- `EMCLI_KEY` is supplied by the orchestrator that launches `emcli`, never as an argument the agent
|
||||
constructs. The agent has no command that reveals secret values.
|
||||
- All policy decisions happen inside `emcli`; the agent cannot bypass them because it has no other
|
||||
path to the mail servers.
|
||||
|
||||
## 5. Configuration & secrets
|
||||
|
||||
- **Encryption key:** `EMCLI_KEY` env var, a base64-encoded 32-byte key (AES-256). If
|
||||
absent or malformed, every command that touches the DB fails closed with an error
|
||||
envelope; no plaintext fallback.
|
||||
- **Admin key:** `EMCLI_ADMIN_KEY` env var, a base64-encoded 32-byte key (AES-256). Required for
|
||||
admin commands and for `init`. If absent or malformed when an admin command is attempted, the
|
||||
command fails with `emcli: this command requires EMCLI_ADMIN_KEY (admin privilege)`.
|
||||
- **Agent key:** `EMCLI_KEY` env var, a base64-encoded 32-byte key (AES-256). Required for agent
|
||||
commands. If absent or malformed, every agent command fails closed with a `config` error envelope;
|
||||
no plaintext fallback. `EMCLI_ADMIN_KEY` is accepted as a fallback for agent commands when
|
||||
`EMCLI_KEY` is not set.
|
||||
- **Database path:** `EMCLI_DB` env var; default `~/.config/emcli/emcli.db`
|
||||
(`%AppData%\emcli\emcli.db` on Windows).
|
||||
- **Field-level encryption:** secret columns are stored as AES-256-GCM ciphertext with a
|
||||
random 96-bit nonce per value, prefixed to the ciphertext. Non-secret config remains
|
||||
plaintext for debuggability. Decryption with the wrong key fails (GCM auth tag) and is
|
||||
surfaced as an error, never silently ignored.
|
||||
- **Envelope encryption (DEK):** `emcli init` generates a random data-encryption key (DEK) that
|
||||
protects all account secrets. The DEK is stored in the `settings` table sealed under both keys:
|
||||
- `dek_wrap_admin` — the DEK encrypted under `EMCLI_ADMIN_KEY` (AES-256-GCM).
|
||||
- `dek_wrap_agent` — the DEK encrypted under `EMCLI_KEY` (AES-256-GCM).
|
||||
The DEK is never written in cleartext. Admin commands unwrap from `dek_wrap_admin` only; agent
|
||||
commands unwrap from `dek_wrap_agent` (or `dek_wrap_admin` if only the admin key is present).
|
||||
There is no cross-slot fallback for admin commands — a holder of only `EMCLI_KEY` cannot unwrap
|
||||
the admin DEK slot.
|
||||
- **`init` idempotency:** re-running `emcli init` does not regenerate the DEK; the existing wrapped
|
||||
DEK rows are preserved.
|
||||
- **Field-level encryption:** secret columns are encrypted with the DEK using AES-256-GCM with a
|
||||
random 96-bit nonce per value, prefixed to the ciphertext. Non-secret config remains plaintext for
|
||||
debuggability. Decryption with the wrong key fails (GCM auth tag) and is surfaced as an error,
|
||||
never silently ignored.
|
||||
|
||||
Secret columns: account password, OAuth client secret, OAuth refresh token.
|
||||
|
||||
@@ -142,7 +168,9 @@ audit_log
|
||||
settings
|
||||
key TEXT PK
|
||||
value TEXT
|
||||
-- includes: audit_retention_days, schema_version
|
||||
-- includes: audit_retention_days, schema_version,
|
||||
-- dek_wrap_admin (DEK sealed under EMCLI_ADMIN_KEY),
|
||||
-- dek_wrap_agent (DEK sealed under EMCLI_KEY)
|
||||
```
|
||||
|
||||
Notes:
|
||||
@@ -216,8 +244,11 @@ command that advances read state is `ack`.
|
||||
|
||||
### 7.2 Admin commands (human-readable / TUI)
|
||||
|
||||
- **`emcli init`** — TUI flow: creates the DB (generating schema), adds the first account,
|
||||
and runs OAuth consent if the account is OAuth2.
|
||||
Require `EMCLI_ADMIN_KEY`.
|
||||
|
||||
- **`emcli init`** — TUI flow: creates the DB (generating schema + DEK), adds the first account,
|
||||
and runs OAuth consent if the account is OAuth2. Requires both `EMCLI_ADMIN_KEY` and `EMCLI_KEY`
|
||||
(writes both DEK wrap slots). Idempotent — re-running does not regenerate the DEK.
|
||||
- **`emcli account add | edit | remove | list`** — interactive add/edit; `list` prints a
|
||||
table (never secrets). `account add` accepts `--process-backlog` (default off) which sets
|
||||
the account's baseline policy: off ⇒ newly-seen folders floor at their current max UID
|
||||
@@ -225,8 +256,12 @@ command that advances read state is `ack`.
|
||||
- **`emcli whitelist in|out add|remove|list --account <name>`** — manage whitelist entries.
|
||||
- **`emcli config set|get`** — global settings (e.g. `audit_retention_days`).
|
||||
- **`emcli audit list [--account <name>] [--limit N]`** — view recent audit entries.
|
||||
- **`emcli doctor`** — verifies `EMCLI_KEY` is present and valid, the DB opens, and each
|
||||
account's IMAP/SMTP connectivity and auth succeed. Human-readable diagnostics.
|
||||
|
||||
### 7.2a `doctor` — agent-role diagnostics
|
||||
|
||||
`doctor` is authorised by `EMCLI_KEY` (or `EMCLI_ADMIN_KEY`). It verifies the key is present and
|
||||
valid, the DB opens, and each account's IMAP/SMTP connectivity and auth succeed. Prints
|
||||
human-readable diagnostics. Can be run by the agent or by a human; does not require admin privilege.
|
||||
|
||||
### 7.3 Defaults & limits
|
||||
- `list --limit` default: 50; maximum: 500.
|
||||
|
||||
Reference in New Issue
Block a user