# dsh-secret-paste > 一个 DeepSeek Harness 插件:拦截粘贴进输入框的密钥,存入官方凭据存储,并只把 > `[secret:REF]` 占位符发给模型。真实值永远不会出现在消息、会话历史或任何转录里。
[![npm](https://img.shields.io/npm/v/dsh-secret-paste?style=flat-square&color=5B4CF0)](https://www.npmjs.com/package/dsh-secret-paste) [![MIT](https://img.shields.io/badge/license-MIT-0B7285?style=flat-square)](LICENSE) [![DSH](https://img.shields.io/badge/DSH-Web-5B4CF0?style=flat-square)](cordis.patch.yml) 🌐 **中文** | English:[README.md](README.md)
## 为什么需要它 你把 API Key 或 token 粘进对话框。没有保护的话,它会直接进模型、进会话日志——这正是 密钥最不该出现的地方。这个插件拦截粘贴,把值存入 DSH 官方凭据存储 (`~/.dsh/.credentials.yaml`,权限 0600),并在草稿里替换成 `[secret:REF]`。 模型只会看到占位符,需要真实值时再通过专门工具按需读取。 ## 工作方式 | 步骤 | 行为 | |---|---| | 检测 | 对粘贴文本运行 `@sanity-labs/secret-scan`(源自 gitleaks / TruffleHog 的 1100+ 条规则)。 | | 存储 | 通过官方 `credentials.set` Web API 写入值——不新增任何服务端路由。 | | 替换 | 命中的片段在草稿中变成 `[secret:PASTE_N]`。 | | 取用 | 模型需要真实值时调用 `secret_resolve(ref)` 工具。 | 明文值只存在于两个地方:(1) 当前浏览器标签页的内存;(2) `~/.dsh/.credentials.yaml` (权限 0600)。其它所有表面——发出的消息、会话历史、转录——只能看到 `[secret:REF]`。 ## 安装 ### 前置条件 - 一个 DeepSeek Harness **Web** profile(示例用 `web`)。 - `PATH` 里有 `dsh` CLI 和 `pnpm`(`dsh plugin` 是 pnpm 的薄封装)。 ### 从 npm registry 安装(推荐) ```bash dsh plugin --profile web add dsh-secret-paste ``` 这条命令做的事: 1. 在 profile 目录里执行 `pnpm add dsh-secret-paste`。 2. 安装唯一的运行时依赖(`@sanity-labs/secret-scan`)。 3. 因为包声明了 `dsh.bundle.patch`,会被自动登记进 `dsh.profile.bundles` 成为一层。 安装过程**不会运行任何构建**——浏览器 bundle 是预构建后随包发布的。 ### 重启 服务端(`secret_resolve` 工具)在启动时加载,所以装完后要**重启 `dsh web`**, 然后**强制刷新浏览器标签页**(`Cmd+Shift+R` / `Ctrl+Shift+R`)以加载客户端 bundle: ```bash # 重启你管理的 dsh web 进程(Ctrl-C 后重新启动,或用你的进程管理器) ``` ### 验证 - **设置 → 插件**里能看到 `dsh-secret-paste`。 - 往输入框粘一个 GitHub token(`ghp_...`):它会立刻变成 `[secret:PASTE_N]` 并出现提示 chip。 ### 由 AI / 自动化安装 AI agent 或 CI 安装时步骤完全相同: 1. `dsh plugin --profile web add dsh-secret-paste` 2. 重启 `dsh web`(`secret_resolve` 工具注册所必需)。 3. 强制刷新浏览器标签页。 4. 验证:粘贴 `ghp_...` 应得到 `[secret:PASTE_N]`;或检查页面 `window.__DSH_BOOT__` 里包含 `dsh-secret-paste` 条目。 ## 使用 ### 自动检测并隐藏 粘贴包含高置信密钥(`ghp_...`、`sk-proj-...`、JWT 等)的文本,会立即被存储并替换成 `[secret:PASTE_N]`,同时出现一个 chip: > 🔒 隐藏 · <规则> · PASTE_N · 撤销 - **悬停** chip 会在浮层中显示真实值——不会引起布局跳动。 - 还在编辑时,点 **撤销** 可恢复明文。 - **发送后** chip 保留但撤销按钮消失;答案返回后 chip 自动移除。 ### 中等置信 `confidence === 'medium'` 的命中(例如 `Bearer `)原样留在草稿里, 弹出一个「疑似密钥」chip 让你确认(隐藏)或忽略。 ### 手动标记 检测库不认识的格式(`ark-...`、部分 `sk-...`)**绝不猜测**。选中文本后使用 「标记为密钥」动作,再点「隐藏并存储」。 ### 嵌套占位符 已经包含 `[secret:REF]` 的选区可以再包一层;`secret_resolve` 会把这种链 **递归解析**到明文(遇到环或缺失的内层引用返回 `found: false`)。 ## 模型侧:`secret_resolve` 模型需要真实值时调用 `secret_resolve(ref)` 工具: - 返回 `{ found, value, source }`。 - 值里的嵌套占位符会被递归解析成明文。 - 该值属**敏感内容**:工具描述明确要求模型不得在回复、文件、命令或工具参数里 回显或复述它。 ## 安全模型 - **值不泄露**:消息、历史、转录里只有 `[secret:REF]`;值只存在于标签页内存和 0600 权限的凭据文件。 - **检测保守**:`high` 自动隐藏,`medium` 等待确认,未知格式绝不猜测 (手动标记兜底)。 - **引用名防冲突**:ref 已被占用时自动顺延到下一个空位,绝不覆盖;同一值 每会话只存一次并复用引用名。 - **无读取端点**:凭据存储按设计「只读有无、不枚举」;刷新后内存值清空, chip 不持久化。 - **使用时刻可见(v1)**:只有模型主动询问时,`secret_resolve` 才把值带入 模型上下文。 ## 开发 ```bash node scripts/build.mjs # 重新构建 lib/client.js(无需外部打包器) npm test # node --test tests/*.test.mjs ``` ## 目录结构 ``` dsh-secret-paste/ ├── package.json # dsh.bundle.patch + dsh.client 声明 ├── cordis.patch.yml # 挂载服务端行 ├── lib/ │ ├── index.js # 服务端:secret_resolve 工具(递归解析) │ └── client.js # 预构建的浏览器 bundle ├── src/ │ ├── resolve.js # 嵌套占位符解析器(服务端共用) │ ├── scan.js # 检测辅助(与测试共用) │ └── client/index.js # 粘贴拦截、chip、credentials.set ├── vendor/secret-scan.cjs # vendored @sanity-labs/secret-scan@1.1.0 (MIT) ├── scripts/build.mjs # 组装 lib/client.js └── tests/ # node:test 单元测试 ``` ## 许可证 MIT。`vendor/secret-scan.cjs` 是 [`@sanity-labs/secret-scan`](https://www.npmjs.com/package/@sanity-labs/secret-scan) v1.1.0(MIT)的编译产物,其规则源自 [gitleaks](https://github.com/gitleaks/gitleaks)(MIT)与 TruffleHog 检测器; 其许可证保留在 `vendor/secret-scan.LICENSE`。