🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

加密凭据保险库 Ox0400/dsh-vault

DeepSeek Harnessv1.10.75
Ox040018 天前有提交已提供中文文档扫描:含敏感能力

dsh-vault — 为 DeepSeek Harness 打造的加密凭据保险库

37.7/100
综合分
12
GitHub 星标
871
npm 周下载
—/5
用户评分

数据截至 2026/10/16 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-vault
npm 源 · dsh-vault 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · market
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 18 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-vault @ 1.10.75
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/30 15:56:31

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 145 处,展示前 50 条)
  • 读取本地文件
    lib/index.js:117
    const dirs = readdirSync(base);
  • 读取本地文件
    lib/index.js:118
    const withLogins = dirs.filter(d => existsSync(join(base, d, 'logins.json')));
  • 读取本地文件
    lib/index.js:127
    return statSync(join(base, b)).mtimeMs - statSync(join(base, a)).mtimeMs;
  • 读取本地文件
    lib/index.js:1048
    const data = await readFile(args.path);
  • 读取本地文件
    lib/index.js:1848
    const entries = await readdir(dir);
  • 写入本地文件
    lib/index.js:1877
    await mkdir(dirname(args.path), { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/index.js:1878
    await writeFile(args.path, JSON.stringify(payload, null, 2), { mode: 0o600 });
  • 写入本地文件
    lib/index.js:2035
    await mkdir(dirname(args.path), { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/index.js:2037
    await writeFile(args.path, '\uFEFF' + rows.map(r => r.join(',')).join('\n') + '\n', { mode: 0o600 });
  • 写入本地文件
    lib/index.js:2194
    await mkdir(dirname(args.path), { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/index.js:2196
    await writeFile(args.path, '\uFEFF' + rows.join('\n') + '\n', { mode: 0o600 });
  • 读取本地文件
    lib/index.js:2324
    const raw = await readFile(args.path, 'utf8');
  • 删除本地文件
    lib/index.js:4413
    await unlink(stale);
  • 删除本地文件
    lib/index.js:4671
    await unlink(source);
  • 删除本地文件
    lib/index.js:5174
    await unlink(stale);
  • 删除本地文件
    lib/index.js:5980
    await unlink(target);
  • 删除本地文件
    lib/index.js:6148
    await unlink(source);
  • 删除本地文件
    lib/index.js:6155
    await unlink(join(dir, sidecar));
  • 读取本地文件
    lib/cli.js:412
    if (!existsSync(path)) {
  • 写入本地文件
    lib/cli.js:606
    await mkdir(dirname(file), { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/cli.js:607
    await writeFile(file, body, { mode: 0o600 });
  • 写入本地文件
    lib/cli.js:608
    await chmod(file, 0o600);
  • 写入本地文件
    lib/store.js:137
    await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
  • 删除本地文件
    lib/store.js:142
    await rm(`${this.path}.lock`, { force: true }).catch(() => { });
  • 写入本地文件
    lib/store.js:145
    await chmod(dirname(this.path), 0o700).catch(() => { });
  • 写入本地文件
    lib/store.js:328
    await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
  • 读取本地文件
    lib/store.js:349
    raw = await readFile(this.auditPath, 'utf8');
  • 写入本地文件
    lib/store.js:886
    await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
  • 读取本地文件
    lib/store.js:911
    const raw = await readFile(this.path, 'utf8');
  • 写入本地文件
    lib/store.js:941
    await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
  • 读取本地文件
    lib/store.js:972
    raw = await readFile(this.path, 'utf8');
  • 发起网络请求
    lib/breach.js:44
    const res = await fetch(`${HIBP_RANGE_URL}${prefix}`, {
  • 执行外部命令固定命令
    lib/chrome.js:40
    return execFileSync('security', ['find-generic-password', '-a', 'Chrome', '-s', service, '-w'], { encoding: 'utf8' })
  • 执行外部命令固定命令
    lib/chrome.js:44
    return execFileSync('security', ['find-generic-password', '-s', service, '-w'], { encoding: 'utf8' })
  • 执行外部命令固定命令
    lib/chrome.js:73
    const pw = execFileSync('secret-tool', ['lookup', 'application', app], { encoding: 'utf8' }).replace(/ +$/, '');
  • 读取本地文件
    lib/chrome.js:86
    const raw = readFileSync(localStatePath, 'utf8');
  • 执行外部命令固定命令
    lib/chrome.js:104
    const out = execFileSync('powershell.exe', ['-NoProfile', '-Command', script], { encoding: 'utf8' }).trim();
  • 写入本地文件
    lib/chrome.js:194
    copyFileSync(dbPath, copy);
  • 读取本地文件
    lib/chrome.js:197
    if (existsSync(extra))
  • 写入本地文件
    lib/chrome.js:198
    copyFileSync(extra, copy + suffix);
  • 执行外部命令固定命令
    lib/chrome.js:204
    const out = execFileSync('sqlite3', ['-separator', sep, copy, "SELECT origin_url, username_value, quote(password_value) FROM logins"], { encoding: 'ut
  • 删除本地文件递归
    lib/chrome.js:231
    rmSync(dir, { recursive: true, force: true });
  • 执行外部命令固定命令
    lib/keychain.js:87
    const password = execFileSync('security', args, {
  • 执行外部命令固定命令
    lib/keychain.js:105
    const dump = execFileSync('security', ['dump-keychain'], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
  • 执行外部命令固定命令
    lib/keychain.js:122
    const dump = execFileSync('security', ['dump-keychain'], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
  • 读取本地文件
    lib/session.js:62
    if (existsSync(cache)) {
  • 读取本地文件
    lib/session.js:69
    if (existsSync(candidate))
  • 读取本地文件
    lib/session.js:87
    if (existsSync(candidate))
  • 读取本地文件
    lib/firefox.js:128
    const doc = JSON.parse(readFileSync(loginsPath, 'utf8'));
  • 读取本地文件
    lib/sqlite.js:130
    const buf = readFileSync(dbPath);
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-atomic-write@deepseek-ai/dsh-home-paths@deepseek-ai/dsh-llm@deepseek-ai/dsh-system-prompt@deepseek-ai/dsh-tools@deepseek-ai/dsh-typert-protocol@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 加密存储凭据
  • TOTP验证

DeepSeek Harness 的加密凭据保险库——AES-256-GCM + TOTP,模型工具 + 设置界面

v1.10.752026-10-12
· v1.10.75 — 威胁模型写清楚了,工具清单也补齐了 纯文档与仓库卫生的一批,没有行为变更。之所以单独发一版,是因为其中两条属于"使用者做判断前就该看到的信息"。 · 🔒 明确写出"它保护什么 / 不保护什么" 起因是社区的一条评论(准确且引用到位):保险库插件自己也是插件,与 harness 同进程运行,所以 readonly / ask / auto 约束的是模型的工具调用,不是插件之间的隔离。上游 @deepseek-ai/dsh-credentials-local 对自己的凭据存储也是同样的措辞("agent tool processes run as that same user…cannot isolate secrets from the agent"),并把 OS 钥匙串提供方列为待做事项。 两份 README 现在都有一段威胁模型: · 保护:静态的库文件(备份、同步盘、硬盘被拿走)、同机其他用户、秘密意外进入模型上下文、密码被写进配置文件; · 不保护:同进程的另一个插件(解密值就在那个进程内存里)、被攻陷的机器或账号、你自己敲进文件/截图/shell 历史里的主密码、模型拿到值之后做什么; · 减少暴露面:lockTimeoutSeconds / vault_lock 会清空内存密钥;钥匙串托管主密钥能拿掉配置与环境里的明文,但不会造出进程内边界 —— 因为以你身份运行的插件同样能向钥匙串索取同一个条目。 中文 README 此前只有四条要点、没有任何推理,这次补齐。 · 🧰 工具清单:先基础,再扩展 —— 而且终于完整了 · 工具 原本是一张 64 行平铺表,默认注册的 11 个工具淹没在 113 个里;更要紧的是它只写了 77 个,漏掉的 36 个里包括 vault_clipboard(基础工具!)、vault_export_env、vault_import_browser 和整对 pass 钱包工具。 现在是 基础(11)在前,后面四个按需开启的分组,每组标题都标明开哪一档才有: 基础 11 · 管理 53 · 导入导出 27 · 浏览器会话 8 · 备份与保险库文件 14 分组取自代码(CORE_TOOLS / IO_TOOLS / SESSION_TOOLS / FILE_TOOLS,其余归 management),与注册时 toolGroup() 用的是同一套规则。顺带修掉:表格里 vault_templates 那行的重复第三列、混进工具表的 kind: card(它不是工具)、条目模型漏列的 cookie / card 两种 kind、以及三处被 (e.g. 截断的句子。 · 🖼️ 图标生成器入库 src/client/site-icons.ts 的文件头一直写着"machine-generated",但生成器从来没进仓库 —— 加一个品牌只能手改 67 KB 的 SVG 路径。现在有 pnpm gen:icons(读 simple-icons 的 data/simple-icons.json + icons/.svg,只重写图标表、保留人工整理的 host→brand 映射,--check 不一致时退出 1)。 顺带说明为何没有改成依赖 simple-icons:它 15.4 MB / 3482 个文件,且不是 DSH 的平台模块,绑上去会把上一轮省下的 13 MB 又赔回去;而 harness 自带的 siteGlyph(平台模块,34 个外链站点品牌)与我们只交集 20 个,缺的 48 个恰好是凭据保险库最需要的。 · 🧪 测试 · 新增 tests/readme-tools.spec.ts:每个注册工具都必须在两份 README 的工具列里出现、不得提到不存在的工具、每组成员与标题声明的数量都必须与代码一致(反证过:删掉一行立即报出); · 两个浏览器套件改为读取插件自己的 section:harness 更新后页面多了一个 shell 自己的 ,原来 document.querySelector('section') 拿到的是它(token 全空、面板全 null),而且 svg circle:first-child 命中 shell 的 spinner —— 也就是说"评分环已上色"这条一直在测错误的元素(假绿)。input[type=file] 现在页面上有两个,未限定范围的 .first() 把附件传给了输入框区,所以附件断言失败的理由也是错的; · 修掉一个偶发:测试清理临时目录时与后台落盘竞态(ENOTEMPTY … rmdir,约三次一现),按 Node 的建议加 maxRetries。 安装:npm i dsh-va
v1.10.742026-09-30
· v1.10.74 — 正式版 Stable release 这一批从 1.10.73 起经过了 4 个 rc,主要包含五块内容。 · 🔑 命令行可以取动态验证码了 vault_totp 早就给模型用,但脚本拿不到:CLI 里没有 totp,而 get --field otpSecret 给的是长期密钥 —— 那恰恰是不该进日志的东西。 code=$(dsh-vault totp TwoFactor) # 只输出 6 位数字 dsh-vault totp TwoFactor --json # 附带 digits / period / secondsRemaining secondsRemaining 是给脚本用的:据此等一个新窗口,而不是硬闯只剩两秒的码。list --json / show 新增 hasTotp,可以先筛出哪些条目能用,不需要把任何密钥读进上下文。 · 🐞 五个真 bug(来自一轮独立审查,每条都先复现再修) | 症状 | 原因 | |---|---| | --password-stdin 遇空 stdin 永久挂死 | readline 对零字节只发 close 不发 line,下面那句"空密码"检查根本到不了 | | get --field(没给值)打印主密钥 | 无值被记成 true,读取时视为"没传",于是回退到主密钥 | | get --field tags / createdAt 报"没有这个字段" | 把"非字符串"和"不存在"混为一谈 | | get "" 打印密钥并退 0 | ''.includes 恒真,空名字命中所有条目 | | 库文件损坏报 TypeError | 原始解析错误直接冒出来,且不说哪个文件 | · 📦 安装体积 17.4 MB → 4.5 MB playwright-core(约 13 MB)只有浏览器登录会话用得到,而且是懒加载,现在声明为可选 peer 依赖。 顺带说明一个常见的误解:optionalDependencies 做不到这件事 —— npm 和 pnpm 默认照样安装它,只是"装失败不报错";真正跳过下载的是 peerDependencies + peerDependenciesMeta.optional。需要那组工具时装一句即可:cd ~/.dsh/profiles/web && npm i playwright-core(报错信息里会直接告诉你)。 · 📄 文档逐条对着代码核对过 写了一份 [env] 标记的说明,而那个标记在改成分组显示时就删了;两份 README 引用了一句 CLI 从不打印的报错原文;工具档位被写成 access.json(实际是 tools.json);"运行时依赖全部是 peerDependencies" 与真实的 playwright-core 冲突;测试数 420 在七处过时。 最要紧的一条:"绝不要把主密码明文写进配置"的警告块只有英文 README 有,中文版一个字都没有 —— 已补齐,并加上语言切换链接。 · 🛡️ 三道常驻守卫 · pnpm scan:dead —— 每个导出声明都必须在自己的声明行之外被引用过(11 个死导出就是这样找出来删掉的); · pnpm release:check —— 每个发到 npm 的版本都必须有 GitHub release,并挂在 postpublish 上(它自己还修过两个问题:异步发布导致的空转核验、以及 npm 对 bin 的规范化); · 密级扫描器的本机私有词表 —— 真实库里的条目名一旦进入任何被提交/发布的内容,测试立即失败。 安装:npm i dsh-vault(latest)。门禁:496 单测/集成、tsc 双端零错误、真机 CLI 41/41(其中动态码用独立实现的 RFC 6238 算法校验,并先自校验公开测试向量)、无 node_modules 环境 6/6,以及提示符回显、OAuth 崩溃、编辑器往返、主题、细节与对比度套件。 --- · English Stable release after four release candidates (1.10.74-rc.1 … rc.4). A TOTP code from the command line. vault_totp was available to the model, but a script could not get a code — and get --field otpSecret hands over the long-lived secret instead. code=$(dsh-vault totp ) prints s
v1.10.74-rc.42026-09-24预发布
· v1.10.74-rc.4 — 删掉 11 个死导出,并加一道守卫 上一轮审查指出仓库里有一批"没人引用的导出"。我没有直接照单执行,而是换一种方法独立重算了一遍——扫描全部 33 个源文件,统计每个导出声明在全仓库(含自身文件、测试、脚本、文档)的出现次数,只在声明处出现一次的才算死代码。结果与审查完全一致,11 个,不多不少: toAddCookie cacheSize keychainCacheSize clearKeychainCache TAG_LENGTH randomHex TAG_INTEGER TAG_NULL TAG_OID KdbxField __getToolProfileApplier · 🔍 一个被推翻的猜测 我原本怀疑 __getToolProfileApplier(注释写着"Set by tests")背后藏着整条陈旧的工具档位应用链。查下来没有:reapplyToolProfile 在 apply() 里设置、被 VaultGateway.setToolProfile 读取,而运行时切换档位在 tests/integration.spec.ts 里已经通过 gateway 测过。它只是一个从没被任何测试用过的钩子。 这恰好说明了死代码真正的害处: · toAddCookie(注释:"把存储的 CookieData 映射回 Playwright 的 addCookies 形状")读起来像已经实现了"带着已保存 cookie 打开浏览器" —— 但没有任何调用者,那个功能并不存在; · cacheSize / keychainCacheSize / clearKeychainCache 都自称"供诊断/测试使用",却没有任何 importer。 · 🛡️ 守卫:范围刻意收窄,才能一直开着 scripts/dead-exports.mjs 的规则是:每个导出声明必须在自己的声明行之外被引用过。 之所以不做得更"彻底",是因为另一个口径会误伤:仓库里另有 58 个"只在本文件内使用、但多写了 export"的符号(大多是模块自己的类型,这是正常惯例)。真要全清就是 58 处 diff,没人会维护,而且随时会被写回去。只有全仓库零引用的才报错,这条线才能长期生效。 规则本身也做了反证:tests/dead-exports.spec.ts 用植入符号验证它确实会报警、也确实不会误报被引用的符号;另外端到端验了一次——临时往 src/der.ts 塞一个 export function plantedOrphan(),脚本与测试双双失败,删掉后双双恢复。 安装:npm i dsh-vault@next。门禁:496 单测/集成、tsc 双端零错误、真机 CLI 41/41 及其余 UI 套件全绿。 --- · English Eleven exported symbols had no reference anywhere but their own declaration. Rather than take the audit's list on faith, I re-derived it with a different method — scanning all 33 source files and counting every exported declaration that occurs exactly once in the entire repository (including its own file, tests, scripts and docs). Same eleven, no more and no fewer: toAddCookie cacheSize keychainCacheSize clearKeychainCache TAG_LENGTH randomHex TAG_INTEGER TAG_NULL TAG_OID KdbxField __getToolProfileApplier A suspicion that did not survive checking. I expected __getToolProfileApplier — whose comment says "Set by tests" — to be hiding a stale tool-profile chain. It was not: reapplyToolProfile is assigned in apply(), read by VaultGateway.setToolProfile, and runtime profile switchi
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-vault — 为 DeepSeek Harness 打造的加密凭据保险库 dsh-vault 是 DeepSeek Harness 的安全插件,用来存放敏感凭据 —— 用户名、邮箱、手机号、密码、TOTP 密钥,以及开发者凭据,比如 SSH 连接、API key、secret、OAuth access/refresh token —— 落盘时全部加密,再通过 CRUD、搜索、密码生成、TOTP 工具,外加一个 Settings UI 页面,把这些凭据交给模型使用。 安全与实现 - 零外部加密依赖:全部基于 Node 内置的 node:crypto(AES-256-GCM 认证加密、scrypt 密钥派生、RFC 6238 TOTP)。 - 主密码:每条记录都用 256 位密钥加密,密钥由 scrypt(master password, salt) 派生,算法为 AES-256-GCM。密钥绝不落盘;解锁后缓存在进程内,重启时重新派生。 - 防篡改:GCM 认证标签,加上固定明文的校验信封 —— 主密码错误或密文被改动,会立刻失败,绝不返回乱码。 - 落盘无明文:磁盘上的文档不含明文凭据;每条记录使用独立的随机 nonce。 - 原子写入:复用 harness 的 writeFileAtomic 加文件锁;进程内写入串行化,跨进程写入者需要拿锁。
展开完整文档(README 剩余 1 节)
dsh-vault — 为 DeepSeek Harness 打造的加密凭据保险库

dsh-vault 是 DeepSeek Harness 的安全插件,用来存放敏感凭据 —— 用户名、邮箱、手机号、密码、TOTP 密钥,以及开发者凭据,比如 SSH 连接、API key、secret、OAuth access/refresh token —— 落盘时全部加密,再通过 CRUD、搜索、密码生成、TOTP 工具,外加一个 Settings UI 页面,把这些凭据交给模型使用。

安全与实现

- 零外部加密依赖:全部基于 Node 内置的 node:crypto(AES-256-GCM 认证加密、scrypt 密钥派生、RFC 6238 TOTP)。
- 主密码:每条记录都用 256 位密钥加密,密钥由 scrypt(master password, salt) 派生,算法为 AES-256-GCM。密钥绝不落盘;解锁后缓存在进程内,重启时重新派生。
- 防篡改:GCM 认证标签,加上固定明文的校验信封 —— 主密码错误或密文被改动,会立刻失败,绝不返回乱码。
- 落盘无明文:磁盘上的文档不含明文凭据;每条记录使用独立的随机 nonce。
- 原子写入:复用 harness 的 writeFileAtomic 加文件锁;进程内写入串行化,跨进程写入者需要拿锁。
- 搜索不泄露:vault_search 只返回摘要(id/title/kind/username/email/phone/host/port/url/tags)—— 绝不返回密码、密钥、token 或 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 key |
| secret | 通用密钥(client secret、shared secret 等) |
| accessToken / refreshToken / expiresAt | OAuth token 对和过期时间(epoch 毫秒) |
| otpSecret | TOTP 密钥(裸 Base32 或 otpauth:// URI) |
| url / notes / tags | 元数据 |

有两种条目自带结构:cookie 条目存的是抓取到的浏览器会话,card 条目存 cardNumber / cardExpiry(MM/YY)/ cardCvv / cardHolder —— 搜索摘要只暴露有效期和持卡人,绝不暴露卡号和 CVV,Bitwarden JSON 导出时映射为 card 条目(type 3,品牌自动推断)。
| fields | 任意键值对(如 {"region": "us-east-1"}),可搜索 |

工具

113 个工具,分五组。Basic 是默认安装注册的部分;其余通过工具配置档开启(Settings → Credentials → Permissions → Model tools)。

Basic(默认注册)—— 11

十一个工具。下面所有内容都是可选项。

| 工具 | 用途 |
| vault_list | 列出 vault 目录下可用的 vault(每个 vault 一个 .json 文件,不含 access/meta/export 文件) |
| vault_search | 搜索标题/分类/用户名/邮箱/电话/主机/端口/URL/备注/标签/自定义字段(含数值/布尔/嵌套值;空格分隔的词按 OR 匹配);可选 createdAfter/createdBefore epoch 毫秒过滤;返回不含密钥的摘要;limit 必须是 1–100 的整数 |
| vault_get | 按 id 读取完整条目(含所有密钥) |
| vault_add | 添加条目(字段任意组合;空字符串/空数组会被忽略) |
| vault_update | 按 id 更新字段(未提供的字段保留;空字符串清空字段;title 可改名;rotationDays: 0 清除轮换 = 永不轮换) |
| vault_delete | 软删除条目(移入回收站,磁盘上仍加密) |
| vault_fill | 找到匹配 host/URL/用户名/标题的条目,返回其凭据 |
| vault_clipboard | 取出某个条目的密钥字段供直接复制(username/password/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) |

Management(Standard 及以上)—— 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 | Watchtower 风格的逐条目风险分析(灵感来自 1Password/Bitwarden):标记过短/过弱的密码、键盘序列、内嵌年份、常见密码、重复使用、http:// 站点、缺少 2FA、过期 —— 给出 0–100 评分和 good/warn/poor 判定(不涉及密钥);条目行显示 ⚠ 徽标 |
| vault_breach_check | Watchtower 风格的泄露扫描,对照 Have I Been Pwned(k-匿名:只有 SHA-1 前缀离开本机),带离线常见密码兜底 |
| vault_integrity | 验证磁盘上的保险库文件能否正确解密,并与内存中的存储一致 |
| vault_merge | 把一条目合并进另一条目;keepSource: true 表示合并后保留源条目 |
| vault_attach / vault_attachments / vault_attachment / vault_detach | 给条目附加文件(私钥、证书、配置、恢复码)—— 以 base64 存进加密条目内部,静态加密;列出名称/大小、读取内容、移除 |
| vault_quick_add | 快速记录(标题 + 一条密钥),可选标签/备注 |
| vault_expiry | 设置/清除过期时间(expiresAt: 0 表示移除) |
| vault_stats | 概览计数,含 trashCount(不涉及密钥) |
| vault_verify | 校验单条目,或审计所有条目(all: true),检查各类字段是否完整、端口/过期时间是否合理(不涉及密钥) |
| vault_duplicates | 查找重复分组:mode = both(默认)/ title / content(不涉及密钥) |
| vault_report | 可打印的清单,带过期/轮换列和统计页脚(不涉及密钥) |
| vault_match_url | 查找匹配某 URL 的登录条目(Bitwarden/1Password 风格:精确主机、子域名、父域名、路径前缀;www./端口归一化),给出 0–100 评分 —— 绝不返回密码 |
| vault_env | 把带 env 标记的条目(标签含 env)渲染成 KEY=VALUE 行 —— 见 Environment export |
| vault_copy | 把条目(含密钥)复制到另一个指定保险库 |
| vault_templates | 内置模板 + 用户自定义模板(保存/列出/移除),KeePassXC 风格;内置模板现在包括 Wi-Fi、Server、Database、Identity、Bank account、Card(灵感来自 1Password) |
| vault_notes | 追加或替换条目的自由格式备注(vault_update 的便捷快捷方式) |
| vault_changes | 列出时间窗口内的保险库活动(默认 24h):新建、更新或软删除的条目,最新的在前 |
| vault_find | 模糊查找条目:匹配时忽略大小写、标点和空白,所以 gh 能命中 GitHub |
| vault_mask | 在任意文本中遮盖疑似凭据(API key、token、密码、私钥),方便安全地记录或引用 |
| vault_history | 显示本进程内保险库的近期活动,最新的在前:变更(add/update/delete/restore/purge/merge/rollback)加上读取(read、totp)、搜索和保险库切换。只记录操作、条目标题和时间戳——绝不记录密码、密钥、用户名或其他机密值 |
| vault_recent | 列出最近新建或更新的条目(最新的在前),以不含机密的摘要形式 |
| vault_pin | 固定(收藏)条目,让它在搜索和列表中排在最前 |
| vault_unpin | 取消固定条目(移除其收藏标记) |
| vault_tags | 列出所有条目用到的标签,以及每个标签下的条目数量 |
| vault_generate_username | 生成随机用户名或匿名邮箱建议 |
| vault_rotate_password | 生成新的强密码,存到条目上,并返回新值,供调用方交给目标服务 |
| vault_touch | 把条目标记为最近使用(更新其 updatedAt,不改内容) |
| vault_note_secret | 在生成的标题下存一条机密(不用命名的一次性备注) |
| vault_search_advanced | 用多个可选条件搜索:标题子串、用户名/邮箱子串、kind、tag、created-after/before(epoch millis)、仅收藏 |
| vault_count | 返回活跃条目数量(可选按 kind 和/或 tag 过滤) |
| vault_set_icon | 设置条目的 UI 图标(emoji)和/或强调色 |
| vault_describe | 用平实语言描述条目(kind、identity、host/url、tags、expiry),不泄露机密 |
| vault_last_modified | 列出最近修改的条目(updatedAt),最新的在前 |
| vault_has | 快速检查保险库中是否有匹配标题/用户名/主机的凭据(子串匹配,设置 exact 时按标题精确匹配) |
| vault_autofill_check | 检查保险库中是否有可用于某 URL/主机的凭据:返回最佳匹配条目(仅用户名/邮箱,绝不返回机密)或未找到的结论 |
| vault_undelete_all | 把所有软删除(已丢进回收站)的条目恢复到活跃集合 |
| vault_get_many | 按 id 读取多个条目,每个只返回请求的字段 |

导入与导出(Full,或 Custom → Import-export)— 27

在其他密码管理器之间迁入迁出、批量操作,以及 env/CSV/1Password/Bitwarden/KeePass/pass 互通。

| 工具 | 用途 |
| vault_import_csv | 从 CSV 文件批量导入凭据(自定义列会变成字段;overwrite: true 把字段合并进已有条目,而不是重复创建) |
| vault_bulk_delete | 按查询/类型/标签或显式 id 软删除条目;需要 confirm: true(默认只做演练);进入回收站的条目可以恢复 |
| vault_export / vault_import | 整个 vault 的可移植加密备份/迁移(单独的导出密码) |
| 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 或再导回这里;条目类别映射到 login / credit card / API credential / server |
| 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 keychain / Linux keyring 或 peanuts / Windows DPAPI),或从 macOS Keychain 导入(默认是互联网密码 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)导出登录条目,可导入 Chrome/Firefox/Edge |
| vault_migrate_keepass | 按 KeePass 2.x 导入 CSV 格式(Group,Title,Username,Password,URL,Notes)导出条目,用于迁移到 KeePass |
| 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 文件(或明文文件)变成一个条目,标题取自文件名;第一行是密码,其余行按 login:/email:/url: 元数据解析。返回 added/skipped |
| vault_export_env | 把带 env 标记的条目(tags 含 "env")写入指定路径的 .env 文件,格式为 KEY=VALUE 行(值做 shell 引号转义) |
| vault_export_csv | 把 vault 条目导出到 CSV 文件(格式与 vault_import_csv 接受的相同),可按 kind 过滤 |

浏览器会话(Full,或 Custom → Browser sessions)—— 8

捕获并重放 cookie 会话;需要可选的 playwright-core。

| 工具 | 用途 |
| vault_session_open | 在指定 URL 打开真实的有头浏览器窗口,让你手动登录(密码、2FA、验证码)—— 对于禁止嵌入的网站,这是捕获登录状态的通用做法 —— 需要可选的 playwright-core |
| vault_session_collect | 收集已打开浏览器会话的全部 cookie(含 HttpOnly),保存为 cookie 条目 |
| vault_session_import | 从粘贴的 JSON(devtools 导出格式)或原始 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 中) |
| vault_session_prune | 从已保存会话中移除过期 cookie(会话 cookie 保留);preview: true 只报告不写入 |

备份与 vault 文件(Full,或 Custom → Backups & files)—— 14

备份、切换 vault、重新加密、恢复码和系统搜索。

| 工具 | 用途 |
| vault_recovery_code / vault_verify_recovery / vault_recovery_status | 一次性 vault 恢复码(1Password/Bitwarden 风格):32 位字符的码只显示一次,只存它的 SHA-256 哈希;验证是否持有该码;检查是否已设置 |
| vault_rekey | 原地把 vault 升级到全新的 scrypt KDF 参数 |
| vault_switch | 为当前会话切换活动 vault |
| vault_backup / vault_backup_now | 带时间戳的加密备份,命名为 <vault>-backups-YYYY-MM-DD_HH-MM-SS-<hex>.json(所属 vault 和日期都可见);保留策略只留最新的 N 个(maxBackups,默认 10) |
| vault_restore_backup | 从备份恢复:mode: "merge"(默认)把备份条目复制进当前 vault,让它们出现在条目列表里;mode: "replace" 先用安全快照覆盖整个 vault |
| vault_vault_rename / vault_vault_delete | 重命名具名 vault(文件移动,活动会话跟随),或永久删除一个(默认 vault 受保护) |
| vault_search_system | 在 Chrome / Keychain 里搜索站点和用户名——绝不暴露密码 |
| vault_backup_status | 报告距上次写入备份文件过了多少天(1Password 风格的备份提醒;新式 <vault>-backups-<date>.json 和旧式 vault-backup-<epoch>.json 命名都能识别)。返回 daysSinceBackup 和一条建议 |
| vault_search_history | 搜索范围包含软删除(已丢进回收站)的条目,并标出各自的删除状态 |
| vault_backups | 列出可用的加密 vault-backup-.json 文件(最新的在前),附绝对路径和时间戳 |

典型工作流:存一条 SSH 凭据(kind: ssh + host/port/username/password 或 privateKey),让模型先 vault_search 找到主机,再 vault_get 取连接详情;保留 api-key/oauth 条目,用于 API 网关的访问令牌/刷新令牌轮换。

安装

dsh-vault 是一个 bundle(声明了 dsh.bundle 的包):装进配置档后,它的 cordis.patch.yml 会自动插入 vault 插件行(按包名 dsh-vault 引用;主密码通过 DSH_VAULT_PASSWORD 环境变量注入)。包里自带构建脚本——从 git 安装会自动编译 lib/。

下面四条安装路径都经过端到端验证(安装 → 识别 bundle 层 → 插件激活并注册全部 7 个 vault_ 工具 → 真实跑通 vault_add/vault_get 往返 → 卸载移除该层):

| 路径 | 命令 | 需要构建 | allowBuilds |
| npm | add dsh-vault | 否(预构建 lib/) | 否 |
| GitHub | add github:Ox0400/dsh-vault#v0.1.1 | 是(prepare) | 是(首次运行) |
| 本地路径 | add /abs/path/to/dsh-vault | 否(链接到已构建源码) | 否 |
| tarball | add ./dsh-vault-0.1.1.tgz | 否(预构建 lib/) | 否 |

方案 1:从 npm 安装(最省事)

dsh plugin --profile web add dsh-vault

npm 包自带预构建的 lib/ 产物——不用 allowBuilds,不用本地编译,装完就能用。启动前先设置主密码:

export DSH_VAULT_PASSWORD='your strong master password'

方案 2:从 GitHub 安装(锁定 tag 或 commit)

dsh plugin --profile web add github:Ox0400/dsh-vault#v0.1.1
git 安装拉取的是源码,不是构建产物,所以 prepare 脚本会在安装时构建 lib/。pnpm ≥10 默认禁止 git 依赖运行构建脚本。验证过的流程:

1. 运行 add 命令——它会因 allowBuilds 报错失败,并打印出需要放行的确切键(包含仓库 URL 的那一行,带解析后的 commit hash):

allowBuilds:
dsh-vault@https://codeload.github.com/Ox0400/dsh-vault/tar.gz/<sha>: true

2. 把这个确切的键追加到配置档的 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

3. 重新运行 add——pnpm 现在会运行 prepare 脚本,构建 lib/,然后安装。

固定 tag/commit,这样上游后续推送就无法悄悄改变安装时运行的内容。放行就是放行:允许该包的代码在安装时在你的机器上执行——只对你信任的来源授予。

方案 3:从本地路径安装

dsh plugin --profile web add /absolute/path/to/dsh-vault

pnpm 把 checkout 链接进配置档;只要 lib/ 存在,bundle 就会被识别(必要时先在 checkout 里运行 pnpm build)。

方案 4:从 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)。

命令行

插件把 vault 交给助手。内置的 dsh-vault 命令把同一个 vault 交给 shell 和脚本,所以技能的子进程能读取密钥,明文永远不会进入模型的上下文。

0. 首先:你到底能敲哪个命令?

包没安装之前,没有任何东西提供 dsh-vault 二进制——插件是 Cordis bundle,而 dsh 没有插件子命令注册表(启动器只解析自己的 flag;dsh plugin … 只是转发给 pnpm,所以 dsh web exec … 会把参数交给 web 应用,然后失败并报 error: too many arguments)。看例子之前先选好你那一行:

| 你的环境 | 命令 |
| 源码 checkout(git clone + pnpm build) | pnpm vault——或者 node lib/cli.js,或者 ./lib/cli.js |
| 插件安装进配置档 | 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 lines become shell variables
echo "$DEMO_API_KEY"

有两点值得知道:

- 只有打了 env 标签的条目才会导出。 一个都没打,你会看到 dsh-vault: nothing to export — no entry carries the "env" tag.;在 UI 里给条目打标签(条目的 tags 字段),或者用 vault_update { id, tags: ["env"] }。
- 名字由标题和字段推导而来(DASHSCOPE + apiKey → DASHSCOPE_API_KEY),除非条目设置了 envKeys —— 见 Environment export。

名字 —— 只有三个,其中两个故意都叫 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 # grouped: what env exports vs the rest
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 # the entry's primary secret, stdout only dsh-vault get my-entry --field apiKey # one named field dsh-vault get my-entry --mask # confirm it exists without printing it dsh-vault show my-entry # non-secret metadata as JSON dsh-vault totp my-entry # the current TOTP code (digits only) dsh-vault totp my-entry --json # + digits, period, secondsRemaining dsh-vault env # env-tagged entries as KEY=VALUE dsh-vault env --mask # human view, both sections as KEY=VALUE: exported items
NPM_REGISTRY_API_KEY=npm_***
unexported items
EXAMPLE_BILLING_API_KEY=***
dsh-vault export-env .env # …or write them to a 0600 file dsh-vault verify # check the master password, nothing on 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) 是错的。命令替换会按空白拆分,而且不会去掉命令行工具输出的引号,所以带空格的值会被拆成错误的词,每个值还会保留字面引号:

$ export $(dsh-vault env); echo "$WITH_SPACE"
['pass] # split at the space
bash: export: here': not a valid identifier
$ eval "$(dsh-vault env)"; echo "$WITH_SPACE"
[pass word here] # read as intended

在脚本里,优先用文件形式——完全不用 eval:

dsh-vault export-env .env && set -a && . ./.env && set +a

4. 主密码

先看 --password-stdin,再看 $DSH_VAULT_MASTER_PASSWORD(或 $DSH_VAULT_PASSWORD),最后是交互式提示,提示会隐藏你输入的内容(终端切到 raw 模式;用 tests/e2e/cli-prompt.mjs 验证)。主密码不会从插件的配置文件里读取,所以命令行工具从不依赖(也不会泄露)配置档补丁里存的东西:

DSH_VAULT_MASTER_PASSWORD=… pnpm vault list
printf '%s\n' "$PASSWORD" | pnpm vault list --password-stdin

5. 安装细节

源码检出:

git clone git@github.com:Ox0400/dsh-vault.git && cd dsh-vault
npm install && npm run build # builds lib/, including executable lib/cli.js

pnpm vault list # the repo's own entry point (package.json script)
node lib/cli.js list # same thing, spelled out
./lib/cli.js list # the file is executable too
npm link # …or put a real dsh-vault on PATH

要针对某个配置档开发插件,你还需要把检出目录链接进配置档,并加一层补丁把它插进去(这个仓库平时就是这么开发的);命令行工具这些都不需要——它直接读 vault 文件。手动链接的插件没有 .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 # into a profile (plugin + CLI together)
pnpm dsh plugin --profile web exec dsh-vault list # --profile is required

npm i -g dsh-vault && dsh-vault list # globally
npx dsh-vault list # or not at all

读取类命令(list、get、env、show、totp、verify、export-env)只依赖 Node,所以全局安装和 npx 两种形式都能用。只有写入 vault 才需要 harness 运行时,而插件始终带着它。

profile 这条路还有两个坑:它的 .bin shim 可能被之后的 pnpm install 清掉;另外 pnpm dsh plugin --profile web add dsh-vault 会根据包的 dsh.bundle 声明来同步 dsh.profile.bundles —— 如果你自己的补丁层也插入了这个插件,就会挂载两次,所以先把补丁那一行删掉。

Privacy of the breach check

vault_breach_check (and the security page) can check passwords against Have I
Been Pwned. Exactly one thing leaves this machine:

GET https://api.pwnedpasswords.com/range/<FIRST 5 HEX CHARS OF SHA-1(password)>
Add-Padding: true

The response lists every suffix in that bucket (hundreds of hashes) and the match
is computed locally — the full hash, the password, and the entry it belongs to never leave. A password already in the bundled common-password list is answered offline and produces no request at all; repeat lookups of the same prefix are cached in memory for an hour.

What k-anonymity does not hide, stated plainly:

- the five-character prefix (20 bits, one bucket in ~1M) goes to Cloudflare,
together with your IP;
- an observer who can guess candidate passwords can compute each candidate's
prefix and see whether you queried that bucket — that is the known limit of this protocol, not something the header fixes;
- Add-Padding only removes the response-size signal (without it, a bucket's
response length correlates with how common the password is).

To keep even the prefix on your machine, pass online: false — the check then uses only the offline list, which is far smaller, so treat it as a screening aid rather than a verdict.

Strength indicator

Each entry row shows a three-star indicator after its title — ☆☆☆ = 0 score, ★★★ = full marks — in six half-star steps:

strength indicator: the seven states

A half star is a real half-filled star, not a ½ next to it: the coloured copy
of the same three glyphs is clipped to a percentage, and ★/☆ share an advance
width so the clip lands exactly on a star boundary.

| score | filled stars | band |
| 0–8 | 0 | weak |
| 9–24 | ½ | weak |
| 25–41 | 1 | weak |
| 42–58 | 1½ | fair |
| 59–74 | 2 | fair |
| 75–91 | 2½ | strong |
| 92–100 | 3 | very strong |

Hovering it shows the exact score and verdict. The score is computed host-side
(the list never receives the secret) and covers the entry's *password or card
PIN; machine-generated API keys and private keys are deliberately not scored,
so every key does not sit at full marks.

Environment export

Entries tagged env can be materialised as KEY=VALUE lines (vault_env,
vault_export_env), and their names follow the field names every toolchain
expects:

| Entry | Exported key |
| title DASHSCOPE, apiKey | DASHSCOPE_API_KEY |
| title DASHSCOPE, prefix: APP_ | APP_DASHSCOPE_API_KEY |
| envKey: "TAVILY_TOKEN", accessToken | TAVILY_TOKEN |
| …the same entry's refreshToken / fields.scope | TAVILY_TOKEN_REFRESH_TOKEN / TAVILY_TOKEN_SCOPE |

- The field suffix is vendor-standard: apiKey → API_KEY, accessToken →
ACCESS_TOKEN, refreshToken → REFRESH_TOKEN, privateKey → PRIVATE_KEY,
password → PASSWORD, cardNumber → CARD_NUMBER.
- Set an entry's env-var names (optional field envKeys, also via
vault_add/vault_update, or comma-separated in the editor) when the derived
names are not what your scripts expect. They are positional and used
verbatim (no prefix, no title):

| envKeys | exported |
| ["DASHSCOPE_API_KEY"] | DASHSCOPE_API_KEY=apiKey |
| ["GOOGLE_ACCESS_TOKEN", "GOOGLE_REFRESH_TOKEN"] | ...=accessToken, ...=refreshToken |
| ["MY_KEY"] on an entry with 3 secrets | MY_KEY, MY_KEY_REFRESH_TOKEN, MY_KEY_SCOPE |

Each name must be a POSIX identifier ([A-Za-z_][A-Za-z0-9_]), at most 8,
and no duplicates. envKey (singular string) is still accepted as a
one-element shorthand and is folded into envKeys on write.
- Custom fields are exported too, as _<FIELD>.

Upgrading: keys changed from DASHSCOPE_APIKEY to DASHSCOPE_API_KEY in
1.10.64 to match vendor conventions. Set envKey on an entry to pin an exact
name if a script depends on the old spelling.

Tool profiles

The plugin ships 110+ model tools, but registers only 11 core tools by default (vault_list/search/get/add/update/delete/fill/clipboard/totp/generate_password/strength) to keep the model's tool catalog small and cheap.

Switch profiles in Settings → Credentials → Permissions → Model tools — it applies instantly (no restart) and is persisted per vault:

| Profile | Registers |
| Basic (default) | core only — everyday use |
| Standard | + management: favourites, tags, icons, expiry/rotation, health, duplicates/merge, attachments, templates, env masks |
| Full | everything, incl. bulk import/export, browser sessions, backups and vault-file operations |
| Custom… | core + any of: Management / Import-export / Browser sessions / Backups & files |

tools: basic|standard|full|custom can also be set in the plugin config; the UI choice wins and is stored in <vault dir>/tools.json (the process-wide tool catalog is not per vault).

Configuration

[!WARNING] Security: never put the master password in plaintext
A profile patch (cordis.patch.yml / cordis.yml) is a config file that is
easy to commit to git, screenshot, or capture in logs — so a plaintext masterPassword: row leaks the key to your whole encrypted vault. Always use masterPasswordEnv and export the real password into the process/shell environment instead (e.g. export DSH_VAULT_PASSWORD='…'). See the community-plugin-audit handbook for the evidence-first checklist for credential-handling plugins.

✗ DON'T — plaintext master password in a config file
- id: vault
name: dsh-vault
config:
masterPassword: 'my-secret'

✓ DO — reference an environment variable; keep the file itself 0600
- id: vault
name: dsh-vault
config:
masterPasswordEnv: DSH_VAULT_PASSWORD

| Option | Description |
| masterPassword | The master password inline (appears in cordis.yml; not recommended) |
| masterPasswordEnv | Environment variable name holding the master password (recommended) |
| path | Vault file path; defaults to $DSH_HOME/vault/default.json |
| name | Vault name for the default path (e.g. name: work → $DSH_HOME/vault/work.json) |
| accessMode | Access policy for the model tools. Three states: readonly (mutations rejected on tools + UI), ask (default — reads free, every add/update/delete goes through the harness approval channel so the user confirms each write), or auto (automatic read-write, no per-call prompt). The Settings UI offers this exact three-way choice and persists it to <vault dir>/access.json. |
| autoCapture | false (default). When true, the system prompt instructs the model to detect credentials shared in conversation and — per user preference — offer to save them with vault_add. |
| lockTimeoutSeconds | Auto-lock: after this many seconds of inactivity the vault re-locks (key wiped) and every read/write requires vault_unlock. 0/absent disables. |
| exportPasswordEnv | Environment variable holding the export/import password for vault_export/vault_import (never pass it as a model argument). |
| backupRetention | How many encrypted backups to keep (default 10); vault_backup prunes older copies. |

Example:

- id: vault
name: dsh-vault
config:
masterPasswordEnv: DSH_VAULT_PASSWORD
accessMode: ask
autoCapture: true

With autoCapture: true, when you share a credential in chat (e.g. "my npm token is npm_…"), the assistant offers to store it; on your consent it calls vault_add immediately. With autoCapture off, credentials are only saved when you explicitly ask. The Settings UI shows the current mode (read-only / ask-before-write / automatic read-write) with a dropdown to switch it, an auto-capture toggle (detect credentials shared in chat → offer to save), a kind filter, a health & rotation summary, a trash view, and masked secret fields with a Show/Hide toggle.

The vault is created automatically on first tool use; every launch re-unlocks with the master password. Forgetting the master password = permanent data loss (no backdoor — by design).

Development

Clone and develop locally:

git clone git@github.com:Ox0400/dsh-vault.git
cd dsh-vault
pnpm install # installs devDependencies (typescript/tsdown/vitest, …)
pnpm build # builds host lib/.js and the browser bundle lib/client.js
pnpm test # runs the 506 vitest tests

Tests need harness peer packages such as dsh-llm/dsh-system-prompt; inside the harness monorepo these resolve via workspace links.

The brand marks in the credential list are 68 SVG paths inlined into the browser bundle — no runtime dependency and no network call. They are generated, and the generator is committed:

npm i --no-save simple-icons@16 # 15 MB, dev-only: never a dependency of this package
pnpm gen:icons # refreshes src/client/site-icons.ts
pnpm gen:icons -- --check # exits 1 when the table is stale

Only the glyph table is rewritten; the host→brand map (gist.github.com, console.cloud.google.com) is curated knowledge and is preserved. Brands that simple-icons has since removed (amazonaws, slack, linkedin, …) are kept and reported rather than dropped.

Common commands:

pnpm test # unit + integration tests (vitest, 506)
pnpm typecheck # tsc -p tsconfig.json --noEmit
pnpm build # = build:host (tsc) + build:client (tsdown)
npm pack # optional: tarball for dsh plugin add ./dsh-vault-0.1.1.tgz

All 506 tests pass (crypto / TOTP / password generation / store CRUD / gateway / integration).

Browser checks run against a real dsh web instead of vitest, and every one of
them refuses to operate on the default vault:

node tests/e2e/theme-check.mjs # dual-theme tokens, ring track, attach row
node tests/e2e/polish-check.mjs # empty-state copy, relative-age hover hints
node tests/e2e/contrast-audit.mjs # WCAG sweep over all eight tabs, both themes

See tests/e2e/README.md for the safety rules and for the two traps that make a
contrast audit lie (serialised color(srgb …) values, and opacity counting as part of the colour).

Keeping credentials out of public content

Everything in this repository is public — the npm tarball, the GitHub releases, the issue and discussion posts. Before publishing, pnpm scan:secrets checks the working tree, the tracked files and the commit messages for anything that looks like a credential, and pnpm scan:public additionally checks every release and comment already posted. The local half also runs in pnpm test, so a committed secret fails CI rather than shipping.

Theming

The UI reads the host's design tokens instead of hard-coded colours. A local
semantic layer maps onto the real --dsw-alias- namespace, with literal
fallbacks so the page still renders outside 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));

Because the aliases flip with body[data-ds-dark-theme], light and dark follow
the host automatically — there is no second stylesheet and no theme prop.

Two rules the layer exists to enforce:

- Never use state--primary as body text. Those tokens are tuned for fills
and icons: on white they measure 2.28:1 (success) and 2.15:1 (warn), far below
WCAG AA. The --v--text variants mix them towards the theme's own text colour,
so one declaration darkens on light and lightens on dark (light 5.8/5.6/7.5:1,
dark 12+/11+/6.5:1).
- Never dim text with opacity. opacity: .8 turns a 5.8:1 colour into
3.9:1. Express "secondary" with a token (--v-text-2), not with transparency.

tests/e2e/contrast-audit.mjs verifies both across every tab in both themes.

Packaging & Publishing

This package is a standard npm bundle:

- dsh.bundle.patch → cordis.patch.yml (the layer applied automatically when a profile lists this bundle)
- dsh.client → browser-side declaration (exports["./client"] points at lib/client.js)
- prepare script → self-contained build on git install (tsc host + tsdown client)
- No runtime dependencies: what the host provides arrives through peerDependencies, so there are
no duplicate instances. playwright-core — needed only by browser login sessions, and lazily required — is an optional peer, so a normal install does not download it (~13 MB). Install it
into your profile when you want those tools: cd ~/.dsh/profiles/web && npm i playwright-core

Distribution options:

npm pack # tarball → dsh plugin add ./dsh-vault-0.1.1.tgz
npm publish --access public # registry → dsh plugin add dsh-vault

Security Boundaries & Known Limitations

What this protects

- A vault file that leaves the machine — a backup, a synced folder, a stolen disk. Entries are AES-256-GCM encrypted under a key derived from the master password with scrypt (N=32768, r=8, p=1, persisted in the document and raisable later; old documents stay decryptable).
- Other users on the same machine — the vault and every file the CLI writes are 0600.
- Secrets reaching the model's context by accident — vault_search / vault_update never return passwords, keys or tokens, and the CLI exists so a script can read a secret without it passing through a conversation.
- A password embedded in config, when you use masterPasswordEnv instead of masterPassword (see the warning above).

What this does not protect

- Another plugin in the same host process. dsh-vault is itself a plugin: it runs in-process with the harness and decrypted entries live in that process's memory. readonly / ask / auto constrain the model's tool calls — they do not isolate plugins from one another. The harness states the same limit for its own credential store: agent tool processes run as your OS user, so it "cannot isolate secrets from the agent" (@deepseek-ai/dsh-credentials-local). Only running plugins out-of-process would change this, and no plugin can enforce that by itself.
- A compromised machine or user account — a keylogger, a debugger attached to the harness process, or anything else running as you.
- A master password you typed into a file, a screenshot or a shell history. Hence masterPasswordEnv.
- What the model does with a value you let it read. vault_get returns it into that tool call's result, which is model context; vault_search/vault_update never expose one.

Reducing the exposure

lockTimeoutSeconds auto-locks an idle vault and vault_lock locks it on demand; both wipe the in-memory key. A master key held by the OS keychain (macOS Keychain / Windows Credential Manager / libsecret) would take the plaintext password out of config and environment files — the exposure that survives a disk copy — but it would not create an in-process boundary, because a plugin running as you can ask the keychain for the same item. The DSH project lists an OS-keychain credentials provider as deferred work.

Known limitation: this plugin targets single-machine / personal deployments; team-shared vaults are out of scope.

Listed in

- awesome-dsh-plugin — official curated directory (Security & Safety section)
- awesome-deepseek-harness (Dominic789654) — community directory (Security section)

Submissions pending maintainer review: Anil-matcha/awesome-dsh-plugin #127 · 0xsline/awesome-deepseek-harness #563 · dsh-handbook #65
预览分享卡片长什么样 ↓Ox0400/dsh-vault
分享 · DeepSeek Harness Hub
Ox0400/dsh-vault

dsh-vault — 为 DeepSeek Harness 打造的加密凭据保险库

这是你的仓库?认领条目

回到顶部