Appearance
passno 设计(草案,2026-11-16)
passno 是开源密码管理器,人和 AI agent 共用。跨平台(macOS / Windows / Linux,移动端以后再做),不用服务器同步,v1 就支持家庭和小团队共享,可以从 1Password 和 Apple 密码迁移。许可证为 Apache-2.0 / MIT 双许可。
调研依据见 docs/research/01–04。GPL 项目(KeePassXC、Bitwarden 客户端、Rowel 应用部分、Proton Pass)只参考思路,不复制代码。
1. 原则
- agent 默认拿不到明文。 agent 能做的是"用"密钥:注入到一条命令、代发一个 HTTP 请求、代填一个网页表单、代签一次 SSH。要直接取出明文,必须是条目上单独开过的例外,而且每次都要人确认。
- 所有改动都签名,每个文件只有一个写入者。 这样不用服务器也能做多设备同步和多人共享,而且能检测篡改和回滚。
- 核心逻辑全在 Rust 里。 后台进程、CLI、浏览器扩展、管理界面、以后的 Swift 扩展都只是客户端,规则只在一个地方实现。
- SOLID: 格式、加密、同步传输、平台解锁、IPC 身份校验、导入器各自放在独立的 trait 后面。加一个平台或一种导入格式,就是加一个实现,不改已有的分支。
2. 组件
crates/
passno-model 条目数据模型(纯数据,无 I/O)
passno-crypto KDF / AEAD / 签名 / 密钥包裹(只依赖 RustCrypto 等纯库)
passno-vault 操作日志、快照、纪元、成员日志、合并(HLC)
passno-sync trait SyncTransport:Folder(iCloud/Syncthing/Dropbox)、Git
passno-import trait Importer:OnePux、OnePasswordCsv、AppleCsv、Cxf
passno-gen 密码 / 口令生成、网站密码规则(PMR)、强度估算
passno-match URL 匹配(PSL、PMR 共用账号域名组)
passno-totp TOTP / otpauth 解析
passno-unlock trait Unlocker:Password、MacSecureEnclave、WindowsHello、LinuxPolkit
passno-ipc 协议定义 + trait PeerVerifier(每个系统一个实现)
passno-agent 授权引擎、审计日志(哈希链)、代执行(run/inject、HTTP 代理、SSH)
passno-daemon passnod:持有密钥、自动锁定、审批请求、对外提供 IPC
passno-cli pn:给人和 agent 用的命令行(含 MCP server:`pn mcp`),随 App 一起安装(见第 12 节)
passno-nmhost 浏览器 native messaging 转发程序
passno-ffi UniFFI 绑定,给 iOS / macOS 的 Swift 代码用
apps/desktop Tauri v2 管理界面,也负责审批弹窗;安装包里同时带 pn、passnod、passno-nmhost
extension/ Manifest V3 扩展(TypeScript),一套代码支持 Chrome/Edge/Firefox;Safari 版由 apple/ 打包
apple/ Swift:AutoFill provider(iOS + macOS)、CXF 导入、Safari 扩展;iOS 主 App(阶段 6 验证后定用 Tauri 还是 SwiftUI)
(以后)windows/ 通行密钥插件(MSIX)分层依赖:
- model ← crypto ← vault ← (sync, import) ← daemon ← (cli, nmhost, desktop)。
- 上层只依赖 trait,具体实现在启动时注入。测试时换成假实现:内存目录、假解锁器、假的对端校验。
3. 数据模型
- Item 包含:
id、vault_id、kind(Login / Password / Note / Card / Identity / SshKey / ApiCredential / Document / Wifi / Custom)、title、urls[](每条带匹配模式)、fields[]、totp[]、passkeys[]、attachments[]、tags[]、favorite、archived、history[]、agent_policy、created、updated。 - Field 包含:
label、value、type(text / concealed / email / url / phone / date / monthYear / address / menu / reference / file)、section、designation(username / password / none)。 - 导入时映射不了的字段一律变成自定义字段,不丢数据。导入完给出报告:每个来源导入了多少条、哪些字段被降级。
4. 密钥结构
主密码 ──Argon2id(m=64MiB,t=3,p=4)──┐
Secret Key(128 位,默认开启)───────┴─HKDF→ AUK(账户解锁密钥)
AUK 包裹 → 成员私钥:X25519(加密)+ Ed25519(签名);每台设备一对设备密钥
保险库纪元密钥(256 位,随机)→ 用每个成员的 X25519 公钥分别包裹(HPKE)
条目 → XChaCha20-Poly1305,AAD = vault_id ‖ item_id ‖ epochSecret Key 默认开启(用户 2026-11-16 定)。初始化时生成,同时给出应急套件(PDF 或二维码),提示用户打印或存放在别处。高级选项里可以关掉,只适合不同步的本地保险库。
平时解锁用 Touch ID、Face ID、Apple Watch,不用每次输主密码(用户 2026-11-16 定):
- macOS / iOS:
- AUK 用安全飞地 P-256 密钥包裹(ECIES)。
- 访问控制用
biometryCurrentSet;Mac 上额外允许 Apple Watch 确认(LAPolicy.deviceOwnerAuthenticationWithBiometricsOrWatch)。 - 新录入或删除指纹后,包裹失效,必须重新输一次主密码。
- 需要签名的 App bundle。
- Windows: Windows Hello 签挑战,再对签名做 HKDF。
- Linux: 主密码,加 Polkit。
- macOS / iOS:
生物识别只是便利,不能替代主密码。 以下情况必须输主密码:
- 重启后第一次解锁;
- 距上次输主密码超过 14 天(可调);
- 指纹有变化;
- 加新设备;
- 修改共享成员或安全设置。
这样主密码不会被忘掉,安全飞地也不会成为单点。
Secret Key 在苹果设备之间同步: 存进 iCloud 钥匙串(可同步、端到端加密),用户可以关掉。这样新的 Mac 或 iPhone 只要登录同一个 Apple ID,再输一次主密码就能用,不用扫码。
- 安全飞地的密钥绑定在单台设备上,不能同步,所以每台设备首次解锁后各自建立 Touch ID 包裹。
- Windows / Linux 设备仍然要扫码或手动输入 Secret Key。
KDF 参数写在文件头里,以后可以升级。
5. 存储与同步
<vault-root>/
account/<member>.pub 公钥 + 指纹
vaults/<vault-id>/
members/<device>/<seq> 成员日志(签名、哈希链)
keys/<epoch>/<member> 包裹好的纪元密钥
ops/<device>/<seq> 加密 + 签名的操作(只追加)
snapshots/<device>/<n> 定期快照- 每个文件只有一个写入者,所以 iCloud、Syncthing、Dropbox、git 都不会产生冲突。
- 文件名用随机 ID,内容按大小分档补齐,减少元数据泄露。
- 合并规则:按字段取最后写入者(HLC 时间戳,平局时比设备 ID)。并发修改中被覆盖的版本放进历史。
- 本地 SQLite 只是搜索和匹配缓存,可以随时从日志重建。
- 防回滚:每台设备记住每个写入者的最高序号和对应哈希,发现倒退就告警。
6. 共享
- 加入: 当面核对指纹,或扫邀请二维码。管理员在成员日志里签"加人",再把纪元密钥包裹给新成员。
- 角色: owner / admin / write / read。另有一种 agent 使用权限,见第 7 节。
- 移除: 开新纪元、换新密钥、重加密,并提示其余成员轮换那个人看得到的密码。没有服务器,撤回不了对方已经读过的内容,界面上要说清楚。
- 验证: 读取时拒绝没有写权限的签名。
7. agent 访问
agent 不是加密层面的成员,拿不到任何密钥。它们是后台进程里的策略主体。
- 登记:
pn agent enroll <名字>为 agent 生成一个专属凭据,用户在桌面端确认一次。以后每个请求都带上这个凭据,同时后台进程校验调用方的进程树(macOS 用 audit token,Linux 用SO_PEERCRED,Windows 用命名管道的客户端 PID)。 - 能看到的范围: 只有条目上
agent_policy允许、或者所在保险库开了"agent 可用"的条目。agent 只能看到标题、URL 和用户名,看不到密码。 - 操作分档:
| 档位 | 方式 | 明文会到 agent 吗 |
|---|---|---|
| 代填网页 | 经扩展,在 agent 驱动的浏览器里填表单 | 不会 |
| 代发 HTTP | 本地代理把占位符替换成密钥;绑定 host + path + method;不跟随重定向 | 不会 |
| SSH 代签 | passno 充当 ssh-agent | 不会 |
| 注入命令 | pn run --env K=pn://… -- cmd,审批绑定到精确的命令行 | 会到子进程(最弱一档,界面上标黄) |
| 取出明文 | 条目单独允许后,每次都要人确认 | 会(例外) |
- 审批: 桌面弹窗,选项为本次 / 本次运行 / N 小时 / 拒绝。敏感条目还要再过一道 Touch ID 或 Windows Hello。远程时可以以后加手机推送。保险库一锁,所有授权全部作废;托盘上有一键全部撤销。
- MCP:
pn mcp只暴露 list / search / request_grant / run / http / fill / generate,返回的是授权编号和执行结果,不是值。 - 审计: 每次请求和授权都记进哈希链日志,界面上能查也能校验。
- 输出打码(raw、base64、URL 编码、JSON)只是最后一道保险,不当成安全边界。
8. 浏览器自动填充
- 链路: 扩展 ⇄
passno-nmhost(stdio)⇄ passnod(socket / 命名管道)。nmhost 校验 argv[1] 里的扩展 origin;扩展和后台进程之间首次配对用 X25519,配对码在桌面端确认。 - 能力:
- 识别登录、注册、改密码、TOTP 表单。
- 页面内菜单:封闭 shadow root + 扩展页 iframe + popover,检测页面篡改。
- 只在用户真实点击后才填。
- 提交后在隔离的横幅里提示保存或更新。
- 注册和改密码表单旁边直接给生成的密码,按网站规则生成。
- HTTP Basic 认证。
- 匹配在核心里做:默认 eTLD+1(PSL,含私有段),可选主机或精确匹配,加上 PMR 共用账号域名组。禁止 HTTPS 降级,跨域 iframe 要单独确认。
9. 密码生成
- 随机字符: 长度、字符类、每类最少几个、排除易混淆字符。
- 口令短语: EFF 长词表,可选分隔符、大写、数字。
- 按网站规则: 用 PMR 的
password-rules.json加页面上的passwordrules属性,经password-rules-parser解析。 - 强度: 生成的密码显示精确熵值;手输的密码用 zxcvbn 估算。
- 调用方: CLI、扩展、界面、agent 共用同一个实现。
10. 管理界面(Tauri v2)
- 浏览、搜索、编辑、标签、收藏、归档、回收站、附件、历史版本。
- 生成器、导入向导(显示迁移报告)。
- 安全检查:重复密码、弱密码、缺少 2FA 的网站(PMR),以及泄露检测(HIBP k-匿名,可选)。
- 共享成员管理、agent 授权和审计、设备管理、同步状态。
- 视觉风格、交互和流畅度指标见
docs/ui/style.md。 - 明文留在 Rust 侧,按需显示或复制。剪贴板按设定时间清空。CSP 和 capability 都配到最严。
11. 迁移
| 来源 | v1 方式 | 能迁的 | 迁不了的 |
|---|---|---|---|
| 1Password | .1pux | 所有账户和保险库、全部字段类型、TOTP、附件、标签、归档、历史 | 通行密钥(桌面版导不出) |
| 1Password | CSV(兜底) | 登录、密码 | 自定义字段、附件 |
| Apple 密码 | CSV | 标题、URL、用户名、密码、备注、TOTP | 通行密钥、多个 URL、Wi-Fi、共享群组 |
| 二者 | CXF(以后) | 包括通行密钥 | 附件(Apple API 不支持文件) |
- CXF 的解析和映射在 Rust 核心里做,基于
credential-exchange-format(MIT)。macOS / iOS 原生壳只负责把 JSON 传过来。 - 导入是幂等的:用来源 UUID 去重,同一份文件重复导入不会出现重复条目。
12. 安装与命令行:App 自带 CLI(用户 2026-11-16 定)
装了 App 就有 pn,不用另外安装。 CLI、后台进程和界面在同一个安装包里,版本始终一致,签名也一致(后台进程靠校验签名来认 CLI,见第 7 节)。
12.1 各平台怎么带上 CLI
| 平台 | 安装包内容 | pn 怎么进 PATH |
|---|---|---|
| macOS | passno.app/Contents/MacOS/ 里放 passno(界面)、pn、passnod、passno-nmhost,都用同一个 Team ID 签名 | 第一次启动时,欢迎页里有一个默认勾选的"安装命令行工具":建软链 /usr/local/bin/pn → App 里的 pn,系统弹一次管理员授权。用户拒绝的话,退而建 ~/.local/bin/pn,并提示怎么把它加进 PATH。软链指向 App 内部,所以 App 一更新,CLI 跟着更新。设置里可以重装或卸载。 |
| macOS 后台进程 | passnod 用 SMAppService 注册成登录项(LaunchAgent),由 App 管理 | — |
| Windows | MSIX 包里带 pn.exe | 用 MSIX 的 AppExecutionAlias,装完直接就能在 PATH 里用,不需要管理员 |
| Linux | deb、rpm 装到 /usr/bin/pn;AppImage 首次运行时提示建 ~/.local/bin/pn 软链 | — |
| iOS | 没有 CLI | — |
做法参考的是常见桌面软件,我凭经验写的,没逐个核对源码,agy 实现时请对一下:VS Code 有"在 PATH 中安装 code 命令",Docker Desktop 把 CLI 软链到 /usr/local/bin,Tailscale 的 macOS App 里自带 CLI 程序。
pn doctor 检查以下几项,每个问题都给出一条修复命令:
- CLI 是否在 PATH 上,是否指向当前 App;
- 后台进程是否在运行;
- 保险库是否已解锁;
- 浏览器扩展是否已配对;
- 签名是否一致。
12.2 对 agent 友好的帮助和输出
- 帮助分三层:
pn --help:一屏以内。说明 passno 是什么,列出最常用的 8 条命令,每条带一个例子,最后一行写"AI agent 请先运行pn agent-help"。pn <命令> --help:参数、2–4 个可以直接复制的例子、输出格式、可能的错误码。pn help <命令> --json:机器可读的帮助,包括参数、类型、默认值、输出的 JSON Schema、退出码。- 三层都从 clap 定义自动生成,同时导出 man 页和
docs/cli/*.md,保证只有一个来源。
pn agent-help:给 LLM agent 的简明指南(Markdown,200 行以内):- passno 的安全模型:拿不到明文,只能"用",每次使用都要人批准;
- 推荐的调用方式:先
pn search,然后pn run、pn http或pn fill; - 遇到"待批准"、"已拒绝"、"已锁定"时怎么办;
- 典型任务的完整示例;
- 明确告诉 agent:"不要试图读取或打印密钥,这种请求会被拒绝并记进审计日志。"
pn agent install-skill: 把上面的指南写成 skill 或说明文件,装到 Claude Code(~/.claude/skills/passno/)、Codex 等 agent 的约定位置。也可以--print只打印不安装。- 输出:
- 所有读操作都支持
--json,字段名稳定,带schema_version。列表和事件流用 NDJSON。 - 默认输出给人看(表格、颜色)。stdout 不是终端时自动去掉颜色。
- 所有读操作都支持
- 不交互、不卡住:
- stdin 不是终端时,绝不弹出交互式提问。缺参数就直接报错,并给出正确用法。
- 需要人批准时,立即返回:json
{"status":"pending_approval","grant_id":"…","hint":"请用户在 passno 中批准;或加 --wait 60 等待"}--wait <秒>用来等待批准,最长 120 秒。
- 退出码固定:
| 退出码 | 含义 |
|---|---|
| 0 | 成功 |
| 2 | 用法错误 |
| 3 | 保险库已锁定 |
| 4 | 被拒绝 |
| 5 | 找不到 |
| 6 | 等待批准超时 |
| 7 | 后台进程没运行 |
| 8 | 策略不允许 |
- 错误信息三段式: 发生了什么、为什么、下一步运行什么。例如:命令打错时给出最接近的建议。
error: 保险库已锁定 原因: 空闲 15 分钟后自动锁定 下一步: 请用户在 passno 中解锁(Touch ID),或运行 pn unlock - CLI 和 MCP 共用一套说明。
pn mcp的工具描述和pn agent-help出自同一个定义,不会出现两边说法不一样的情况。
13. 待定
- iOS 主 App 用 Tauri(复用 React 界面)还是 SwiftUI:阶段 6 做完技术验证后再定,见
docs/plan.md。