Skip to content

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. 原则 ​

  1. agent 默认拿不到明文。 agent 能做的是"用"密钥:注入到一条命令、代发一个 HTTP 请求、代填一个网页表单、代签一次 SSH。要直接取出明文,必须是条目上单独开过的例外,而且每次都要人确认。
  2. 所有改动都签名,每个文件只有一个写入者。 这样不用服务器也能做多设备同步和多人共享,而且能检测篡改和回滚。
  3. 核心逻辑全在 Rust 里。 后台进程、CLI、浏览器扩展、管理界面、以后的 Swift 扩展都只是客户端,规则只在一个地方实现。
  4. 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 ‖ epoch
  • Secret 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。
  • 生物识别只是便利,不能替代主密码。 以下情况必须输主密码:

    • 重启后第一次解锁;
    • 距上次输主密码超过 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、附件、标签、归档、历史通行密钥(桌面版导不出)
1PasswordCSV(兜底)登录、密码自定义字段、附件
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
macOSpassno.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 管理—
WindowsMSIX 包里带 pn.exe用 MSIX 的 AppExecutionAlias,装完直接就能在 PATH 里用,不需要管理员
Linuxdeb、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 友好的帮助和输出 ​

  1. 帮助分三层:
    • pn --help:一屏以内。说明 passno 是什么,列出最常用的 8 条命令,每条带一个例子,最后一行写"AI agent 请先运行 pn agent-help"。
    • pn <命令> --help:参数、2–4 个可以直接复制的例子、输出格式、可能的错误码。
    • pn help <命令> --json:机器可读的帮助,包括参数、类型、默认值、输出的 JSON Schema、退出码。
    • 三层都从 clap 定义自动生成,同时导出 man 页和 docs/cli/*.md,保证只有一个来源。
  2. pn agent-help:给 LLM agent 的简明指南(Markdown,200 行以内):
    • passno 的安全模型:拿不到明文,只能"用",每次使用都要人批准;
    • 推荐的调用方式:先 pn search,然后 pn run、pn http 或 pn fill;
    • 遇到"待批准"、"已拒绝"、"已锁定"时怎么办;
    • 典型任务的完整示例;
    • 明确告诉 agent:"不要试图读取或打印密钥,这种请求会被拒绝并记进审计日志。"
  3. pn agent install-skill: 把上面的指南写成 skill 或说明文件,装到 Claude Code(~/.claude/skills/passno/)、Codex 等 agent 的约定位置。也可以 --print 只打印不安装。
  4. 输出:
    • 所有读操作都支持 --json,字段名稳定,带 schema_version。列表和事件流用 NDJSON。
    • 默认输出给人看(表格、颜色)。stdout 不是终端时自动去掉颜色。
  5. 不交互、不卡住:
    • stdin 不是终端时,绝不弹出交互式提问。缺参数就直接报错,并给出正确用法。
    • 需要人批准时,立即返回:
      json
      {"status":"pending_approval","grant_id":"…","hint":"请用户在 passno 中批准;或加 --wait 60 等待"}
      --wait <秒> 用来等待批准,最长 120 秒。
  6. 退出码固定:
退出码含义
0成功
2用法错误
3保险库已锁定
4被拒绝
5找不到
6等待批准超时
7后台进程没运行
8策略不允许
  1. 错误信息三段式: 发生了什么、为什么、下一步运行什么。例如:
    error: 保险库已锁定
    原因: 空闲 15 分钟后自动锁定
    下一步: 请用户在 passno 中解锁(Touch ID),或运行 pn unlock
    命令打错时给出最接近的建议。
  2. CLI 和 MCP 共用一套说明。 pn mcp 的工具描述和 pn agent-help 出自同一个定义,不会出现两边说法不一样的情况。

13. 待定 ​

  • iOS 主 App 用 Tauri(复用 React 界面)还是 SwiftUI:阶段 6 做完技术验证后再定,见 docs/plan.md。

文档对应提交 80b7c2d · 由仓库 docs/ 自动生成