Files
mnote/design/12-vault/process/12-3-chrome-extension-vault-save-v1.md
T
Agent Board 262e66b02e feat: purge legacy agent hosts and land vault Chrome extension path
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.
2026-07-25 14:25:37 +08:00

43 KiB
Raw Blame History

12-3 [process] Chrome 扩展:站点登录/注册页保存到密码箱 v1

创建时间:2026-07-24
状态:PROCESS
Owner12-vault(扩展客户端 + mnote-web 鉴权/跨域适配;改 tree kernel / Page Aggregate
建议 repo 落点:design/12-vault/process/12-3-chrome-extension-vault-save-v1.md

上位依据:

  • design/12-vault/process/12-1-password-vault-dedicated-crud-workbench-v1.mdvault 真源、CRUD API、L0/L1、同站多账号)
  • design/12-vault/process/12-2-vaultd-local-token-agent-read-path-v1.mdagent 读密通道;本设计不复用 agent token 做写入口
  • /home/lix/.agent-infra/vault-policy.md + $mnote-vaultAI 读密策略;扩展默认写人用 vault
  • ARCHITECTURE.md local-firstAGENTS.md 密码箱薄指针

1. Overview

1.1 一句话

Chrome MV3 扩展在用户完成 MNote 远程登录后,于外站注册/登录页弹出「保存到密码箱」;默认同时保存账号密码与浏览器登录态(Cookie);登录态以「每账号独立文件」落盘;Agent 经 $mnote-vaultlogin / session / resolve --field session 复用,与 /vault 同一真源。

1.2 痛点

现状 问题
录入只靠 /vault 手填 跨站复制账号密码,摩擦高、易漏改
12-1 Non-Goals 写明「不做 OS 级自动填表扩展」 产品一期聚焦工作台合理;录入侧仍缺浏览器入口
12-2 agent token 只服务读密 不能把 agent capability token 交给扩展当写凭证

1.3 与 12-1 / 12-2 的关系

