Retire ACP/Hermes/OpenCode surfaces and rename hermes_tools to mnote_agent_tools so Page AI stays on Pi Lab only. Add Chrome vault extension + extension token route, pre-release purge design, and soft-retire legacy smokes for the small-group production cut.
24 KiB
12-2 [process] 本地 vaultd + Agent Token 读密路径(不依赖 mnote-web)v1
创建时间:2026-07-23
状态:PROCESS(P0 L-Access 已落地;P1a–P1d 已落地:web AI list/get/resolve/login/session → core;mnote-vault serveUDS + CLI sock→内嵌 fallback;login/session 不依赖 mnote-web)
Owner:12-vault
建议 repo 落点:design/12-vault/process/12-2-vaultd-local-token-agent-read-path-v1.md上位依据:
design/12-vault/process/12-1-password-vault-dedicated-crud-workbench-v1.md(vault 系统空间、AI 密码本策略、文件真源)/home/lix/.agent-infra/vault-policy.md(多 agent 唯一策略 SSOT)skills/mnote-vault/$mnote-vaultdesign/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.md(禁裸读敏感路径)ARCHITECTURE.mdlocal-first 原则触发证据:
- Paseo session
21242fc0-db03-454c-a1e5-723688351ee7:取密依赖 mnote-web 存活 +auth-e2ecookie,步骤膨胀、失败面大。- 用户目标:读库本质是本地文件;解密/受控读取只需轻量常驻服务,不应绑庞大 mnote-web;agent 内部保存 token 做授权取密。
1. Overview
1.1 一句话
文件是库;
mnote-vaultd是受控读密网关(及未来 master key 匣);agent 只持 capability token,不持 master key,也不依赖 mnote-web 进程。
1.2 与 12-1 的关系
| 维度 | 12-1(已落地主线) | 12-2(本文) |
|---|---|---|
| 真源 | {workspaceRoot}/.mnote/vault/** 结构化文件 |
不变 |
| 人用 UI | /vault + mnote-web /api/vault/* |
保留;不作为 agent 读密必经 |
| AI 策略 | 单 AI 本 + 单 resolve 通道 + 禁扫 vault 文件 | 策略不变;transport 换 |
| Agent 通道 | HTTP → mnote-web(需 cookie / auth-e2e) | UDS/loopback → vaultd(Bearer token) |
| 常驻进程 | 整站 mnote-web | 可选 极小 vaultd |
12-1 P1「多 agent 唯一策略」中的语义(只读 AI 本、resolve 唯一取密、禁通用 file)冻结保留。
本文只改 谁持进程、用什么凭证、是否依赖 3000。
1.3 现状诚实声明(实现前必读)
当前实现(2026-07):
- 条目为 workspace 下 结构化 Markdown + frontmatter(
mnote.kind: credential)。 cipher-book.json是 片段占位符展开(如[A]→ 共享串),不是 磁盘 AES 整库加密。- 尚无「用户 master password → 内存 master key → 密文 at rest」完整链路。
- 「解密」在产品语言上 = load 文件 + cipher 展开 + nested secret 解析 + 策略门闩。
因此 12-2 分两层能力,禁止混称:
| 层 | 名称 | P0 是否必须 | 说明 |
|---|---|---|---|
| L-Access | 受控读密网关 | 是 | token 鉴权、只暴露 list/get/resolve、审计、不依赖 mnote-web |
| L-Crypto | 真·at-rest 加密 | P2 可选 | master key 仅驻 vaultd 内存;token 永不等于 master key |
P0 交付 L-Access 即可消除「3000 挂了就不能取密」。
L-Crypto 在文件格式与迁移就绪后再做,不阻塞 agent 读路径瘦身。
2. Goals / Non-Goals
2.1 Goals
- Agent 读密(list / get / resolve / login 所需密钥字段)不依赖 mnote-web 运行。
- 常驻面缩小为
mnote-vaultd(或等价 one-shot CLI 内嵌同一 core,见 §5.4),不跑 SSR/Sidebar/文档编辑。 - Agent 内部保存 vault_token;每次调用带 token;禁止把 master password / master key 写入 agent 配置。
- 保持 12-1 策略:仅 AI 密码本 actor;禁通用工具扫
.mnote/vault/**;resolve 审计不落明文到日志。 - CLI / Pi 工具 / skill 语义对齐:稳态 1~2 次调用完成取密(已知 id 时仅
resolve)。 - mnote-web UI 与 human CRUD 可继续用现网路径;演进期可与 vaultd 并行,终态共享
mnote-vault-core。
2.2 Non-Goals(本设计不承诺)
- 不重做
/vault工作台 UI。 - 不把 vault 变成 graph /
tree.*节点。 - 不要求远程多机共享 vaultd(本机 local-first;跨机另案)。
- 不在 P0 强制引入 at-rest 加密迁移(L-Crypto = P2)。
- 不取消「禁扫 vault 文件」策略(即便未来密文 at rest,仍禁止 agent 把 vault 目录当通用笔记读)。
- 不把 Cloudflare 过人机验证自动化为无人值守(仍 human →
session回写)。
3. 问题根因(为何要拆)
今天 Agent 取密关键路径:
skill/policy
→ mnote-vault-cli auth-e2e # 需要 mnote-web + 用户 auth API
→ list / resolve HTTP # 需要 3000 存活 + cookie
→ (可选)login / session
失败面:
- web 未启动 / 崩溃 / 端口占用
- cookie eval / FORCE_COLOR / 环境变量污染
- agent 探索路径、读 policy、猜 URL → 工具次数爆炸
根因归类:
| 误绑定 | 正确归属 |
|---|---|
| 读本地文件 → 需要 Web 服务器 | 读文件 → 本地 IO + 可选小网关 |
| Agent 身份 → mnote-web session cookie | Agent 身份 → vault capability token |
| 解密权 → 与 SSR 同进程 | 解密/受控 resolve → vaultd(或 CLI 内嵌 core) |
4. 目标架构
4.1 分层图
┌──────────────────────────────────────────────────────────────┐
│ Agent runtime(Grok / Codex / Hermes / Pi / Paseo) │
│ - 持有 vault_token(env / 0600 文件 / 可选 keyring) │
│ - 薄客户端:CLI 或 Pi tool → 只打 vaultd │
│ - 禁止:通用 file 扫 .mnote/vault/** │
└────────────────────────────┬─────────────────────────────────┘
│ Authorization: Bearer <token>
│ 优先 Unix Domain Socket
▼
┌──────────────────────────────────────────────────────────────┐
│ mnote-vaultd(常驻 · 小 · 本机) │
│ - 校验 token(iss/aud/exp/scope/actor) │
│ - 解析 vault root(workspace / AI actor root) │
│ - load index / credential md + cipher expand + nested secret │
│ - L-Crypto 时:内存持 master key;lock 清空 │
│ - 审计 jsonl(无 plaintext value) │
│ - 不托管:SSR、树、Page AI、control-plane 全站逻辑 │
└────────────────────────────┬─────────────────────────────────┘
│ 受控读写 FS
▼
┌──────────────────────────────────────────────────────────────┐
│ 本地文件真源(SSOT,与 12-1 相同) │
│ {workspaceRoot}/.mnote/vault/ │
│ vault-index.json │
│ credentials|*.md(条目) │
│ cipher-book.json │
│ audit.jsonl │
│ AI 本:MNOTE_AI_VAULT_ACTOR 对应 root(实现沿用 12-1) │
└──────────────────────────────────────────────────────────────┘
旁路(可选,非读密阻塞路径):
mnote-web /vault UI、human CRUD、共享到 AI 本
Chrome 扩展保存到 vault(人用 human token,见 12-3;**禁止**复用本设计 agent token)
登录态落盘:sessions/{credId}/{accountId}.json(12-3 §5.6);login/session CLI 读该路径,fallback frontmatter loginSession
vault-login-helper resolve 后外站登录 / session 回写
4.2 两类密钥(禁止混用)
| 名称 | 持有者 | 用途 | 落盘? |
|---|---|---|---|
| Master key(L-Crypto) | 仅 vaultd 内存 | 解密 at-rest 密文包 | 否(仅 unlock 后内存) |
| Vault token(capability) | Agent 配置 / runtime | 向 vaultd 证明「谁、何 scope、何 actor」 | 可:~/.config/mnote/vault-tokens/*.token,0600 |
硬规则:
- Token 不能派生或编码 master key。
- 泄露 token ⇒ 在 vaultd 存活且已 unlock 时可 resolve;不能离线解密磁盘(L-Crypto 后更强)。
- 泄露 master password ⇒ 可 unlock 整库;不得写入任何 agent skill / env 默认模板。
4.3 Token 声明(逻辑 schema)
{
"v": 1,
"iss": "mnote-vaultd",
"aud": "local-vaultd",
"sub": "agent:paseo",
"actor": "mnote-e2e",
"scope": ["list", "get", "resolve", "login", "session"],
"workspace": "optional-stable-id-or-path-hash",
"iat": 0,
"exp": 0,
"jti": "uuid"
}
- 序列化:紧凑 JSON + HMAC-SHA256(本机 secret)或 ed25519(若未来多签发方)。
- 传输:
Authorization: Bearer <base64url(payload).base64url(sig)>(类 JWT 即可,不必上完整 OIDC)。 - P0 默认 scope:
list+get+resolve;login/session可同 token 或分 token。 actor必须等于 AI 密码本 actor(默认mnote-e2e,envMNOTE_AI_VAULT_ACTOR可覆盖签发时固化进 token)。
4.4 Bootstrap(token 从哪来)
一次(人或安装脚本):
1. 启动 vaultd(或 CLI doctor)
2. (L-Crypto)用户 unlock → master key 入内存
3. vaultd issue-token \
--agent paseo \
--actor mnote-e2e \
--scope list,get,resolve,login,session \
--ttl 90d
4. 写入 ~/.config/mnote/vault-tokens/default.token(0600)
5. 各 agent skill / env:MNOTE_VAULT_TOKEN_FILE 或 MNOTE_VAULT_TOKEN
日常 agent:
读 token → 调 resolve → 用完 value(不写聊天、不写无关日志)
轮换:revoke jti + 重新 issue;vaultd 维护 deny jti 集合(内存 + 可选小文件)。
5. mnote-vaultd 规格
5.1 进程与监听
| 项 | P0 决定 |
|---|---|
| 二进制名 | mnote-vaultd(或 mnote vault-daemon 子命令) |
| 默认传输 | Unix Domain Socket:$XDG_RUNTIME_DIR/mnote-vaultd.sock(回退 /tmp/mnote-vaultd-$UID.sock) |
| TCP 备选 | 127.0.0.1:17300,默认关;仅显式 --listen-tcp |
| 权限 | sock 0600、属主当前用户;拒绝非本 uid(若 OS 支持 peer cred) |
| 启动 | user systemd / 登录脚本 / 首次 CLI 自动拉起(见 §5.4) |
| 资源 | 目标:常驻 RSS 远小于 mnote-web;无浏览器、无 leptos SSR |
5.2 API 表面(JSON over HTTP/1.1 on UDS,或 length-prefixed JSON)
与现 Pi / CLI 语义对齐,路径前缀建议 /v1/:
| 方法 | 路径 | scope | 解密/展开 | 说明 |
|---|---|---|---|---|
| GET | /v1/health |
无 token 也可 | 否 | ok, unlocked, version, actorDefault |
| GET | /v1/items |
list |
否* | L0 列表;同现 list_ai |
| GET | /v1/items/{id} |
get |
否* | 元数据 + 掩码;无 password 明文 |
| POST | /v1/items/{id}/resolve |
resolve |
是 | body: field, accountId?, secretId? |
| POST | /v1/items/{id}/login |
login |
内部 resolve | 可 P1 仍代理或旁路 helper |
| POST | /v1/items/{id}/session |
session |
写 session | 人机回写 |
| POST | /v1/lock |
admin | — | 清 master key(L-Crypto) |
| POST | /v1/unlock |
admin | — | 本机交互/钥匙环;不给 agent 默认 scope |
| POST | /v1/tokens/issue |
admin | — | 签发 agent token |
| POST | /v1/tokens/revoke |
admin | — | 吊销 jti |
* list/get:若未来元数据也加密,仅解密标题等非 secret 字段;password 永不出现在 list/get。
错误码(稳定字符串,CLI/skill 可分支):
| code | HTTP | 含义 | Agent 动作 |
|---|---|---|---|
vaultd_unavailable |
— | sock 连不上 | 尝试 autostart 或提示用户起服务 |
vault_token_missing |
401 | 无 Bearer | 检查 token 文件 / env |
vault_token_invalid |
401 | 签名/格式错 | 重新 issue |
vault_token_expired |
401 | exp | 轮换 token |
vault_scope_denied |
403 | scope 不足 | 重新签发含所需 scope |
vault_actor_mismatch |
403 | token.actor ≠ AI 本 | 用正确 actor token |
vault_locked |
423 | L-Crypto 未 unlock | 提示用户 unlock(不是启动 mnote-web) |
vault_item_not_found |
404 | id 不存在 / 非 AI 本 | list 再选 |
vault_resolve_field_invalid |
400 | 字段不合法 | 对齐 schema |
vault_resolve_ai_only |
403 | 试图 resolve 用户本 | 策略拒绝(与 12-1 一致) |
5.3 与文件布局(概念,兼容 12-1)
{workspaceRoot}/.mnote/vault/ # SSOT,vaultd 与 mnote-web 共享读
vault-index.json
… credential markdown …
cipher-book.json
audit.jsonl # 可继续追加;vaultd 写 agent 通道审计
~/.config/mnote/
vaultd.toml # 可选:root 覆盖、ttl 默认、tcp 开关
vault-tokens/
default.token # agent 用,0600
vaultd-hmac.key # 签发 secret,0600;仅 vaultd/admin CLI 读
$XDG_RUNTIME_DIR/mnote-vaultd.sock
Workspace root 解析顺序(P0):
- 请求头 / CLI 显式
--workspace - env
MNOTE_VAULT_WORKSPACE vaultd.toml默认- 与现 mnote-web 一致的「当前 dev workspace」约定(实现时对齐
vault.rsAI root 解析,避免双根)
AI actor root:继续 MNOTE_AI_VAULT_ACTOR;token 内 actor 必须匹配。
5.4 无常驻时的退化(仍不依赖 mnote-web)
若用户不愿常驻 daemon:
mnote-vault-cli resolve --id X
→ 若 sock 可达:走 vaultd
→ 若不可达:CLI 内嵌 mnote-vault-core(同代码)+ 本机 token 校验
校验密钥来自 ~/.config/mnote/vaultd-hmac.key(用户可读则等同本机信任)
注意: 内嵌模式仍 禁止 无 token 裸读;仍 禁止 agent 用通用 Read 打开 vault 文件。
Autostart:CLI 可 spawn 一次 vaultd(user 级),避免每个 agent 拉起全站 3000。
5.5 login / session 边界
| 能力 | 建议 owner | 依赖 |
|---|---|---|
| resolve 密码字段 | vaultd 必含 | 仅本地文件 |
| list/get | vaultd 必含 | 仅本地文件 |
| 外站 login playbook | vaultd 插件 或 vault-login-helper |
网络 + 可选浏览器 |
| Cloudflare session 回写 | 同 login helper | 人 + chrome-bridge |
P0 最小闭环:health + list + get + resolve。
login/session 可 P0.5 从现 mnote-web 逻辑迁出或短期仍调 web(但 resolve 不得回退依赖 web)。
6. Agent 侧合同
6.1 环境变量
| 变量 | 含义 |
|---|---|
MNOTE_VAULT_TOKEN |
直接 token 字符串(会话级优先) |
MNOTE_VAULT_TOKEN_FILE |
默认 ~/.config/mnote/vault-tokens/default.token |
MNOTE_VAULT_SOCK |
覆盖 UDS 路径 |
MNOTE_VAULT_WORKSPACE |
workspace root |
MNOTE_AI_VAULT_ACTOR |
与签发 token 一致(文档提示;以 token.actor 为准) |
MNOTE_BASE_URL + MNOTE_COOKIE |
读密路径废弃;仅 human/web 调试保留 |
6.2 CLI 稳态(目标)
# 一次安装(人)
mnote-vault doctor # sock / token / unlock 状态
mnote-vault unlock # 仅 L-Crypto
mnote-vault issue-token … # 写入 token 文件
# Agent 每次取密(已知 id)
mnote-vault resolve --id <id> --field password [--account-id …] [--raw]
# 未知 id
mnote-vault list
mnote-vault resolve --id <id> --field password
删除读密对 auth-e2e 的依赖。
auth-e2e 可留作「测 mnote-web UI」工具,不得再出现在 $mnote-vault skill 稳态步骤。
6.3 Skill / policy 修订点(实现阶段同步,本文先钉文案)
/home/lix/.agent-infra/vault-policy.md 与 $mnote-vault 将改为:
- 读密:token + vaultd/CLI,不经 mnote-web。
- 仍禁扫
.mnote/vault/**。 - 登录仍优先
login;CF 仍 human +session。 - 故障树:
vault_locked→ 用户 unlock;vaultd_unavailable→ 起 vaultd / CLI 内嵌;不要默认desktop:hot。
6.4 Pi tools
| 现名 | 后端切换 |
|---|---|
mnote.vault.list|get|resolve|login|session |
默认连 vaultd;失败策略见 §5.4 |
| 鉴权 | Bearer token,不再注入 web cookie |
6.5 安全行为(agent)
- 聊天 / transcript:只写
transcriptHint,不写 password / cookieHeader。 - resolve value 仅用于当前工具链下一步(登录、填表),不回显用户除非用户明确要求。
- 禁止把 token 贴进公开 issue / 截图。
7. mnote-web 关系与迁移阶段
7.1 阶段
| 阶段 | 内容 | 退出标准 |
|---|---|---|
| A 现状 | Agent → mnote-web cookie → 文件 | — |
| B 并行(P0) | 抽出 mnote-vault-core;vaultd + CLI token 路径;web 仍可直读文件 |
不启 3000 可 resolve smoke 绿 |
| C 收口(P1) | web /api/vault/ai/* 改为调 core 或代理 vaultd;skill 去 auth-e2e |
双路径一致;旧 cookie AI 路径标 deprecated |
| D 可选(P2) | L-Crypto at-rest;web unlock UI 只负责把 key 交给 vaultd | 磁盘密文;无 key 不可读 |
7.2 代码落点(建议)
| crate / 路径 | 职责 |
|---|---|
rust/crates/mnote-vault-core(新) |
index/load/parse、cipher expand、nested secret resolve、audit append、(P2)encrypt |
rust/crates/mnote-vaultd(新)或 mnote-vault bin |
UDS 服务、token 校验、issue/revoke |
mnote-web vault_store |
逐步 thin wrapper → core;UI 路由保留 |
scripts/mnote-vault-cli.js |
默认 sock+token;保留 --via-web 仅调试 |
禁止: 在 vaultd 内重新实现第二套 frontmatter schema。
7.3 兼容
- 文件 schema(
mnote.vault.credential.v1等)不为 12-2 破坏性变更。 - 现有 AI 本条目无需迁移即可被 vaultd list/resolve(L-Access)。
- L-Crypto 若上:单独 migration 设计 + 双读窗口,不在本文展开实现细节,只保留扩展点。
8. 威胁模型(摘要)
| 威胁 | 缓解 |
|---|---|
| Agent prompt 注入要求 dump 全库 | 按条 resolve;list 无密;rate limit;审计 |
| Token 文件被同机其他用户读 | 0600 + UDS peer cred |
| Token 被复制到另一台机器 | aud=local-vaultd + 可选绑定 machine-id;无 hmac key 则签失败 |
| 磁盘被盗 | P0:OS 磁盘加密依赖;P2:vault at-rest |
| 恶意进程连 sock | 无有效 token 拒绝;sock 权限 |
| 日志泄漏 | audit 不写 value;CLI 默认 JSON 管道、skill 禁回显 |
| mnote-web RCE | 读密不依赖 web 后,攻击面与 key 分离(P2 更明显) |
| 用户把 vault 目录加进 agent 工作区 | 策略 + Pi allowlist deny;文档披露(12-1 已有) |
9. 验收标准(设计完成 → 实现门闩)
9.1 P0 必须
- mnote-web 未运行时,持有效 token 可
list+resolve成功(AI 本已有条目)。 - 无 token / 坏 token → 明确错误码,不回退匿名读文件。
- resolve 响应含 value;audit 无 value(沿用 store audit)。
- CLI 稳态文档与 skill:无
auth-e2e作为读密前置。 - 通用 file 工具读 vault 路径仍 deny(web 与 Pi 侧回归,既有策略)。
- 单元测试:token 校验、scope;store 原有 20 测。
- 集成 smoke:
cargo run -p mnote-vault -- …不启 3000(2026-07-23 本机绿)。
9.2 P1
- login/session 不依赖 web(core + CLI + UDS;外站 HTTP 仅 api_first 出站,resolve 仍本地)。
- mnote-web AI list/get/resolve/login/session thin-wrap 同一 core(2026-07-23)。
- policy / skill / Paseo appendSystemPrompt 全文切换(skill 已更新 login/session 本地路径;Paseo 段可再对齐)。
9.3 P2(L-Crypto)
- unlock 前 resolve →
vault_locked。 - master key 仅内存;lock 后不可 resolve。
- token 无法离线解密 credential 文件。
10. 实现 Checklist(批准设计后执行;本文件阶段只列不写代码)
PR-1 — core 抽出
- 新建
mnote-vault-core,从vault_store搬迁:load index、load credential、cipher expand、resolve_record_secret_field、AI actor root 解析。 - mnote-web AI 路径 thin-wrap core(list/get/resolve/login/session);CRUD/workbench 仍可双路径。
PR-2 — vaultd + token
- token issue/verify(HMAC + 文件格式
mnv1.*)。 - UDS server:health/list/get/resolve/login/session(
mnote-vault serve)。 - audit 写入(resolve/login/session 经 store)。
- CLI 默认 local-core + token(
mnote-vaultbin;sock 优先 fallback 内嵌)。
PR-3 — agent 面
- 更新
vault-policy.md、$mnote-vault(读密去 auth-e2e;login/session 本地 CLI)。 - Pi tool 仍进程内 thin-wrap core(不强制走 sock;可后置切 UDS)。
- smoke:不启 3000 → resolve / session+login reuse。
- TESTING_REFERENCE 增补 vault-local 段落(含 session/login)。
PR-4 — login 迁出
- login/session 进 core + CLI + UDS(api_first 出站 HTTP 在 core;human_required 仍人 + session 回写)。
- skill 表述:login/session 可本地、不依赖 mnote-web。
PR-5 — L-Crypto(另设计修订)
- 密文格式、unlock UI、迁移工具。
11. 待决问题(实现前钉死;默认推荐已标)
| # | 问题 | 推荐默认 |
|---|---|---|
| D1 | UDS vs TCP | UDS 默认;TCP 显式开 |
| D2 | Token 算法 | HMAC-SHA256 本机 secret |
| D3 | 无 daemon 时 CLI 内嵌 | 允许,同 token 校验 |
| D4 | login 是否进 P0 vaultd | P0 不含;P0 只 resolve 闭环 |
| D5 | web 是否立刻代理 vaultd | P1;P0 双路径并行 |
| D6 | AI root 与多 workspace | P0 单默认 workspace + env 覆盖 |
| D7 | L-Crypto 时间表 | 不进 P0;扩展点保留 |
| D8 | token TTL | 默认 90d;可 issue 无 exp 仅 dev |
若用户否定推荐,在本文修订表追加一行后再开 PR-1。
12. 修订记录
| 日期 | 说明 |
|---|---|
| 2026-07-23 | v1 初稿:本地文件 SSOT + vaultd + agent token;与 12-1 AI 策略对齐;L-Access P0 / L-Crypto P2;验收与 PR 切片 |
| 2026-07-23 | P0 落地:mnote-vault-core + mnote-vault CLI(issue-token/list/get/resolve);无 web smoke 绿;UDS vaultd / web 共 core 留 P1 |
| 2026-07-23 | P1d:login/session → core + CLI/UDS;web thin-wrap;session 回写 + login reuse 不依赖 mnote-web |
| 2026-07-24 | 交叉 12-3:登录态演进为 sessions/{credId}/{accountId}.json;CLI 语义不变,读序优先 session 文件 |
13. 批准栏(实现启动条件)
- 用户确认 §2 Goals / Non-Goals(2026-07-23「先按12-2实现」)
- 用户确认 §11 默认决策(含 D3 内嵌 CLI)
- P0:core + CLI 内嵌 resolve;login / UDS / web thin-wrap 后置
P0 交付物:rust/crates/mnote-vault-core、rust/crates/mnote-vault(bin mnote-vault)。