Harness 的本地加密凭据保险库:Web 设置页与 vault_* 工具,可存取与复制密码、API 密钥、TOTP 与银行卡信息,支持健康审计、到期轮换、导入导出以及只读/询问访问模式。
安装
# npm 包(预构建)
dsh plugin --profile web add dsh-vault
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:Ox0400/dsh-vault
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
English | 中文
dsh-vault 是一个面向 DeepSeek Harness 的安全加密插件:把你在使用 AI 过程中产生的用户名、邮箱、手机号、密码、二次动态密钥(TOTP) 以及开发工作流中常用的 SSH 连接、API Key、Secret、OAuth access/refresh token 等敏感凭据加密存储,并通过模型工具提供增删改查、检索、密码生成与动态验证码生成能力。
安全性与实现
- 零外部依赖:全部加密基于 Node 内置
node:crypto(AES-256-GCM 认证加密 + scrypt 密钥派生 + RFC 6238 TOTP),不引入任何第三方加密库。 - 主密码:所有条目由
scrypt(主密码, 盐)派生的 256 位密钥经 AES-256-GCM 加密。密钥永不落盘;进程内解锁后缓存复用(避免每次写入重复 scrypt),重启后重新派生。 - 防篡改:GCM 认证标签 + 文档内固定明文的校验信封,错误主密码、密文被改动都会立即失败,绝不返回垃圾数据。
- 磁盘无明文:加密文档中不含任何明文凭据;每个条目独立随机 nonce。
- 原子写入:复用 harness 的
writeFileAtomic+ 文件锁,任何时刻磁盘上都是完整的新/旧文档;进程内写入串行化、跨进程写入加锁。 - 检索不泄密:
vault_search只返回摘要(id/标题/分类/用户名/邮箱/手机/主机/端口/URL/标签),绝不返回密码、密钥、令牌与 TOTP 密钥;完整凭据只能通过vault_get按 id 显式读取。
条目模型
每条记录包含一个 title、一个可选 kind 分类,以及任意组合的字段:
| 字段 | 说明 |
|---|---|
kind |
login(默认)/ ssh / api-key / secret / oauth / cookie / card / custom |
username / email / phone |
账号身份 |
password |
密码 |
host / port |
SSH 主机与端口(如 db.internal / 2222) |
privateKey |
SSH 私钥(PEM) |
apiKey |
API 密钥 |
secret |
通用 Secret(client secret、共享密钥等) |
accessToken / refreshToken / expiresAt |
OAuth 令牌对与过期时间(epoch millis) |
otpSecret |
TOTP 密钥(Base32 或 otpauth:// URI) |
url / notes / tags |
元信息 |
其中两种 kind 有各自的形状:cookie 条目保存捕获的浏览器会话;card 条目保存 cardNumber / cardExpiry(MM/YY)/ cardCvv / cardHolder —— 检索摘要只暴露到期日与持卡人,绝不给卡号与 CVV;导出 Bitwarden JSON 时映射为 card 项(type 3,品牌自动推断)。
| fields | 任意附加键值(如 {"region": "us-east-1"}),可检索 |
工具
共 113 个工具,分五组。基础是默认安装就会注册的;其余通过工具分级按需开启(设置 → 凭据库 → 权限 → 模型工具集)。
基础(默认注册) — 11
共 11 个工具。下面各组都需按需开启。
| 工具 | 说明 |
|---|---|
vault_list |
列出保险库目录下可用的保险库(一个 .json 一个,排除 access/meta/导出等旁车文件) |
vault_search |
跨标题/分类/用户名/邮箱/手机/主机/端口/URL/备注/标签/自定义字段(含数字/布尔/嵌套值;空格分隔多词 OR 命中)检索;可选 createdAfter/createdBefore 毫秒时间戳过滤;返回无密摘要;limit 须为 1–100 的整数 |
vault_get |
按 id 读取完整条目(含全部密钥) |
vault_add |
新增条目(上述字段任意组合;空字符串/空数组字段会被忽略) |
vault_update |
按 id 更新字段(未提供的字段保留;空字符串清除该字段;title 可改名;rotationDays: 0 清除轮换 = 永不轮换) |
vault_delete |
软删除条目(移入回收站,磁盘上仍加密保留) |
vault_fill |
按 host/URL/用户名/标题匹配条目并返回其凭据 |
vault_clipboard |
取出某条目某个字段的值用于直接复制(用户名/密码/apiKey 等) |
vault_totp |
为存储的 otpSecret(或直接传入的 Base32/otpauth URI)生成当前 6 位动态验证码 |
vault_generate_password |
生成强随机密码(长度/字符集/分组可选)或易记口令短语(passphrase: true,EFF 词表,words/separator/wordDigits) |
vault_strength |
零依赖密码强度评估(0–100 分,weak/fair/strong/very strong) |
管理(标准档及以上) — 53
收藏、标签、活动历史、健康检查、模板、附件,以及卡与 2FA 的细节。
| 工具 | 说明 |
|---|---|
vault_compare |
逐字段比较两个条目(onlyA/onlyB/differ/equal)——只返回字段名,绝不返回密钥值 |
vault_rename |
一次调用即可重命名条目(vault_update 的快捷方式) |
vault_restore / vault_purge / vault_restore_recent |
从回收站恢复 / 永久删除 / 撤销最近一次删除 |
vault_lock / vault_unlock |
显式锁定保险库(清空内存密钥)/ 重新解锁 |
vault_password_history |
列出条目的历史密码(1Password/Bitwarden 风格,最新在前,上限 10 条;不含当前密码) |
vault_password_rollback |
将条目密码回滚到某条历史记录(当前密码先归档,可逆) |
vault_apply_tags |
按查询批量增/删/替换条目标签(支持 dry-run,不含密钥) |
vault_totp_uri |
为存储的或裸 TOTP 密钥生成 otpauth:// 配置 URI |
vault_rotation |
报告已过期 / 待轮换 / 即将过期的凭据;soonWindowDays(1-90,默认 7)调整即将窗口(不含密钥) |
vault_health |
保险库健康扫描:弱密码/复用凭据、未启用 2FA、不安全的 http:// 站点,以及整体安全评分(0–100) |
vault_watchtower |
每个条目的风险分析(借鉴 1Password Watchtower / Bitwarden):标记弱密码、键盘序列、嵌入年份、常见密码、跨条目复用、http 站点、缺 2FA、过期——带 0–100 评分与 good/warn/poor 判定(不含机密);条目行显示 ⚠ 徽标 |
vault_breach_check |
Watchtower 风格泄露扫描:对接 Have I Been Pwned(k-anonymity,仅发送 SHA-1 前缀),附离线常见密码兜底 |
vault_integrity |
校验磁盘上的保险库文件可正确解密并与内存中的 store 一致 |
vault_merge |
合并一个条目到另一个;keepSource: true 保留源条目 |
vault_attach / vault_attachments / vault_attachment / vault_detach |
给条目附加文件(私钥、证书、配置、恢复码)——base64 存于加密条目内随库加密;列出/读取/移除 |
vault_quick_add |
快速录入(标题 + 一个密钥),支持 tags/notes |
vault_expiry |
设置/清除过期(expiresAt: 0 移除) |
vault_stats |
概览计数,含 trashCount(不含密钥) |
vault_verify |
校验单个条目或审计全部条目(all: true):按 kind 检查完整性、端口/过期合理性(不含密钥) |
vault_duplicates |
查找重复分组:mode = both(默认)/ title / content(不含密钥) |
vault_report |
可打印清单,含到期/轮换列与统计页脚(不含密钥) |
vault_match_url |
按 URL 查找匹配的登录条目(Bitwarden/1Password 风格:精确主机、子域、父域、路径前缀;www./端口归一化),带 0–100 评分——绝不返回密码 |
vault_env |
把标记 env 的条目(tags 含 env)渲染为 KEY=VALUE 行 —— 见环境变量导出 |
vault_copy |
复制条目(含密钥)到另一个命名保险库 |
vault_templates |
内建 + 用户自定义模板(save/list/remove,KeePassXC 风格);内建模板新增 Wi-Fi、服务器、数据库、身份、银行账户、银行卡(借鉴 1Password) |
vault_notes |
追加或整体替换条目的自由备注(等价于一次 vault_update) |
vault_changes |
列出某个时间窗内(默认 24 小时)的库内活动:新增/更新/软删除,最新在前 |
vault_find |
模糊查找:忽略大小写、标点与空白,所以 gh 也能命中 GitHub |
vault_mask |
把任意文本里像凭据的部分打码(API key、token、密码、私钥),便于安全地记录或引用 |
vault_history |
本进程内的近期活动,最新在前:写操作(增/改/删/恢复/清空/合并/回滚)与读操作(读取、验证码) |
vault_recent |
列出最近新增或更新的条目(最新在前),只含非敏感摘要 |
vault_pin |
收藏(置顶)条目,使其在搜索与列表中排在最前 |
vault_unpin |
取消收藏(移除 favorite 标记) |
vault_tags |
列出全库用到的所有标签及每个标签下的条目数 |
vault_generate_username |
生成随机用户名或匿名邮箱建议 |
vault_rotate_password |
生成新的强密码、写入条目,并把新值返回给调用方交给目标服务 |
vault_touch |
把条目标记为"最近使用"(只更新 updatedAt,不改内容) |
vault_note_secret |
用一个自动生成的标题存一条单独的密钥(临时记一条,不必先想标题) |
vault_search_advanced |
多条件组合搜索:标题子串、用户名/邮箱子串、kind、标签、创建时间区间、仅收藏 |
vault_count |
返回活跃条目数量(可按 kind / 标签过滤) |
vault_set_icon |
设置条目的 UI 图标(emoji)与强调色 |
vault_describe |
用自然语言描述一条目(kind、身份、主机/URL、标签、到期),不泄露任何密钥 |
vault_last_modified |
按最后修改时间列出条目,最新在前 |
vault_has |
快速判断库里是否存在匹配的凭据(标题/用户名/主机子串;exact 时按标题精确匹配) |
vault_autofill_check |
判断库里是否有可用于某 URL/主机的凭据:返回最佳匹配条目(只给用户名/邮箱,绝不给密钥),或明确"没有" |
vault_undelete_all |
把回收站里的条目全部恢复 |
vault_get_many |
按 id 批量读取多个条目,且只返回请求的字段 |
导入导出(完整档,或自定义 → 导入导出) — 27
与其它密码管理器之间的进出、批量操作,以及 env/CSV/1Password/Bitwarden/KeePass/pass 互转。
| 工具 | 说明 |
|---|---|
vault_import_csv |
从 CSV 批量导入凭据(自定义列变为 fields;overwrite: true 合并字段到已有条目,不再产生重复) |
vault_bulk_delete |
按 query/kind/tag 或显式 ids 批量软删除条目;需 confirm: true(默认仅试运行);回收站可恢复 |
vault_export / vault_import |
整库加密备份/迁移(独立导出密码) |
vault_export_bitwarden / vault_import_bitwarden |
Bitwarden/Vaultwarden JSON 互通(完整字段映射,支持覆盖) |
vault_import_bitwarden_encrypted |
解密 Bitwarden 口令保护 JSON 导出(PBKDF2/Argon2id + HKDF → AES-256-CBC + HMAC)并导入,需提供导出口令 |
vault_import_manager_csv |
密码管理器 CSV 自动识别表头:Bitwarden(login_uri/login_username/…)、1Password 8、Dashlane、NordPass、Keeper、LastPass(fav/grouping/extra);支持 dryRun 预览 |
vault_import_kdbx |
KeePass KDBX:3.1 与 4.x、AES-KDF 或 Argon2(RFC 9106)、AES-256-CBC 或 ChaCha20 载荷、支持 keyfile |
vault_export_1password |
导出为 1Password 1PUX 归档(ZIP + export.data),可导入 1Password 或用 vault_import_1password 再导入;条目类别映射 login/信用卡/API 凭据/服务器 |
vault_import_1password / vault_import_1pif |
1Password 1PUX(ZIP)与旧版 1PIF 文本导出 |
vault_import_enpass |
Enpass JSON 导出(文件夹→标签、类型化字段、TOTP) |
vault_import_keepass_xml |
KeePass 2.x XML 导出(明文或 ****** 掩码值) |
vault_import_chrome / vault_import_keychain |
从 Chrome Login Data(macOS 钥匙串 / Linux 钥匙环或 peanuts / Windows DPAPI)或 macOS 钥匙串导入密码(默认只读互联网密码 inet——真正用于网站登录的条目,classes: ["genp"] 可改读通用密码;会话缓存 + 预览,避免授权弹窗轰炸);所有文件导入均支持 dryRun 预览 |
vault_import_firefox |
Firefox 配置导入(logins.json + key4.db,NSS 3DES / PBES2-AES,支持主密码) |
vault_export_totp |
列出所有带 TOTP 密钥的条目及其标题与签发方标签 |
vault_bulk_export |
把全部条目(含密钥)导出为 JSON 文件,用于审计或迁移 |
vault_export_browser |
按浏览器密码管理器 CSV 格式导出登录条目(name,url,username,password) |
vault_migrate_keepass |
按 KeePass 2.x 导入 CSV 格式导出(Group,Title,Username,Password,URL,Notes) |
vault_export_keepass_xml |
导出为 KeePassXC 兼容的 XML 文档(KeePass 2.x schema) |
vault_import_browser |
导入浏览器密码管理器 CSV(name,url,username,password,Chrome/Firefox/Edge 导出格式) |
vault_export_wallet |
导出为 pass(标准 Unix 密码管理器)兼容的目录树:一条目一文件,首行是密码 |
vault_import_wallet |
从 pass 目录树导入:每个 .gpg(或明文)文件成为一个条目,文件名即标题,首行是密码 |
vault_export_env |
把带 env 标签的条目写成 .env 文件(KEY=VALUE,值做 shell 引号处理) |
vault_export_csv |
把条目导出为 CSV(与 vault_import_csv 同格式),可按 kind 过滤 |
浏览器会话(完整档,或自定义 → 浏览器会话) — 8
捕获与复用 cookie 会话;需要可选的 playwright-core。
| 工具 | 说明 |
|---|---|
vault_session_open |
在真实浏览器窗口中打开指定网址,供用户手动登录(密码、二次验证、验证码)——对禁止嵌入的网站也能捕获登录态(需要可选的 playwright-core) |
vault_session_collect |
收集已打开浏览器会话的全部 cookie(含 HttpOnly),保存为 cookie 条目 |
vault_session_import |
从粘贴的 JSON cookie 数组(开发者工具导出格式)或原始 Cookie 头字符串保存会话 cookie——无需浏览器的替代方案 |
vault_session_import_file |
导入 Netscape cookie-jar 文件(curl -b / wget / 浏览器扩展导出;与 vault_session_export 输出的格式一致) |
vault_session_list |
列出已保存的登录会话、cookie 数量、已过期与 7 天内过期数量(不含值) |
vault_session_export |
将会话导出为 Cookie 请求头、Netscape cookie-jar 文件(curl -b)、原始 JSON(Playwright addCookies 格式)或可直接运行的 Playwright 片段 |
vault_session_close |
关闭已打开的浏览器登录会话(已收集的 cookie 仍保留在保险库中) |
vault_session_prune |
移除已保存会话中的过期 cookie(无过期的会话 cookie 保留);preview: true 只报告不修改 |
备份与保险库文件(完整档,或自定义 → 备份与文件) — 14
备份、切换保险库、换密钥、恢复码与系统搜索。
| 工具 | 说明 |
|---|---|
vault_recovery_code / vault_verify_recovery / vault_recovery_status |
一次性保险库恢复码(1Password/Bitwarden 风格):32 位代码仅显示一次,只保存其 SHA-256 哈希;可验证代码持有;查询是否已设置 |
vault_rekey |
原地升级 scrypt KDF 参数并重加密 |
vault_switch |
为当前会话切换活动保险库 |
vault_backup / vault_backup_now |
时间戳加密备份,文件名 <库名>-backups-YYYY-MM-DD_HH-MM-SS-<hex>.json(库名+日期一目了然);自动保留最近 N 份 |
vault_restore_backup |
从备份还原:mode: "merge"(默认)把备份条目复制进当前库,出现在条目列表;mode: "replace" 整体覆盖(先写安全快照) |
vault_vault_rename / vault_vault_delete |
重命名命名库(文件移动,当前会话跟随)或永久删除(default 库受保护) |
vault_search_system |
在 Chrome / 钥匙串中搜索站点与用户名——绝不暴露密码 |
vault_backup_status |
报告距上次备份已过去多少天(1Password 式备份提醒) |
vault_search_history |
搜索时包含已软删除(回收站)的条目,并标注其删除状态 |
vault_backups |
列出可用的加密备份文件(最新在前)及其绝对路径与时间戳 |
典型用法:把 SSH 凭据存成 kind: ssh(host/port/username/password 或 privateKey),让模型先 vault_search 找到主机、再 vault_get 取连接信息;api-key/oauth 条目用于 API 网关的 access/refresh token 轮换。
安装
dsh-vault 是一个 bundle(声明 dsh.bundle 的包):安装到 profile 后,它的 cordis.patch.yml 会自动插入 vault 插件行(按包名 dsh-vault 引用,主密码经 DSH_VAULT_PASSWORD 环境变量注入)。包内含自包含构建脚本,git 安装时会自动编译 lib/。
以下四种安装方式均已端到端实测通过(安装 → bundle 层被识别 → 插件激活且 7 个 vault_* 工具全部注册 → vault_add/vault_get 真实往返成功 → 卸载移除 layer):
| 方式 | 命令 | 需构建 | 需 allowBuilds |
|---|---|---|---|
| npm | add dsh-vault |
否(预构建 lib) | 否 |
| GitHub | add github:Ox0400/dsh-vault#v0.1.1 |
是(prepare) | 是(首次) |
| 本地路径 | add /绝对/路径/to/dsh-vault |
否(链接已构建源码) | 否 |
| tarball | add ./dsh-vault-0.1.1.tgz |
否(预构建 lib) | 否 |
方式一:从 npm 安装(最省事)
dsh plugin --profile web add dsh-vault
npm 包自带预构建的 lib/ 产物,无需 allowBuilds、无需本地编译,安装即用。启动前设置主密码:
export DSH_VAULT_PASSWORD='你的强主密码'
方式二:从 GitHub 安装(锁 tag 或 commit)
dsh plugin --profile web add github:Ox0400/dsh-vault#v0.1.1
git 安装拉取的是源码,prepare 脚本会在安装时构建 lib/。pnpm ≥10 默认阻止 git 依赖执行构建脚本。实测流程:
执行
add命令——会因allowBuilds失败,并打印需要放行的精确键(含仓库 URL 与解析后的 commit hash 的那行):allowBuilds: dsh-vault@https://codeload.github.com/Ox0400/dsh-vault/tar.gz/<sha>: true把该精确键追加到 profile 的
pnpm-workspace.yaml($DSH_HOME/profiles/<name>/pnpm-workspace.yaml):packages: - . allowBuilds: dsh-vault@https://codeload.github.com/Ox0400/dsh-vault/tar.gz/<sha>: true重新执行
add——pnpm 现在会运行prepare脚本,构建lib/并完成安装。
建议锁定 tag/commit 再安装,避免上游推送改变安装时执行的代码。允许构建 = 允许该包的代码在你的机器上于安装时执行;只对你信任的源码授予。
方式三:本地路径安装到 profile
dsh plugin --profile web add /绝对/路径/to/dsh-vault
pnpm 将 checkout 链接进 profile;只要 lib/ 存在(必要时先在 checkout 里执行 pnpm build)即被识别为 bundle。
方式四:本地 tarball 安装
npm pack && dsh plugin --profile web add ./dsh-vault-0.1.1.tgz
tarball 自带预构建 lib/ 产物,无需构建或 allowBuilds。
dsh plugin --profile web remove dsh-vault 卸载(同时移除依赖与 layer)。
命令行
插件把保险库交给助手;自带的 dsh-vault 命令把同一个保险库交给 shell 与脚本,于是技能里的子进程可以自己取密钥,明文永远不进模型上下文。
0. 先确认:你能敲的是哪条命令?
没安装就没有 dsh-vault 这个可执行文件 —— 插件是 Cordis bundle,dsh 也没有插件子命令注册表(启动器只解析自己的参数;dsh plugin … 只是转发给 pnpm,所以 dsh web exec … 会把参数交给 web app,报 error: too many arguments)。看下面的例子前,先找到你那一行:
| 你的情况 | 能敲的命令 |
|---|---|
源码 checkout(git clone + pnpm build) |
pnpm vault —— 或 node lib/cli.js、./lib/cli.js |
| 插件装进了 profile | pnpm dsh plugin --profile web exec dsh-vault |
全局安装(npm i -g dsh-vault) |
dsh-vault |
| 什么都没装 | npx dsh-vault |
# 源码 checkout(就是本仓库当前的状态):
pnpm vault list
# 全局安装后(npx 同理)才是:
dsh-vault list
npx dsh-vault list
下面的例子统一写成 dsh-vault …,请替换成你那一行的写法(checkout 用 pnpm vault …)。
1. env 是干什么的
get 取一个值,不需要任何标签。dsh-vault env 是另一种场景:脚本要一次拿好几个密钥。它把打了 env 标签的条目打印成 shell 赋值语句,让脚本自己取走:
$ dsh-vault env
DEMO_API_KEY='sk-demo-123'
OTHER_PASSWORD='pw-not-exported'
eval "$(dsh-vault env)" 就是把这些赋值读进当前 shell —— 这通常才是你要用它的原因:
eval "$(dsh-vault env)" # KEY=VALUE 变成 shell 变量
echo "$DEMO_API_KEY"
两个前提要知道:
- 只有打了
env标签的条目才会被导出。 一个都没打时,输出的是dsh-vault: nothing to export — no entry carries the "env" tag.;先在 UI 里给条目的标签加上env,或让助手执行vault_update { id, tags: ["env"] }。 - 名字默认由标题 + 字段推导(
DASHSCOPE+apiKey→DASHSCOPE_API_KEY);想固定名字就在条目的envKeys里指定 —— 见环境变量导出。
命名 —— 只有三个,其中两个故意都叫 env:
| 名字 | 是什么 | 含义 |
|---|---|---|
env |
条目的标签 | 决定哪些条目会被 dsh-vault env 导出;不打标签就不导出 |
env |
命令 | 打印带该标签的条目 |
envKeys |
条目的字段(可选) | 固定确切名字,例如 ["DASHSCOPE_API_KEY"];旧的单数写法 envKey 仍作为"只写一个"的简写接受 |
除此之外没有任何叫 env* 的东西:list --json / show 只报 envKeys(该条目会导出的名字,也就是 get 接受的名字)、envTagged(是否被 env 包含)与 hasTotp(totp 能否为它生成动态码)。
2. 有哪些命令
dsh-vault list # 分组显示:哪些会被 env 导出、哪些不会
# Exported by `env` (tag "env") — 1 entry:
# a1b2c3d4-… api-key DASHSCOPE → DASHSCOPE_API_KEY
# Not exported (no "env" tag) — 1 entry; `get <name>` still works for it:
# 9f8e7d6c-… api-key Example Billing → EXAMPLE_BILLING_API_KEY
dsh-vault get my-entry # 取该条目的主密钥,仅 stdout
dsh-vault get my-entry --field apiKey # 取指定字段
dsh-vault get my-entry --mask # 只确认存在,不打印值
dsh-vault show my-entry # 非敏感字段的 JSON
dsh-vault totp my-entry # 当前的 TOTP 动态码(只有 6 位数字)
dsh-vault totp my-entry --json # 附带 digits / period / secondsRemaining
dsh-vault env # 带 env 标签的条目 → KEY=VALUE
dsh-vault env --mask # 人类视角,两段都是 KEY=VALUE:
# ## exported items
# NPM_REGISTRY_API_KEY=npm_***
# ## unexported items
# EXAMPLE_BILLING_API_KEY=***
dsh-vault export-env .env # ……或写成 0600 权限的 .env 文件
dsh-vault verify # 校验主密码,stdout 无输出
密钥只走 stdout,其余(进度、错误)走 stderr,所以 $(dsh-vault get …) 与管道都正常;退出码 0 成功、1 运行错误、2 用法错误。
totp 是为了让脚本能自动完成两步验证、又完全不碰长期密钥:code=$(dsh-vault totp my-entry) 只输出六位数字。如果你用 get my-entry --field otpSecret,打印的是密钥本身 —— 只有确实要把密钥导入别处时才那么做。--json 会多给一个 secondsRemaining,脚本据此等一个新窗口,而不是硬闯即将过期的码。
3. shell 引号:不是 export $(…)
正确的是 eval "$(dsh-vault env)";export $(dsh-vault env) 是错的。命令替换会按空白拆分,而且不会去掉 CLI 输出的引号 —— 值里只要有一个空格就会被拆成多个词,所有值还会带上字面引号:
$ export $(dsh-vault env); echo "$WITH_SPACE"
['pass] # 在空格处被拆断
bash: export: `here': not a valid identifier
$ eval "$(dsh-vault env)"; echo "$WITH_SPACE"
[pass word here] # 按原样读入
脚本里更推荐用文件形式,完全不用 eval:
dsh-vault export-env .env && set -a && . ./.env && set +a
4. 主密码
依次取 --password-stdin → $DSH_VAULT_MASTER_PASSWORD(或 $DSH_VAULT_PASSWORD)→ 交互式提示(输入不回显,终端会切到 raw mode;tests/e2e/cli-prompt.mjs 会验证这一点);它不会去读插件配置文件里的密码,所以 CLI 既不依赖、也不会泄露 profile patch 里那份:
DSH_VAULT_MASTER_PASSWORD=… pnpm vault list
printf '%s\n' "$PASSWORD" | pnpm vault list --password-stdin
5. 安装细节
源码 checkout:
git clone git@github.com:Ox0400/dsh-vault.git && cd dsh-vault
npm install && npm run build # 生成 lib/,含可执行的 lib/cli.js
pnpm vault list # 仓库自带入口(package.json script)
node lib/cli.js list # 等价写法
./lib/cli.js list # 文件本身可执行
npm link # ……或把真正的 dsh-vault 放到 PATH
要开发插件并对接 profile,还需要把 checkout 软链进 profile 并加一层 patch 插入插件(本仓库平时就是这么开发的);CLI 完全不需要这些 —— 它直接读保险库文件。手工软链的插件没有 .bin/dsh-vault 垫片,pnpm … exec 会报 Command "dsh-vault" not found;要么按依赖正常安装(pnpm dsh plugin --profile web add dsh-vault),要么手动建垫片:
ln -sf ../dsh-vault/lib/cli.js ~/.dsh/profiles/web/node_modules/.bin/dsh-vault
npm 安装:
pnpm dsh plugin --profile web add dsh-vault # 装进 profile(插件与 CLI 一起)
pnpm dsh plugin --profile web exec dsh-vault list # --profile 必填
npm i -g dsh-vault && dsh-vault list # 全局
npx dsh-vault list # 或者干脆不装
读命令(list、get、env、show、totp、verify、export-env)只依赖 Node,所以全局与 npx 都能直接用;只有写保险库才需要 harness 运行时,而插件环境一定有。
profile 这条路还有两个注意点:垫片可能被之后在该目录执行的 pnpm install 清掉;pnpm dsh plugin --profile web add dsh-vault 会依据包的 dsh.bundle 声明重整 dsh.profile.bundles —— 如果你自己的 patch 层也插入了这个插件,就会挂两份,要先删掉 patch 层那一行。
泄露检查的隐私边界
vault_breach_check(以及安全页)可以把密码拿去和 Have I Been Pwned 比对。离开本机的只有一样东西:
GET https://api.pwnedpasswords.com/range/<SHA-1(密码) 的前 5 个十六进制字符>
Add-Padding: true
响应会返回该前缀桶里的全部后缀(几百条哈希),匹配在本地完成 —— 完整哈希、密码原文、以及它属于哪个条目都不会离开。如果密码已经在内置常见口令表里,根本不会发起请求;同一前缀的重复查询会在内存里缓存一小时。
k-anonymity 没有隐藏的东西,直说:
- 5 个字符的前缀(20 bit,约百万分之一桶)会连同你的 IP 发给 Cloudflare;
- 能枚举候选口令的观察者,可以算出每个候选的前缀,从而看出你是否查询过那个桶 —— 这是该协议的已知局限,加请求头也解决不了;
Add-Padding只是抹掉响应体积这个信号(不加它,桶的响应长度会和"该密码有多常见"相关)。
如果连前缀都不想出去,就用 online: false —— 此时只查离线词表,覆盖率小得多,适合当筛子而不是定论。
强度指示
列表里每个条目在标题后有一个三星强度指示 —— ☆☆☆ = 0 分,★★★ = 满分,中间按半星分档:
半星是真正的左半实心、右半空心,不是在旁边写个 ½:实现是把同样三颗星叠两层、按百分比裁剪彩色层,而 ★/☆ 字宽一致,所以裁剪正好落在星的边界上。
| 分数 | 实心星数 | 档位 |
|---|---|---|
| 0–8 | 0 | 弱 |
| 9–24 | ½ | 弱 |
| 25–41 | 1 | 弱 |
| 42–58 | 1½ | 中 |
| 59–74 | 2 | 中 |
| 75–91 | 2½ | 强 |
| 92–100 | 3 | 很强 |
悬停可看到精确分数与评价。分数在 host 侧计算(列表本身永远拿不到密钥),针对条目的密码或银行卡 PIN;机器生成的 API Key、私钥故意不评分,免得所有密钥都是满分。
环境变量导出
打上 env 标签的条目可以用 vault_env / vault_export_env 导出为 KEY=VALUE 行,名字按各家工具链预期的字段名生成:
| 条目 | 导出的键 |
|---|---|
标题 DASHSCOPE + apiKey |
DASHSCOPE_API_KEY |
标题 DASHSCOPE + prefix: APP_ |
APP_DASHSCOPE_API_KEY |
envKey: "TAVILY_TOKEN" + accessToken |
TAVILY_TOKEN |
同一条目的 refreshToken / fields.scope |
TAVILY_TOKEN_REFRESH_TOKEN / TAVILY_TOKEN_SCOPE |
字段后缀遵循惯例:
apiKey → API_KEY、accessToken → ACCESS_TOKEN、refreshToken → REFRESH_TOKEN、privateKey → PRIVATE_KEY、password → PASSWORD、cardNumber → CARD_NUMBER。派生名字对不上脚本时,给条目设置环境变量名(可选字段
envKeys,也可用vault_add/vault_update写入,编辑器里用逗号分隔):按顺序一一对应、且原样使用(不加前缀、不从标题推导):envKeys导出结果 ["DASHSCOPE_API_KEY"]DASHSCOPE_API_KEY=apiKey["GOOGLE_ACCESS_TOKEN", "GOOGLE_REFRESH_TOKEN"]分别对应 accessToken、refreshToken条目有 3 个密钥、只配 ["MY_KEY"]MY_KEY、MY_KEY_REFRESH_TOKEN、MY_KEY_SCOPE每个名字必须是合法 POSIX 名(
[A-Za-z_][A-Za-z0-9_]*)、最多 8 个、不可重复。envKey(单个字符串)仍作为"只配一个名字"的简写兼容,写入时会被合并进envKeys。自定义字段同样会导出,形如
<BASE>_<字段>。
升级提示:1.10.64 起键名从
DASHSCOPE_APIKEY改为符合厂商惯例的DASHSCOPE_API_KEY。若脚本依赖旧写法,给条目设置envKey固定名字即可。
工具分级(工具集)
插件内置 110+ 个模型工具,但默认只注册 11 个基础工具(vault_list/search/get/add/update/delete/fill/clipboard/totp/generate_password/strength),让模型的工具目录保持精简、减少 token 开销。
在 设置 → 凭据库 → 权限 → 模型工具集 切换,立即生效、无需重启,并按保险库持久化:
| 档位 | 注册内容 |
|---|---|
| 基础(默认) | 仅核心 —— 日常增删改查/搜索/生成/验证码/填充 |
| 标准 | + 管理: 收藏、标签、图标、到期轮换、健康、重复合并、附件、模板、env 掩码 |
| 完整 | 全部,含批量导入导出、浏览器会话、备份与保险库文件操作 |
| 自定义… | 基础 + 任选: 管理 / 导入导出 / 浏览器会话 / 备份与文件 |
也可在插件配置里写 tools: basic|standard|full|custom;界面选择优先,并保存在 <vault 目录>/tools.json(工具集是进程级设置,不随保险库变化)。
配置
[!WARNING] 安全:不要把主密码明文写进配置 profile patch(
cordis.patch.yml/cordis.yml)是很容易被提交进 git、截图或进日志的配置文件 —— 一行明文的masterPassword:就等于泄露了整个加密保险库的钥匙。请一律使用masterPasswordEnv并把真实密码放进进程/ shell 环境(例如export DSH_VAULT_PASSWORD='…')。 凭据类插件的证据化检查清单见 community-plugin-audit handbook。# ✗ 不要 —— 配置文件里写明文主密码 - id: vault name: dsh-vault config: masterPassword: 'my-secret'# ✓ 应该 —— 引用环境变量,并让文件本身保持 0600 - id: vault name: dsh-vault config: masterPasswordEnv: DSH_VAULT_PASSWORD
| 配置项 | 说明 |
|---|---|
masterPassword |
直接配置主密码(会出现在 cordis.yml 中,不推荐) |
masterPasswordEnv |
环境变量名,运行时从该变量读取主密码(推荐) |
path |
保险库文件路径,默认 $DSH_HOME/vault/default.json |
name |
保险库名,用于默认路径(如 name: work → $DSH_HOME/vault/work.json) |
accessMode |
模型工具的访问策略,三态:readonly(只读,工具与设置页的增/改/删全部被拒绝)、ask(默认——写入前询问,每次增/改/删都会走 harness 审批通道请你确认)、auto(自动读写,无需逐次确认)。设置页提供同样的三选一下拉并持久化到 <vault 目录>/access.json。 |
autoCapture |
false(默认)。设为 true 时,系统提示词会指导模型识别对话中出现的凭据,并按用户偏好用 vault_add 提供保存。 |
lockTimeoutSeconds |
自动锁库:空闲超过该秒数后自动重新锁定(清空内存密钥),之后每次读写需 vault_unlock。0/缺省为禁用。 |
exportPasswordEnv |
存放 vault_export/vault_import 导出密码的环境变量名(绝不能作为模型参数传入)。 |
backupRetention |
保留多少个加密备份(默认 10);vault_backup 自动清理更旧的副本。 |
示例:
- id: vault
name: dsh-vault
config:
masterPasswordEnv: DSH_VAULT_PASSWORD
accessMode: ask
autoCapture: true
autoCapture: true 时,当你在对话中分享凭据(如 "我的 npm token 是 npm_…"),助手会提议存入;你同意后立即调用 vault_add。autoCapture 关闭时,只有你明确要求才保存。设置页显示当前模式(只读 / 写入前询问 / 自动读写)可用下拉切换,还有自动捕获开关(检测对话中的凭据 → 提议保存)、类型筛选、健康与轮换摘要、回收站视图、以及默认遮罩的密钥字段(显示/隐藏切换)。
首次调用任一工具时自动创建保险库;之后每次启动用主密码重新解锁。忘记主密码 = 数据永久丢失(无后门,这是设计使然)。
开发
本地 clone 开发:
git clone git@github.com:Ox0400/dsh-vault.git
cd dsh-vault
pnpm install # 安装 devDependencies(typescript/tsdown/vitest 等)
pnpm build # 构建 host 侧 lib/*.js 与浏览器 bundle lib/client.js
pnpm test # 运行 506 项 vitest 测试
测试需要 harness 的
dsh-llm/dsh-system-prompt等 peer 包,在 harness monorepo 内开发时由 workspace 链接提供。
凭据列表里的品牌图标是内联进浏览器包的 68 条 SVG 路径 —— 没有运行时依赖,也不联网。它们是生成的,而且生成器就在仓库里:
npm i --no-save simple-icons@16 # 15 MB,仅开发用;永远不会成为本包的依赖
pnpm gen:icons # 重新生成 src/client/site-icons.ts
pnpm gen:icons -- --check # 与上游不一致时退出 1
生成器只重写图标表;gist.github.com、console.cloud.google.com 这类主机→品牌映射是人工整理的判断,会原样保留。simple-icons 后来下架的品牌(amazonaws、slack、linkedin 等)会被保留并列出,而不是被静默丢掉。
常用命令:
# 单元 + 集成测试(vitest,506 项)
pnpm test # 或 npx vitest run
# 类型检查
pnpm typecheck # tsc -p tsconfig.json --noEmit
# 构建(host 侧 lib/*.js 与浏览器 bundle lib/client.js)
pnpm build # = build:host (tsc) + build:client (tsdown)
# 打包发布(可选:npm pack 产物可直接 `dsh plugin add ./dsh-vault-0.1.1.tgz`)
npm pack
仓库内所有测试通过:506/506(crypto/TOTP/密码生成/store CRUD/网关/集成)。
浏览器侧检查跑在真实 dsh web 上(不属于 vitest),且一律拒绝在 default 库上运行:
node tests/e2e/theme-check.mjs # 双主题 token、进度环轨道、附件行
node tests/e2e/polish-check.mjs # 空态文案、相对时间悬停
node tests/e2e/contrast-audit.mjs # 8 个标签页 × 双主题的 WCAG 对比度扫描
安全规则与「让对比度审计说谎的两个坑」见 tests/e2e/README.md。
不让凭据进入公开内容
本仓库的一切都是公开的 —— npm 包、GitHub release、issue 与 discussion 帖子。发布前用 pnpm scan:secrets 扫描工作区、被跟踪文件与提交信息中所有「像凭据」的字符串;pnpm scan:public 会额外扫描已经发布出去的每个 release 与评论。本地扫描也包含在 pnpm test 里,所以被提交进来的凭据会让测试失败,而不是被发出去。
主题适配
UI 不写死颜色,而是读取宿主的设计变量:本地语义层映射到真实的 --dsw-alias-* 命名空间,并带字面量兜底,脱离 DeepSeek Harness 也能正常渲染。
--v-text: var(--dsw-alias-label-primary, #1f2328);
--v-border: var(--dsw-alias-border-l2, #d9d9d9);
--v-success-text: color-mix(in srgb, var(--v-success) 55%, var(--v-text));
因为别名会随 body[data-ds-dark-theme] 翻转,浅色/深色自动跟随宿主 —— 没有第二套样式表,也没有主题参数。
这层语义层要守住的两条规则:
- 状态色
state-*-primary不能当正文颜色。 它们是为填充块和图标调的:白底上 success 只有 2.28:1、warn 只有 2.15:1,远低于 WCAG AA。--v-*-text变体把它们与主题正文色混合,于是同一条声明在浅色下压暗、深色下提亮(浅色 5.8/5.6/7.5:1,深色 12+/11+/6.5:1)。 - 不要用
opacity弱化文字。opacity: .8会把 5.8:1 变成 3.9:1。要「次要」就用 token(--v-text-2),不要用透明度。
tests/e2e/contrast-audit.mjs 会在两套主题、全部标签页上验证这两条。
打包与发布
本包是标准 npm bundle:
dsh.bundle.patch→cordis.patch.yml(安装到 profile 后自动应用的 layer)dsh.client→ 浏览器端声明(exports["./client"]指向lib/client.js)prepare脚本 → git 安装时自包含构建(tschost +tsdownclient)- 没有运行时
dependencies:宿主提供的一切都走peerDependencies,不会产生重复实例。playwright-core只有浏览器登录会话用得到、且是懒加载,因此声明为可选 peer —— 普通安装不会下载它(约 13 MB)。需要那组工具时,把它装进你的 profile:cd ~/.dsh/profiles/web && npm i playwright-core
可选发布途径:
npm pack # 产出 tarball → dsh plugin add ./dsh-vault-0.1.1.tgz
npm publish --access public # 发布 npm → dsh plugin add dsh-vault
安全边界与已知限制
它保护什么
- 离开这台机器的保险库文件 —— 备份、同步盘、被拿走的硬盘。条目用 AES-256-GCM 加密,密钥由主密码经 scrypt 派生(N=32768, r=8, p=1,参数持久化在文档里,以后可提升;旧文档仍可解密)。
- 同一台机器上的其他用户 —— 保险库文件与 CLI 写出的文件都是
0600。 - 秘密意外进入模型上下文 ——
vault_search/vault_update从不返回密码、密钥或令牌;CLI 的存在就是为了让脚本取密钥时不必经过一次对话。 - 把密码写进配置文件 —— 前提是你用
masterPasswordEnv而不是masterPassword(见上方警告)。
它不保护什么
- 同一个宿主进程里的其他插件。 dsh-vault 自己就是个插件:它与 harness 同进程运行,解密后的条目就在那个进程的内存里。
readonly/ask/auto约束的是模型的工具调用,不是插件之间的隔离。harness 对自己的凭据存储也是同样的说法:agent 的工具进程以你的 OS 用户身份运行,因此"无法把秘密与 agent 隔离"(@deepseek-ai/dsh-credentials-local)。只有把插件放到独立进程运行才能改变这一点,而这不是某个插件能自己做到的。 - 被攻陷的机器或账号 —— 键盘记录器、挂在 harness 进程上的调试器,或任何以你的身份运行的东西。
- 你自己敲进文件、截图或 shell 历史里的主密码。 所以才要
masterPasswordEnv。 - 模型拿到值之后做什么。
vault_get会把值放进该次工具调用的结果,也就是模型上下文;vault_search/vault_update则从不暴露。
减少暴露面
lockTimeoutSeconds 会在空闲后自动锁库,vault_lock 可以随时手动锁;两者都会清空内存中的密钥。把主密钥交给操作系统钥匙串(macOS Keychain / Windows Credential Manager / libsecret)能让明文密码不再出现在配置与环境文件里 —— 也就是"磁盘被复制后仍然存在"的那部分暴露 —— 但它不会造出进程内边界:以你的身份运行的插件同样可以向钥匙串索取同一个条目。DSH 项目目前把"OS 钥匙串凭据提供方"列为待做事项。
已知限制:本插件面向单机/个人部署;团队共享保险库不在范围内。
已收录于
- awesome-dsh-plugin — 官方精选目录(安全与防护分类)
- awesome-deepseek-harness (Dominic789654) — 社区目录(安全分类)
等待维护者合并的提交:Anil-matcha/awesome-dsh-plugin #127 · 0xsline/awesome-deepseek-harness #563 · dsh-handbook #65
链接
同类插件
toby-bridges/api-relay-audit★ 884
从 DeepSeek Harness 对 AI API 中转站和 LLM 代理运行本地安全审计,生成 Markdown 报告,覆盖提示词注入、模型替换信号、工具调用改写、错误泄漏、流完整性和按 profile 启用的 Web3 风险。
SeaOf0/dsh-redteam-model★ 716
面向授权安全研究的 DSH 合集:九个工作模式(redteam 总控、渗透测试、代码审计、二进制分析、攻防评估、免杀对抗、应急溯源、云安全攻防、CTF 解题)与十五个运行时插件,设置页管理台支持一键部署、安装、更新与卸载。
howmp/dsh-pentest★ 609
面向 DeepSeek Harness 的授权渗透模式:以探索链路记录目标、线索、资产与漏洞,并在 Web 中可视化展示。
PerryLink/dsh-auto-review★ 233
审批链上的第二模型自动审查:只读审查子代理返回带理由的 allow/deny 结构化裁决,默认 fail-closed。
ADWMC/helm-d#helmd★ 225
单包逆向与渗透测试安全分析插件:首轮工具收敛、领域路由与 33 个工具,覆盖 APK、Web、原生二进制、协议、恶意样本与 LLM 场景的逆向分析、加壳脱壳与 License 破解绕过;内置 H-CoT 评估引擎(语义路由、/hcot 命令)、账本驱动的 Web 工作台与工具货架、磁盘案件工作区(证据链自动存证、E 编号结论校验、上下文压缩后恢复)、GitHub 工具检索与 361 篇按需参考文档。
NanmiCoder/dsh-auto-mode★ 164
在 Workspace Write 与 Full access 之间增加 Auto 权限档:日常操作留在官方 workspace-write 沙箱内,由当前会话模型复核升权与破坏性调用,精确的越界访问按次放行一次,意图不明时询问,命中关键路径则拒绝。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。