维度 12-1 12-2 12-3(本文)
真源 {workspace}/.mnote/vault/** 不变 不变
人用写入口 /vault + /api/vault/* 旁路 + 浏览器扩展(同 API 语义)
Agent 读密 / 登录态 HTTP(演进中) vaultd + capability tokenlogin/session 已有 不经扩展 resolve;扩展只 写入 session 文件;Agent 仍走 12-2 CLI
登录态存储 frontmatter 内嵌 loginSession(现状) 同左(AI 本) 演进为每账号独立文件(§5.6);兼容读 frontmatter
鉴权主体 浏览器 session cookie agent token 人会话 / 扩展专用 human token
自动填表 非目标 P0 不做P2 可选

1.4 硬门闩(产品)

扩展在以下任一不满足时,禁止弹出保存、禁止自动提交 vault 写请求:

  1. 已配置 远程 MNote 基址baseUrl,如 http://127.0.0.1:3000
  2. 已用 MNote 账号/密码 完成登录,且会话未过期
  3. 已选定可写 workspace rootUrilocal_folder 的 file://…,与工作台一致)

未登录态 UI 只允许:Options 配置 +「连接密码箱」;工具栏徽章显示「未连接」。


2. Goals / Non-Goals

2.1 Goals

  1. MVP 保存闭环:外站检测到 password 表单 → 用户确认 → 写入当前用户 vault(create 或更新同站条目)。
  2. 默认保存登录态:确认保存时 默认勾选「同时保存登录态」;扩展用 chrome.cookies 抓取目标 origin 的 Cookie,写入 每账号独立 session 文件(§5.6)。
  3. Options 引导:远程地址 + 账号密码登录 + 选择 workspacerootUri+ 连接状态展示。
  4. /vault 同真源POST /api/vault/itemsPATCH /api/vault/items/{id}PUT …/sessionGET /api/vault/list;字段对齐 VaultCreateInput + session file schema。
  5. 安全默认:密钥与 Cookie 只经 extension background 出站;content script 持长期 token / cookie 明文;审计无 value / cookieHeader。
  6. 可选同步 AI 本:保存后二次确认才 share-to-ai;勾选时 连同 session 文件 复制到 AI 本(否则 Agent 无法 login 复用)。
  7. Skill / Agent 可调用$mnote-vault + vault-policy 明确 login / session / resolve --field session 稳态(§6.5)。
  8. 可安装、可调试Manifest V3;本地 unpacked 加载;文档化 host_permissions(含 cookies)。

2.2 Non-Goals(本设计不承诺 / 明确不做)

说明
P0 自动填充 不做 Bitwarden 式 autofill;仅「保存」
Agent capability token 复用 扩展 禁止 使用 mnote-vault agent token / vaultd UDS 写库
改 kernel / tree.* vault 仍非图节点
云端跨机 vault 同步 仍 local-first;远程只是 mnote-web 可达地址
完整密码管理器对标 无 TOTP 引擎、无浏览器原生 Password API 劫持(P2 再评估)
Firefox/Safari P0 只 Chrome/Chromium MV3API 预留可移植
在 content script 内直接 fetch vault API 禁止(token 面扩大)
默认把所有保存项 share-to-ai 禁止静默

2.3 成功标准(设计批准后实现门闩)

  • Options:填 baseUrl + 登录成功 → whoami 显示用户(扩展 CONNECT + API smoke 签发 mnext1
  • 未登录时打开任意站 password 表单 → 可保存(弹层提示去连接且禁用保存)
  • 登录后在示例站提交登录表单 → 弹层预填 url/username/password → 确认 → /vault 列表可见新条目(Chrome 手工
  • 默认勾选「同时保存登录态」 → API smoke 确认磁盘 sessions/{credId}/{accountId}.json
  • 同 origin 已有条目 → 可选「追加账号」写入 accounts[]新账号自有 session 文件(扩展 UI
  • share-to-ai 勾选时 AI 本同时有 session 文件;Agent mnote-vault login --idmode=session 复用(Chrome 手工
  • $mnote-vault skill 写明登录态调用(login / session);vault-policy 待同步
  • audit 有 session_putL0 password / cookieHeader 明文(smoke 断言)
  • 错误 baseUrl / 401 → 明确文案;token 吊销后 401smoke

3. 问题与方案选型

3.1 写入通道

方案 优点 缺点 结论
A. 扩展 → mnote-web /api/vault/*(人会话) 与工作台一致;已有 create/update 需跨域鉴权 采用
B. 扩展 → Native Messaging → 本机写文件 不依赖 3000 无「远程地址」;安装重;与 local-first 桌面强绑定 P2 备选「纯本机」
C. 扩展 → vaultd agent token 不启 web token 设计给 agent 读;无完整 human CRUD 策略 禁止

3.2 鉴权形态

方案 说明 推荐阶段
E1. Session cookie 跨域 POST /api/auth 后带 mnote_session;扩展 fetch credentials: 'include' 本机 localhost 可试;跨站 SameSite 脆弱
E2. Extension human token(推荐) 登录成功后服务端签发 仅 vault.view + vault.edit 的 Bearer;扩展存 chrome.storage.session MVP 主路径
E3. 复用 /api/auth/mnote-web-token 若现网已有可给 API 的 token 实现时审计 scope;不足则走 E2

推荐默认(钉死):E2。
Cookie 路径(E1)可作为同机 dev 旁路,不得作为唯一生产假设。

E2 token 逻辑声明(与 12-2 agent token 分 aud/scope,禁止混用):

{
  "v": 1,
  "iss": "mnote-web",
  "aud": "chrome-extension-vault",
  "sub": "user:<userId>",
  "scope": ["vault.view", "vault.edit"],
  "rootUriAllow": ["file:///…可选绑定…"],
  "iat": 0,
  "exp": 0,
  "jti": "uuid"
}
  • 禁止 scope 含 vault.resolve / AI resolve。
  • 禁止mnv1.* agent token 同密钥、同 aud。
  • TTL 默认 7d(可 Options 续期 / 重新登录);revoke 按 jti。
  • 传输:Authorization: Bearer <token>

3.3 远程地址与 rootUri

Options 配置:
  baseUrl     = https://mnote.example 或 http://127.0.0.1:3000
  account     = 用户账号/邮箱
  password    = 仅登录瞬间使用;成功后**不**长期明文落盘(见 §7)
  rootUri     = (可选)file://… 覆盖;默认自动解析
  sourceKind  = local_folder(固定)
  • baseUrl 规范化:去尾 /;仅 http:/https:;禁止 javascript: 等。
  • 首次连接后把 baseUrl 写入 chrome.storage.sync(可同步)或 localtoken 只进 session storage。
  • 默认 rootUri(钉死):登录成功后 POST /api/local-folder/workspaces/default → 账号固定
    …/users/<actor>/workspaces/my-spacefile:// rootUri(与 web 壳默认 local_folder 一致)。
    Options 不必手填;「高级」仅用于写到其它已授权 local_folder。
  • 多 workspace / 自定义目录:仍可覆盖 rootUri(P1 可做下拉列表)。

4. 目标架构

4.1 组件图

┌─────────────────────────────────────────────────────────────┐
│ 外站页面(任意 origin)                                        │
│  content script:侦测 password 字段 / 提交;发消息给 background │
│  不持 token;不直接打 MNote API                                │
└────────────────────────────┬────────────────────────────────┘
                             │ chrome.runtime.sendMessage
                             ▼
┌─────────────────────────────────────────────────────────────┐
│ Extension Service Workerbackground                       │
│  - 持 baseUrl + human token + rootUri                        │
│  - 保存弹层协调 / 工具栏 badge                                │
│  - fetch(`${baseUrl}/api/vault/...`)                         │
│  - host_permissions: 用户配置的 baseUrloptional 动态)        │
└────────────────────────────┬────────────────────────────────┘
                             │ HTTPS + Bearer
                             ▼
┌─────────────────────────────────────────────────────────────┐
│ mnote-web                                                    │
│  POST /api/auth                     → 登录                   │
│  POST /api/vault/extension/token    → 签发 E2(新)          │
│  GET  /api/auth/whoami              → 校验会话/token         │
│  GET  /api/vault/list               → 同站匹配               │
│  POST /api/vault/items              → create                 │
│  PATCH /api/vault/items/{id}        → update / 追加账号       │
│  PUT  /api/vault/items/{id}/session → 写每账号 session 文件  │
│  POST /api/vault/items/{id}/share-to-ai  → 可选(含 session)│
└────────────────────────────┬────────────────────────────────┘
                             │ 文件写
                             ▼
              {workspace}/.mnote/vault/
                entries/{credId}.md          # 账号密码真源
                sessions/{credId}/{acc}.json # 每账号登录态(§5.6

4.2 仓库落点(建议)

路径 职责
extensions/mnote-vault/(新建) MV3 扩展源码:manifest、background、content、options、popup
rust/crates/mnote-web/src/routes/vault_extension.rs(或并入 vault.rs extension token 签发/吊销;可选 match-by-url 查询
browser/vault-workbench-runtime.js 可选:「复制连接信息到扩展」
design/12-vault/process/12-3-… 本文
文档 scripts/TESTING_REFERENCE.md 增补 extension smoke

放入 recycle/core-domain 图模型。

4.3 Manifest V3 最小权限

{
  "manifest_version": 3,
  "name": "MNote Vault",
  "permissions": ["storage", "activeTab", "scripting", "alarms", "cookies"],
  "optional_host_permissions": ["http://*/*", "https://*/*"],
  "host_permissions": [],
  "background": { "service_worker": "background.js", "type": "module" },
  "action": { "default_popup": "popup.html" },
  "options_page": "options.html",
  "content_scripts": [{
    "matches": ["http://*/*", "https://*/*"],
    "js": ["content.js"],
    "run_at": "document_idle",
    "all_frames": false
  }]
}

