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.
43 KiB
12-3 [process] Chrome 扩展:站点登录/注册页保存到密码箱 v1
创建时间:2026-07-24
状态:PROCESS
Owner:12-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.md(vault 真源、CRUD API、L0/L1、同站多账号)design/12-vault/process/12-2-vaultd-local-token-agent-read-path-v1.md(agent 读密通道;本设计不复用 agent token 做写入口)/home/lix/.agent-infra/vault-policy.md+$mnote-vault(AI 读密策略;扩展默认写人用 vault)ARCHITECTURE.mdlocal-first;AGENTS.md密码箱薄指针
1. Overview
1.1 一句话
Chrome MV3 扩展在用户完成 MNote 远程登录后,于外站注册/登录页弹出「保存到密码箱」;默认同时保存账号密码与浏览器登录态(Cookie);登录态以「每账号独立文件」落盘;Agent 经
$mnote-vault的login/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 token;login/session 已有 |
不经扩展 resolve;扩展只 写入 session 文件;Agent 仍走 12-2 CLI |
| 登录态存储 | frontmatter 内嵌 loginSession(现状) |
同左(AI 本) | 演进为每账号独立文件(§5.6);兼容读 frontmatter |
| 鉴权主体 | 浏览器 session cookie | agent token | 人会话 / 扩展专用 human token |
| 自动填表 | 非目标 | — | P0 不做;P2 可选 |
1.4 硬门闩(产品)
扩展在以下任一不满足时,禁止弹出保存、禁止自动提交 vault 写请求:
- 已配置 远程 MNote 基址(
baseUrl,如http://127.0.0.1:3000) - 已用 MNote 账号/密码 完成登录,且会话未过期
- 已选定可写 workspace
rootUri(local_folder 的file://…,与工作台一致)
未登录态 UI 只允许:Options 配置 +「连接密码箱」;工具栏徽章显示「未连接」。
2. Goals / Non-Goals
2.1 Goals
- MVP 保存闭环:外站检测到 password 表单 → 用户确认 → 写入当前用户 vault(create 或更新同站条目)。
- 默认保存登录态:确认保存时 默认勾选「同时保存登录态」;扩展用
chrome.cookies抓取目标 origin 的 Cookie,写入 每账号独立 session 文件(§5.6)。 - Options 引导:远程地址 + 账号密码登录 + 选择 workspace(rootUri)+ 连接状态展示。
- 与
/vault同真源:POST /api/vault/items、PATCH /api/vault/items/{id}、PUT …/session、GET /api/vault/list;字段对齐VaultCreateInput+ session file schema。 - 安全默认:密钥与 Cookie 只经 extension background 出站;content script 不持长期 token / cookie 明文;审计无 value / cookieHeader。
- 可选同步 AI 本:保存后二次确认才
share-to-ai;勾选时 连同 session 文件 复制到 AI 本(否则 Agent 无法login复用)。 - Skill / Agent 可调用:
$mnote-vault+ vault-policy 明确login/session/resolve --field session稳态(§6.5)。 - 可安装、可调试: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 MV3;API 预留可移植 |
| 在 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 --id可mode=session复用(Chrome 手工) $mnote-vaultskill 写明登录态调用(login/session);vault-policy 待同步- audit 有
session_put;L0 无 password / cookieHeader 明文(smoke 断言) - 错误 baseUrl / 401 → 明确文案;token 吊销后 401(smoke)
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(可同步)或local;token 只进 session storage。 - 默认 rootUri(钉死):登录成功后
POST /api/local-folder/workspaces/default→ 账号固定
…/users/<actor>/workspaces/my-space的file://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 Worker(background) │
│ - 持 baseUrl + human token + rootUri │
│ - 保存弹层协调 / 工具栏 badge │
│ - fetch(`${baseUrl}/api/vault/...`) │
│ - host_permissions: 用户配置的 baseUrl(optional 动态) │
└────────────────────────────┬────────────────────────────────┘
│ 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 权限(cookiesAPI 要求)。 - 若政策要求更紧: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 登录密码; - 仅持久化:
baseUrl、rootUri、userId/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 后过滤,条目量小时足够):
- 取候选 URL 的 registrable origin(
https://github.com)与 path 前缀(可选)。 - 对 list 中每条 credential 的
url+urls[]:- origin 相等 → 候选;
- 多条 → 按
updatedAt降序展示前 N=5。
- 用户选「追加到已有」:
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 |
重 | 备选 |
P0:shadow 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}.json;primary若被新主账号替换则按 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+7d,MNOTE_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 演进顺序:
- 写:始终写
sessions/{credId}/{accountId}.json;可选同步清掉 frontmatterloginSession(或保留镜像一期,二期去掉)。 - 读(login 复用):
a. 若指定--account-id→ 只读该文件;
b. 否则读primary.json,否则accounts[0].id.json;
c. fallback:frontmatterloginSession(旧数据)。 - 新鲜度:同现网
login_session_is_fresh(expiresAt+ TTL)。
5.6.5 扩展抓 Cookie 规则
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 可提示「若刚登录,建议登录成功后再点工具栏『更新登录态』」。
- P1:
webNavigation/ 登录成功 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-web,MVP 建议)
POST /api/vault/extension/token
前置:已登录 session(Cookie)或刚完成 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 Bearer(vault.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
}
规则:
cookieHeader与cookies[]至少一个非空;两者皆有时以cookies[]重算 header 为准(防不一致)。accountId缺省 →"primary"。- 响应 L0:
{ ok, result: { credentialId, accountId, hasLoginSession: true, sessionExpiresAt, revision } },无 cookie 明文。 - audit:
action=session_put,source=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 实现必须:
- 复制 credential 到 AI 本(既有);
- 复制
sessions/{id}/**→ AI vault 同相对路径; - 若仅有 frontmatter
loginSession无文件 → 迁移写出 AI 侧primary.json再清/保留源侧策略按 core 统一。
否则 Agent 勾选同步后仍 login 不到 cookie。
鉴权中间件扩展
RequestContext 识别:
- 现有
mnote_sessioncookie - 新增
Authorization: Bearer mnext1.…(extension human token)
映射到同一 actor_id;vault 路径 require_authenticated 通过即可。
reveal 是否允许 extension token:
- P0:允许(扩展未来 autofill 需要);须审计
source=extension - 若产品收紧:P0 仅 list/create/update,reveal 仍要 web session
推荐 P0:extension token 允许 list / create / update / ensure / session put;reveal 默认关(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 session(P1 落地)或 login 取 cookieHeader |
session 文件 |
6.5.2 Agent 行为规则(写入 skill)
- 需要已登录外站状态时:先
login --id,看mode;session/reused/ok用返回的cookieHeader调目标 API / 注入浏览器。 - 禁止
Read/cat/grep.mnote/vault/sessions/**或 credential 内 cookie。 - 禁止 把
cookieHeader/ token 写入聊天、commit、issue。 human_required→ 用 chrome-bridge 完成验证 →session回写;不要为 session 去起 mnote-web。- 人用扩展写入的 session 在 用户 vault;Agent 默认读 AI 本 → 须用户 share-to-ai(或显式配置 actor 指向人用本,非默认)。
- 多账号:list/get 看
accounts[]+hasLoginSession;login --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.6;CLI 语义不变 |
| 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>或 动态反射已登记 extensionIdAccess-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;短 TTL;revoke;scope 无 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 以外 secret;session 仅 hasLoginSession |
| L1 | create/update 提交明文 password;session put 提交 cookie 仅 HTTPS 到 baseUrl;扩展本地不落盘 |
| L2 | 不提供 resolve;禁止 agent token;session 写允许、读明文不对 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 PUT;core 写 sessions/ |
| P1 | get+merge accounts[] + 每账号 session;login --account-id;/vault/match;share-to-ai 复制 session;workbench「复制到扩展」;登录成功后二次抓 cookie |
P0 |
| P2 | 自动填充(vault.reveal);resolve --field session;提交成功智能检测;Firefox |
安全评审 |
| P3 | Native Messaging 纯本机旁路;导入浏览器 CSV;TOTP;去掉 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 + core(session 文件)
POST /api/vault/extension/token+ revoke- Bearer
mnext1解析接入RequestContext/ vault 鉴权 - scope:view+edit(含 session put);无 resolve
PUT /api/vault/items/{id}/session→ 写sessions/{id}/{accountId}.json- core:
put_login_session/login读路径优先 session 文件,fallback frontmatter - share-to-ai 复制 session 目录
- audit
source=chrome_extension;session_put无 cookie 明文 - 单测:签发、revoke、session 文件读写(
vault_extension_token+session_file_put_resolve_and_share_copy) - API smoke:
scripts/vault-extension-api-smoke.js(auth → mnext1 → create → PUT session → disk → revoke) - (P1)
GET /api/vault/match;login --account-id
PR-C — 扩展 MVP
extensions/mnote-vaultMV3 脚手架(含cookies权限)- Options:baseUrl / 登录 / rootUri
- background vault-api + cookies 抓取
- content 侦测 + 确认层(默认勾选保存登录态)
- popup:状态 + 保存本页 + 更新登录态
- README 安装步骤
PR-D — Skill / Policy
- 更新
/home/lix/.agent-infra/vault-policy.md(每账号 session 文件 + 调用) - 更新
skills/mnote-vault/SKILL.md(login/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 --id→mode=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 + session;mnote-vault login 可复用 |
| S9 | 不勾选同步 AI | 仅人用库有 session;AI login 无该 id 或无 cookie |
| S10 | 审计文件 | 有 create / session_put,无明文密码/cookie 行 |
| S11 | skill 文档 | Agent 按 skill 用 login/session,不扫 vault 文件 |
12. Alternatives Considered
| 方案 | 结论 |
|---|---|
| 只做 bookmarklet 调 API | 无可靠 background;token 易进页面;否 |
| 浏览器原生 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 Bearer;dev 可附带 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}.json;frontmatter 只兼容读 |
| 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 API;skill/agent 调用合同 §6.5 |
15. 批准后下一步(给实现者)
- 产品确认 §13 D1–D5、D11–D14(默认可直接开工)。
- 先 PR-B(token + session 文件 + 鉴权)再 PR-C(扩展),避免扩展对接空 API。
- 同步 PR-D skill/policy,保证 Agent 知道
login/session读的是 session 文件。 - 不做 autofill 直到 P0 保存闭环(账号 + 登录态)绿 + 安全过目。
- 扩展是 人用录入;Agent 读密/读登录态 仍只走
$mnote-vaultCLI,不是扩展通道。