说明:

  • cookies 权限:默认保存登录态时,background 用 chrome.cookies.getAll({ url }) 抓目标 origin Cookie禁止 content script 读 cookie)。
  • optional_host_permissions:用户在 Options 确认 baseUrl 后 chrome.permissions.request 只放行该 origin;目标站默认靠 content_scripts matches(仅读 DOM,不出站)。保存登录态时,对目标站 origin 也需 host 权限(cookies API 要求)。
  • 若政策要求更紧:content_scripts 改为 activeTab + 用户点工具栏再注入(牺牲「自动侦测」)。P0 推荐:全站 content script + 保存必须用户点确认(非静默上传)。

5. 用户流程

5.1 首次连接(Options

1. 打开扩展 Options
2. 输入 baseUrl →「测试连接」→ GET {baseUrl}/api/health 或 /api/auth/whoami(未登录可 401 但仍可达)
3. 输入 MNote 账号/密码 →「登录」
4. 扩展 background
     POST {baseUrl}/api/auth  { provider password, flow signIn, … }
     成功后 POST {baseUrl}/api/vault/extension/token  Cookie 或临时会话)
     或 auth 响应内嵌 extensionToken(实现可选合并)
5. 选择/粘贴 rootUri →「保存配置」
6. Badge:已连接 · 用户 email 缩写

密码处理:

  • Options 表单内存持有至登录成功;
  • 成功后立即清除 DOM 与内存中的 password
  • 禁止 chrome.storage 持久化 MNote 登录密码;
  • 仅持久化:baseUrlrootUriuserId/email(非敏感)、token 放 session(浏览器重启需重新登录或 refresh)。

5.2 外站保存(MVP

触发(满足其一即可进入「候选」状态):
  T1. 页面存在 input[type=password],且用户焦点离开该字段 / 输入长度≥阈值
  T2. form submit 且 form 内含 password
  T3. 用户点击扩展工具栏「保存当前页账号」

门闩:
  未连接 → 仅 toast/小条:「打开 MNote Vault 扩展并登录」
  已连接 → 弹出确认层(扩展 page 或 iframe 隔离 UI,见 §5.4

确认层字段:
  - title(默认:document.title 截断 80 或 hostname
  - url(默认:location.origin + pathname 的 login 路径;可编辑)
  - username / email(从 autocomplete=username / email / name=user 启发式)
  - password(来自 password input;可显示切换)
  - 匹配结果:若 list 命中同站 → 「新建条目」|「追加到已有 · title」
  - **「同时保存登录态」checkbox:默认勾选**(用户可取消)
  - 可选 checkbox:同步到 AI 密码本(默认关;勾选时连同 session 文件)
  - 按钮:保存 / 取消

确认后 background
  1) create 或 update(账号密码)
  2) 若「同时保存登录态」仍勾选:
       chrome.cookies.getAll({ url: pageUrl 或 origin })
       → 拼 cookieHeader
       → PUT /api/vault/items/{id}/session
         { rootUri, accountId, cookieHeader, source: "chrome_extension" }
  3) 若勾选 share-to-ai → POST share-to-ai(服务端复制 credential + session 文件)
  4) toast 成功/失败(session 失败不回滚 credential,但提示「账号已存、登录态未写入」)

5.3 同站匹配规则(P0

服务端或客户端匹配(P0 可客户端 list 后过滤,条目量小时足够):

  1. 取候选 URL 的 registrable originhttps://github.com)与 path 前缀(可选)。
  2. 对 list 中每条 credential 的 url + urls[]
    • origin 相等 → 候选;
    • 多条 → 按 updatedAt 降序展示前 N=5。
  3. 用户选「追加到已有」:
    • PATCH 合并 accounts[] 新 slot(生成 acc_* id);
    • 顶层 username/password 是否更新:
      • 默认:仅追加 slot改 primary 镜像(避免踩掉主账号);
      • 勾选「设为主账号」时再写 primary。

实现可新增只读辅助(P1):

GET /api/vault/match?rootUri=&url=https://example.com/login
→ { items: [ L0 投影… ] }

P0 无此 API 时用 GET /api/vault/list + 客户端过滤。

5.4 确认 UI 隔离

方式 说明 推荐
Content script 注入 shadow DOM 实现快;样式污染风险;密码进页面 JS 上下文 MVP 可接受若确认层只读自 form
chrome.action popup 预填 需用户点图标;最安全 与 T3 共用
独立 extension 页 chrome.windows.create 备选

P0shadow DOM 确认层 + 工具栏手动入口双轨。
密码值从 form 读入后立即通过 message 传 background,确认层可用 type=password 掩码展示;取消则丢弃。

5.5 注册页 vs 登录页

不强制区分 DOM:同一保存路径。
启发式:URL/path 含 signup|register|sign-up 时 title 默认加「注册」前缀;不改变 schema。

5.6 每账号独立登录态文件(核心演进)

5.6.1 为何不继续只靠 frontmatter

现状(12-1 / 12-2 已落地) 问题
loginSession 嵌在 entries/{credId}.md frontmatter 整条 credential 只有一份 session;同站多账号互相覆盖
Cookie 与密码同文件 改 session 会 bump credential revision;审计/diff 面大
扩展默认抓 cookie 后无处按账号落盘 无法满足「每个账号各自登录态」

钉死:登录态 = 独立文件,按 credential + account 一文件。
frontmatter 内 loginSession 仅兼容读(迁移期);新写入一律走 session 文件。

5.6.2 磁盘布局

{workspace}/.mnote/vault/
  entries/
    {credId}.md                         # 账号/密码/accounts[] 真源(无 cookie 明文)
  sessions/
    {credId}/
      primary.json                      # 无 accounts[] 或主账号(accountId 省略 / "primary"
      {accountId}.json                  # 对应 accounts[].id,如 acc_01H…
  attachments/…                         # 既有附件(大 cookie 导出仍可走附件,非默认)
  audit.jsonl
  index.json
  • 路径一律相对 vault root;禁止 ..
  • 文件权限:与 vault 一致(建议 0600 文件 / 0700 目录)。
  • 删除 credential → 同步删除 sessions/{credId}/ 整目录。
  • 删除 account slot → 删除对应 {accountId}.jsonprimary 若被新主账号替换则按 merge 规则重命名/覆盖。
  • share-to-ai → 复制 entries 到 AI 本时 一并复制 sessions/{credId}/** 到 AI vault 同相对路径;否则 Agent 无法 login 复用。

5.6.3 Session 文件 schema

{
  "schema": "mnote.vault.session.v1",
  "credentialId": "cred_01H…",
  "accountId": "acc_01H…",
  "cookieHeader": "session=…; other=…",
  "cookies": [
    {
      "name": "session",
      "value": "…",
      "domain": ".example.com",
      "path": "/",
      "secure": true,
      "httpOnly": true,
      "sameSite": "lax",
      "expirationDate": 1735689600
    }
  ],
  "origin": "https://example.com",
  "expiresAt": "2026-07-31T00:00:00Z",
  "lastLoginAt": "2026-07-24T12:00:00Z",
  "source": "chrome_extension",
  "updatedAt": "2026-07-24T12:00:00Z",
  "revision": 1
}
字段 必填 说明
schema 固定 mnote.vault.session.v1
credentialId / accountId 与路径一致;accountId 无多账号时用 "primary"
cookieHeader 推荐 拼好的 Cookie 请求头(Agent login 主消费)
cookies[] 推荐(扩展写) 结构化;便于工作台展示/过期判断;与 cookieHeader 同步
origin 推荐 抓取时的 origin
expiresAt 推荐 会话逻辑过期(默认 now+7dMNOTE_VAULT_SESSION_TTL_HOURS
source chrome_extension | api | human_bridge | browser | login_api
revision 乐观并发

L0 投影list/get 只暴露 hasLoginSession / sessionExpiresAt / 可选 sessionAccountIds[]永不返回 cookieHeader / cookies[].value

5.6.4 Core 读路径(兼容)

mnote-vault-core / login / put_ai_vault_session 演进顺序:

  1. :始终写 sessions/{credId}/{accountId}.json;可选同步清掉 frontmatter loginSession(或保留镜像一期,二期去掉)。
  2. 读(login 复用)
    a. 若指定 --account-id → 只读该文件;
    b. 否则读 primary.json,否则 accounts[0].id.json
    c. fallbackfrontmatter loginSession(旧数据)。
  3. 新鲜度:同现网 login_session_is_freshexpiresAt + TTL)。
url = 确认层 url 或 tab.url 的 origin(至少 scheme+host+port
chrome.cookies.getAll({ url })
→ 过滤:
  - 排除超大 value(单 cookie > 8KiB 截断并记 note
  - 可选排除已知 tracking 名(P1 列表);P0 全量 origin cookies
→ cookieHeader = cookies.map(c => `${c.name}=${c.value}`).join('; ')
→ cookies[] 保留 domain/path/secure/httpOnly/sameSite/expirationDate
  • 仅 background 持有 cookie 明文;确认层 展示完整 cookie 列表(可显示「已捕获 N 条 Cookie」)。
  • 用户取消「同时保存登录态」→ 跳过 PUT session。
  • 登录表单提交瞬间可能尚无 session cookie
    • P0:仍保存当时 cookies(可能为空或仅 CSRF);toast 可提示「若刚登录,建议登录成功后再点工具栏『更新登录态』」。
    • P1webNavigation / 登录成功 URL 启发式后再二次捕获(另 checklist)。

5.6.6 与 attachments 的关系

  • 12-1 附件 kind: cookies 保留为大文件/导出旁路。
  • 默认路径是 session 文件,不是附件。
  • 禁止把 cookieHeader 再写进 notes_markdown。

6. API 合同

6.1 复用(已存在)

方法 路径 用途 鉴权
POST /api/auth 登录拿会话 公开
GET /api/auth/whoami/api/auth/session 校验身份 session/token
GET /api/vault/list?rootUri=&sourceKind=local_folder L0 列表 / 匹配 view
POST /api/vault/items 创建 edit
PATCH /api/vault/items/{id} 更新 / 追加账号 edit
POST /api/vault/items/{id}/share-to-ai 可选同步 AI 本(须含 session 文件 edit
POST /api/vault/ensure 确保目录 可写
PUT /api/vault/ai/items/{id}/session AI 本 session 回写(既有;演进写文件) AI + session scope

Create body 示例(扩展 → mnote-web):

{
  "rootUri": "file:///home/user/Notes",
  "sourceKind": "local_folder",
  "title": "GitHub",
  "url": "https://github.com/login",
  "username": "alice",
  "password": "plaintext-from-form",
  "email": "alice@example.com",
  "tags": ["from-extension"],
  "folderPath": "imported/browser",
  "notesMarkdown": "Saved via MNote Vault extension"
}

响应:与现网一致,L0 投影(password masked)。

Update 追加账号(语义对齐 12-1 accounts[]):

{
  "rootUri": "file:///…",
  "accounts": [
    { "id": "acc_existing", "label": "work", "username": "…", "password": "…" },
    { "id": "acc_new", "label": "from-extension", "username": "…", "password": "…" }
  ]
}

注意:accounts 在现网为 全量替换VaultUpdateInput.accounts: Option<Vec<…>>)。扩展必须 先 get 再 merge 再 patch,禁止只发新 slot 导致清空旧账号。

6.2 新增(mnote-webMVP 建议)

POST /api/vault/extension/token

前置:已登录 sessionCookie)或刚完成 auth 同请求链。

Request

{
  "clientId": "chrome-extension",
  "extensionId": "optional-chrome-runtime-id",
  "ttlHours": 168
}

Response

{
  "ok": true,
  "result": {
    "token": "mnext1.…",
    "expiresAt": "2026-07-31T00:00:00Z",
    "scope": ["vault.view", "vault.edit"],
    "userId": "…",
    "email": "…"
  }
}

POST /api/vault/extension/token/revoke

Body{ "jti": "…" } 或吊销当前 Bearer。

PUT /api/vault/items/{id}/session(人用 / 扩展 · 新增)

鉴权session cookie 或 E2 Bearervault.edit)。
写目标:用户 vault(非 AI 本);路径 sessions/{id}/{accountId}.json

Request

{
  "rootUri": "file:///home/user/Notes",
  "sourceKind": "local_folder",
  "accountId": "primary",
  "cookieHeader": "session=abc; other=1",
  "cookies": [
    {
      "name": "session",
      "value": "abc",
      "domain": ".example.com",
      "path": "/",
      "secure": true,
      "httpOnly": true,
      "sameSite": "lax",
      "expirationDate": 1735689600
    }
  ],
  "origin": "https://example.com",
  "expiresAt": "2026-07-31T00:00:00Z",
  "source": "chrome_extension",
  "expectedRevision": null
}

规则:

  • cookieHeadercookies[] 至少一个非空;两者皆有时以 cookies[] 重算 header 为准(防不一致)。
  • accountId 缺省 → "primary"
  • 响应 L0{ ok, result: { credentialId, accountId, hasLoginSession: true, sessionExpiresAt, revision } } cookie 明文。
  • auditaction=session_putsource=chrome_extension,无 value。

GET /api/vault/items/{id}/session(可选 P1

  • 默认 返回 cookie(仅 meta)。
  • 人用工作台「查看登录态元数据」用;扩展 P0 不需要 GET。

share-to-ai 合同补强

POST /api/vault/items/{id}/share-to-ai 实现必须:

  1. 复制 credential 到 AI 本(既有);
  2. 复制 sessions/{id}/** → AI vault 同相对路径;
  3. 若仅有 frontmatter loginSession 无文件 → 迁移写出 AI 侧 primary.json 再清/保留源侧策略按 core 统一。

否则 Agent 勾选同步后仍 login 不到 cookie。

鉴权中间件扩展

RequestContext 识别:

  1. 现有 mnote_session cookie
  2. 新增 Authorization: Bearer mnext1.…extension human token

映射到同一 actor_idvault 路径 require_authenticated 通过即可。
reveal 是否允许 extension token

  • P0:允许(扩展未来 autofill 需要);须审计 source=extension
  • 若产品收紧:P0 仅 list/create/updatereveal 仍要 web session

推荐 P0extension token 允许 list / create / update / ensure / session putreveal 默认关(scope 不含 vault.reveal);P2 autofill 再加 vault.reveal
E2 scope 逻辑声明可写 ["vault.view", "vault.edit"]vault.edit 包含 session 文件写入(不必单独 vault.session,避免 token 面碎片化)。

6.5 Agent / Skill 调用登录态($mnote-vault 合同)

扩展只写入;Agent 只经 12-2 CLI / core 消费。 禁止 Agent 为读 session 去扫 .mnote/vault/sessions/** 文件。

6.5.1 稳态命令(AI 必须会)

# 0. 环境(一次)
export MNOTE_VAULT_TOKEN_FILE="${MNOTE_VAULT_TOKEN_FILE:-$HOME/.config/mnote/vault-tokens/default.token}"
# 可选:MNOTE_AI_VAULT_ACTOR / MNOTE_VAULT_WORKSPACE

# 1. 选型(未知 id
mnote-vault list
# L0 可见 hasLoginSession / sessionExpiresAt(无 cookie 明文)

# 2. 优先复用登录态(推荐主路径)
mnote-vault login --id <credId>
# 成功:mode=session | reused → 响应含 cookieHeader(仅 stdout 一次;勿贴聊天)
# 过期/无 session:走 api_first 或 human_required

# 2b. 多账号时指定账号(session 文件按 accountId
mnote-vault login --id <credId> --account-id <accId>

# 3. 人机验证后回写(chrome-bridge / 扩展已写人用库后 share-to-ai
mnote-vault session --id <credId> --cookie-header 'name=value; …' --source human_bridge
# 可选:--account-id <accId>

# 4. 只要 cookie 明文管道(实现演进;与 login 二选一)
# mnote-vault resolve --id <credId> --field session [--account-id <accId>] [--raw]
意图 命令 读哪里
复用未过期 Cookie 访问外站 login sessions/{id}/{account}.json → fallback frontmatter
浏览器验证后落盘 session 写同上路径
只要密码填表 resolve --field password credential md
只要 Cookie 不做登录流程 resolve --field sessionP1 落地)或 logincookieHeader session 文件

6.5.2 Agent 行为规则(写入 skill

  1. 需要已登录外站状态时:先 login --id,看 modesession/reused/ok 用返回的 cookieHeader 调目标 API / 注入浏览器。
  2. 禁止 Read/cat/grep .mnote/vault/sessions/** 或 credential 内 cookie。
  3. 禁止cookieHeader / token 写入聊天、commit、issue。
  4. human_required → 用 chrome-bridge 完成验证 → session 回写;不要为 session 去起 mnote-web。
  5. 人用扩展写入的 session 在 用户 vaultAgent 默认读 AI 本 → 须用户 share-to-ai(或显式配置 actor 指向人用本,非默认)。
  6. 多账号:list/get 看 accounts[] + hasLoginSessionlogin --account-id 与扩展写入的 accountId 对齐。

6.5.3 文档落点(实现 Checklist 同步改)

文件 变更
/home/lix/.agent-infra/vault-policy.md Session 语义改为「每账号文件」;登录稳态表保留
skills/mnote-vault/SKILL.md(全局 symlink 增加「登录态文件 + 调用示例」专节
12-2 交叉引用:session 存储演进见 12-3 §5.6CLI 语义不变
12-1 schema 注明 loginSession frontmatter 兼容;新写走 sessions/

CORS(仅 extension 需要时)

若 background 为 extension origin

  • /api/vault/*/api/vault/extension/*/api/auth
    • Access-Control-Allow-Origin: chrome-extension://<id> 动态反射已登记 extensionId
    • Access-Control-Allow-Headers: Authorization, Content-Type
    • 不需要 Allow-Credentials 若纯 Bearer

更干净:不靠 CORS,仅用 extension host_permissions 直连Chrome 扩展跨域 fetch 不受页面 CORS 限制)。
实现结论:MVP 以 host_permissions + Bearer 为主;服务端 CORS 仅在将来 Web 页面桥接时需要。

6.3 错误码(扩展可分支)

code HTTP 扩展动作
vault_auth_required 401 清 token,引导 Options 登录
vault_extension_token_invalid 401 重新签发
vault_extension_token_expired 401 重新登录
vault_root_required / vault_root_invalid 400 Options 检查 rootUri
vault_permission_denied 403 提示 workspace 无写权限
vault_revision_conflict 409 re-get 后重试 merge
vault_mask_sentinel_rejected 400 勿提交掩码串
network_unreachable 检查 baseUrl / 本机 3000

6.4 审计

沿用 {workspace}/.mnote/vault/audit.jsonl

{"ts":"…","action":"create","actorId":"user_x","credentialId":"cred_…","requestId":"…","ok":true,"source":"chrome_extension"}
{"ts":"…","action":"session_put","actorId":"user_x","credentialId":"cred_…","accountId":"primary","requestId":"…","ok":true,"source":"chrome_extension"}

source 字段:实现时在 append_vault_audit 增加可选 source(无则兼容缺省)。禁止 value / cookieHeader。


7. 安全设计

7.1 威胁与缓解

威胁 缓解
钓鱼站诱导保存到假 baseUrl Options 显示完整 baseUrl;首次连接确认;可选证书钉扎不做 P0
恶意页面读 extension 消息 content 只发「候选字段」;token 永不下行 content;响应不回传 password / cookie
XSS 页面窃取已输入密码 与浏览器密码管理器同类风险;确认层不把 token 注入页面
token 失窃 session storage;短 TTLrevokescope 无 resolve
Cookie 被 content 窃取 仅 background chrome.cookies;不回传 content
磁盘 session 文件泄露 vault 目录权限;path deny 对 agent 通用工具;只经 CLI
扩展被恶意更新 自用 unpacked / 签名发布策略另案;P0 本地加载
日志/崩溃转储 background 不 console.log password / cookieHeader;错误上报脱敏
静默批量上传 必须用户确认;无「自动保存所有表单」默认开
AI 本扩大攻击面 share-to-ai 默认关 + 二次确认;复制 session 须同确认

7.2 与 12-1 安全分级

扩展行为
L0 list/match 元数据可缓存内存短时(TTL≤5min),持久化到 disk 含 hint 以外 secretsession 仅 hasLoginSession
L1 create/update 提交明文 passwordsession put 提交 cookie HTTPS 到 baseUrl;扩展本地不落盘
L2 不提供 resolve;禁止 agent tokensession 允许、读明文不对 extension

7.3 与 chrome-bridge / QA 关系

  • chrome-bridge 用于 agent 人机验证 session 回写(12-2),不是本扩展。
  • 本扩展是 的录入工具;QA 可用 Playwright 装 unpacked 扩展做 smoke,不与 Hermes QA 默认路径耦合。

8. 扩展模块地图

extensions/mnote-vault/
  manifest.json
  background/
    index.js          # 消息路由、API 客户端、badge
    auth.js           # login / token / whoami
    vault-api.js      # list/create/update/session/share
    cookies.js        # chrome.cookies → cookieHeader + cookies[]
    match.js          # origin 匹配
  content/
    detect.js         # password 表单侦测
    save-prompt.js    # shadow DOM 确认层(默认勾选保存登录态)
  options/
    options.html
    options.js
  popup/
    popup.html        # 状态 + 快捷「保存本页」+「更新登录态」+ Options
    popup.js
  shared/
    messages.js       # 消息类型常量
    normalize-url.js
  README.md           # 安装:chrome://extensions 开发者模式

8.1 消息协议(内部)

// content → background
type Msg =
  | { type: "vault/candidate"; payload: { url: string; title: string; username?: string; email?: string; password: string; pageUrl: string } }
  | { type: "vault/ping" };

// background → content
type Reply =
  | { type: "vault/status"; connected: boolean; email?: string }
  | { type: "vault/save-result"; ok: boolean; itemId?: string; sessionSaved?: boolean; message?: string };

// popup/options → background
type Ctrl =
  | { type: "vault/login"; baseUrl: string; account: string; password: string }
  | { type: "vault/logout" }
  | { type: "vault/set-root"; rootUri: string }
  | { type: "vault/save-confirmed"; payload: SavePayload }
  | { type: "vault/refresh-session"; payload: { itemId: string; accountId?: string; pageUrl: string } };

type SavePayload = {
  title: string;
  url: string;
  username?: string;
  email?: string;
  password: string;
  matchItemId?: string;       // 追加到已有
  setPrimary?: boolean;
  saveSession: boolean;       // 默认 true
  shareToAi: boolean;         // 默认 false
  pageUrl: string;
};

8.2 刷新模型

  • 禁止 content/background setInterval 轮询 vault list。
  • 匹配:保存前 单次 list/match;结果可 memory cache 至多 5 分钟或 rootUri 变更时失效。
  • 连接状态:登录/登出/401 时更新 badge;可用 chrome.alarms 做 token 到期前提醒(非轮询 vault)。

9. 分期

阶段 范围 依赖
P0 MVP Options 连接;Bearer;保存确认层;create默认保存登录态 → session 文件list 匹配;toolbar 手动保存;skill/policy 更新 mnote-web token + session PUTcore 写 sessions/
P1 get+merge accounts[] + 每账号 sessionlogin --account-id/vault/matchshare-to-ai 复制 sessionworkbench「复制到扩展」;登录成功后二次抓 cookie P0
P2 自动填充(vault.reveal);resolve --field session;提交成功智能检测;Firefox 安全评审
P3 Native Messaging 纯本机旁路;导入浏览器 CSVTOTP;去掉 frontmatter loginSession 镜像 另设计

9.1 与 12-1 P2「协助录入」关系

12-1 §6 P2「协助录入确认流」可与本扩展合并叙事:浏览器侧录入 = 12-3;工作台内粘贴助手可后置或不做。


10. 实现 Checklist(批准设计后执行)

PR-A — 设计与开关

  • 本文合入 design/12-vault/process/
  • 12-1 / 12-2 交叉引用 session 文件演进(§5.6
  • Feature flag(可选):MNOTE_VAULT_EXTENSION=1 控制 token 路由是否注册(P0 随 MNOTE_VAULT=1

PR-B — mnote-web + coresession 文件)

  • POST /api/vault/extension/token + revoke
  • Bearer mnext1 解析接入 RequestContext / vault 鉴权
  • scopeview+edit(含 session put); resolve
  • PUT /api/vault/items/{id}/session → 写 sessions/{id}/{accountId}.json
  • coreput_login_session / login 读路径优先 session 文件,fallback frontmatter
  • share-to-ai 复制 session 目录
  • audit source=chrome_extensionsession_put 无 cookie 明文
  • 单测:签发、revoke、session 文件读写(vault_extension_token + session_file_put_resolve_and_share_copy
  • API smokescripts/vault-extension-api-smoke.jsauth → mnext1 → create → PUT session → disk → revoke
  • P1GET /api/vault/matchlogin --account-id

PR-C — 扩展 MVP

  • extensions/mnote-vault MV3 脚手架(含 cookies 权限)
  • OptionsbaseUrl / 登录 / rootUri
  • background vault-api + cookies 抓取
  • content 侦测 + 确认层(默认勾选保存登录态
  • popup:状态 + 保存本页 + 更新登录态
  • README 安装步骤

PR-D — Skill / Policy

  • 更新 /home/lix/.agent-infra/vault-policy.md(每账号 session 文件 + 调用)
  • 更新 skills/mnote-vault/SKILL.mdlogin/session 与文件语义)
  • 确认全局 symlink.grok / Codex)→ 仓库 skills/mnote-vault

PR-E — 验收

  • API 层:node scripts/vault-extension-api-smoke.js 对 3000 通过
  • 本机:unpacked 扩展 Chrome 手工加载(用户验收)
  • 测试账号登录扩展 → 保存 example → /vault 可见条目 + sessions/…json
  • share-to-ai 后 mnote-vault login --idmode=session
  • 未登录 content 禁用保存(仅提示去 Options)
  • TESTING_REFERENCE.md 段落

PR-F — P1 增强(可另 PR

  • 同站 accounts 合并 + 每账号 session(扩展 MATCH_URL + append mode
  • workbench 复制连接信息
  • 登录成功后二次抓 cookie
  • resolve --field session

11. 验收场景(手工 / smoke

# 场景 期望
S1 无配置打开 example.com 登录表 无保存弹层或仅「去连接」
S2 配置错误 baseUrl 测试连接失败,不存 token
S3 正确登录 + rootUri badge 已连接
S4 填用户名密码 → 点保存 → 确认(默认保存登录态) create 成功;sessions/{id}/primary.json 存在;password reveal 正确
S5 取消「同时保存登录态」 仅 credential,无新 session 文件
S6 同站再存另一账号选「追加」 accounts 两个;各有 session 文件(若均勾选)
S7 token 过期后保存 401 → 引导重新登录,不写半截
S8 勾选同步 AI AI 本有 credential + sessionmnote-vault login 可复用
S9 不勾选同步 AI 仅人用库有 sessionAI login 无该 id 或无 cookie
S10 审计文件 有 create / session_put,无明文密码/cookie 行
S11 skill 文档 Agent 按 skill 用 login/session,不扫 vault 文件

12. Alternatives Considered

方案 结论
只做 bookmarklet 调 API 无可靠 backgroundtoken 易进页面;否
浏览器原生 Password Manager 导出再 import 不解决「当下保存」;可作迁移旁路
全部走 Native Host 写磁盘 无远程 baseUrl;与用户需求不符;P2 可选
扩展直接读 .mnote/vault 文件 违反 vault path deny 与加密演进;禁止
复用 agent mnote-vault token 权限与 actor 错误;禁止
继续只嵌 frontmatter loginSession 同站多账号互踩;否;演进为每账号文件
session 只做 attachments 附件 路径长、list 难;默认 session 文件,附件作导出旁路

13. 待决问题(实现前钉死;推荐已标)

# 问题 推荐默认
D1 鉴权 E1 cookie vs E2 Bearer E2 Bearerdev 可附带 cookie
D2 content_scripts 全站 vs activeTab 全站侦测 + 用户确认保存
D3 extension token 是否 reveal P0 否P2 autofill 再开
D4 rootUri 如何获得 P0 手动 / 工作台复制P1 列 directory grant
D5 默认 folderPath imported/browser
D6 是否默认 tags ["from-extension"]
D7 扩展目录 monorepo 还是独立 repo monorepo extensions/mnote-vault
D8 发布渠道 P0 仅 unpacked / 自签;商店上架另案
D9 与 12-1「不做自动填表」文案 修订交叉引用,避免矛盾
D10 密码字段启发式失败 工具栏手动选中 / 用户可编辑确认层
D11 默认是否保存登录态 是(默认勾选);用户可取消
D12 session 存 frontmatter vs 独立文件 独立文件 sessions/{cred}/{acc}.jsonfrontmatter 只兼容读
D13 无多账号时 accountId primary
D14 登录瞬间 cookie 可能不全 P0 仍写 + 工具栏「更新登录态」;P1 登录成功二次抓

14. 修订记录

日期 变更
2026-07-24 初稿:Chrome MV3 保存扩展;E2 human token;复用 vault CRUD;与 12-1/12-2 边界钉死
2026-07-24 增补:默认保存登录态;每账号独立 session 文件;PUT session APIskill/agent 调用合同 §6.5

15. 批准后下一步(给实现者)

  1. 产品确认 §13 D1D5、D11D14(默认可直接开工)。
  2. PR-Btoken + session 文件 + 鉴权)再 PR-C(扩展),避免扩展对接空 API。
  3. 同步 PR-D skill/policy,保证 Agent 知道 login/session 读的是 session 文件。
  4. 不做 autofill 直到 P0 保存闭环(账号 + 登录态)绿 + 安全过目。
  5. 扩展是 人用录入Agent 读密/读登录态 仍只走 $mnote-vault CLI不是扩展通道。