🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI
广告☁️ 云服务器特惠阿里云首购 8 折 · 腾讯云合作特惠
DeepSeek Harness Hub
← 返回列表

jwilson411/dsh-secret-scrub

DeepSeek Harnessspec-screened扫描:中风险在 GitHub 查看 ↗
未验证

一个 DeepSeek Harness…

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/8/31 · 已提供中文文档

DeepSeek Harness 插件:在模型看到之前,对已知的密钥形态进行脱敏;JSONL 事件记录绝不存储原始值

综合分
28
GitHub 分
28
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add jwilson411/dsh-secret-scrub
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
信任档位:仅索引本站尚未对其实装验证,仅收录元数据
是什么
dsh 原生插件 · market
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
还在维护吗
活跃:最近一次提交在 25 天前

档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →

数据截至 2026/9/20(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-tools@deepseek-ai/cordis
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

由 DeepSeek 最新模型翻译生成
dsh-secret-scrub

一个 DeepSeek Harness 函数插件,在工具执行的进出路径上对已知的密钥形态进行脱敏,并为每一条记录写入一行仅追加的 JSONL 事件日志——不包含原文。

持有令牌的工具参数会在工具运行之前被脱敏,持有令牌的工具结果会在模型看到它之前被脱敏。每个不同的密钥都会变成一个稳定的占位符:

$SECRET_GITHUB_PAT_3f9a1c04

相同的字节在本次会话剩余时间内会产生相同的占位符,因此模型仍然可以推理出“参数中的令牌在结果中返回了”——而字节本身已经消失,无论是从对话记录中还是从日志中。

它不是什么

这是模式脱敏。它不是密钥管理器。

- 不是密钥管理器或保险库。 它不存储任何东西,不签发任何东西,不轮换任何东西,也不验证任何东西。如果某个密钥已经泄露到提示词中,在这里对它进行脱敏并不能消除泄露:请轮换它。
- 不是原文日志。 事件行记录的是模式名称和占位符。匹配到的字节在内存中被哈希一次,然后丢弃。磁盘上没有反向映射,也没有任何 API 能返回它——即使是对报告事件的工具也没有。
- 不是检测模型。 没有分类器,没有熵启发式,也没有评分。它只匹配下表中的形态,仅此而已。形态不在表中的凭据会直接通过。
- 不是实时扫描 API。 不会针对 GitHub 密钥扫描、AWS STS 或任何其他签发方进行验证。无论它是否曾经是有效的凭据,只要匹配就会被脱敏,而一次脱敏并不构成泄露的证据。
- 不是 DLP。 它不监视文件、出口流量或邮件流。它只位于一个接缝上——工具参数和工具结果——除此之外没有任何东西经过它。

脱敏是损害限制,而不是控制措施。控制措施是不把凭据放在模型能读到的地方。

它所在的位置,以及它不会凭空发明的接缝

dsh-telemetry-redactor 清理的是导出副本:你发送到别处的对话记录。本插件清理的是实时副本,在模型或工具看到它之前。两者互补,且互不替代。

固定的候选发布版本 0.1.1-rc.2 没有暴露工具执行中间件接缝,因此本包不会假装拥有一个。它只注册一个面向模型的工具 secret_scrub_report,并导出 wrapExecute,供拥有该调用权的主机应用。没有进行任何猴子补丁,也没有触及任何私有 API。

安装

dsh plugin --profile default add github:jwilson411/dsh-secret-scrub

dsh plugin 会在 $DSH_HOME/profiles/default 内转发给 pnpm,然后协调该 profile:由于本包的清单声明了 dsh.bundle.patch,它会被追加到 profile 清单有序的 dsh.profile.bundles 列表中,其 cordis.patch.yml 成为一个层。用同样的方式移除它,只需将 add 换成 remove。
固定版本的 DSH 候选发布版

本包是针对固定版本的候选发布版 0.1.1-rc.2 编写并测试的——@deepseek-ai/dsh-tools@0.1.1-rc.2 被精确固定在 devDependencies 中,因此测试针对一个已知的 API 运行,而对等依赖范围是 ^0.1.1-rc.2,与 harness 自身工具包声明它的方式一致。

它注册了什么

| | |
|---|---|
| Cordis 插件 id | secret-scrub(cordis.patch.yml 中的行 id) |
| 注入 | tools——一个硬依赖;插件会等待而不是降级 |
| 工具 | secret_scrub_report |
| 参数 | n(整数,可选;默认 20,上限 200) |

模式

每个模式都有一个稳定的名称,它会逐字出现在每个占位符和每条事件行中;一个版本,每当其正则表达式改变形态时递增;以及它被添加的日期。名称是公开 API——重命名一个名称会使所有已存储的事件成为孤儿。正则表达式则不是。

| 名称 | 版本 | 起始于 | 匹配 |
|---|---|---|---|
| GITHUB_PAT | 1 | 2026-08-31 | GitHub 经典个人访问令牌,ghp_ + 36 |
| GITHUB_FINE_GRAINED_PAT | 1 | 2026-08-31 | GitHub 细粒度个人访问令牌,github_pat_ + 40 或更多 |
| AWS_ACCESS_KEY_ID | 1 | 2026-08-31 | AWS 长期访问密钥 id,AKIA + 16 |
| SLACK_BOT_TOKEN | 1 | 2026-08-31 | Slack 机器人用户 OAuth 令牌,xoxb- + 两个数字段 + 一个秘密段 |
| PEM_PRIVATE_KEY | 1 | 2026-08-31 | 一个 PEM 私钥块,或仅其头部 |
| SK_LIVE_TOKEN | 1 | 2026-08-31 | 通用的 sk- 前缀实时 API 令牌 |

正则表达式,逐字来自 src/scrub.js:

GITHUB_PAT               /\bghp_[A-Za-z0-9]{36}\b/g
GITHUB_FINE_GRAINED_PAT  /\bgithub_pat_[A-Za-z0-9_]{40,}\b/g
AWS_ACCESS_KEY_ID        /\bAKIA[0-9A-Z]{16}\b/g
SLACK_BOT_TOKEN          /\bxoxb-[0-9]{8,}-[0-9]{8,}-[A-Za-z0-9]{16,}\b/g
PEM_PRIVATE_KEY          /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g
SK_LIVE_TOKEN            /\bsk-[A-Za-z0-9_-]{20,}\b/g

PEM_PRIVATE_KEY 有两个分支,最长者优先:一个完整的装甲块会折叠为一个占位符,因此其中的密钥材料会随头部一起消失,而不会被留在被脱敏的头部之下。第二个分支捕获的是在到达之前主体已被截断的头部。

占位符

$SECRET__

是表中的名称。 是 sha256(salt || 0x00 || secret) 截断为八个十六进制字符,其中 salt 按每次脱敏会话生成。

- 相同字节,相同会话 → 相同占位符。 这正是使脱敏文本仍然合理的原因。
- 相同字节,不同会话 → 不同占位符。 salt 每次会话都是新的,因此一个会话的日志无法与另一个会话的日志进行关联,占位符也不是某个秘密的持久标识符。
- 摘要不是承诺。 它故意很短,以保持脱敏
文本可读。不要在其上构建任何假定抗碰撞性的东西,并注意,对已知形态密钥的未加盐短摘要,任何持有候选列表的人都可以暴力破解——这正是它要加盐的原因。

库 API

该工具是一个侧通道。函数才是产品:

import { createScrubState, wrapExecute, scrub } from 'dsh-secret-scrub'

// One state per session. It holds the salt and the placeholder memo, and no
// preimages beyond the life of the process.
const state = createScrubState({ session: sessionId })

// Wrap a tool's execute: arguments in, result out, incidents appended.
const execute = wrapExecute(tool.execute, state, {
tool: tool.name,
incidentLog: '/var/log/dsh/secret-scrub.jsonl',
})

// Or scrub one value directly.
const { value, findings } = scrub(payload, state)
// findings: [{ pattern: 'AWS_ACCESS_KEY_ID', placeholder: '$SECRET_…', count: 2 }]

| export | |
|---|---|
| createScrubState({ session, salt }) | 打开一个会话;在每次调用中传递它 |
| scrub(value, state) | 递归遍历;返回 { value, findings } |
| wrapExecute(execute, state, meta) | 清洗传入参数、传出结果,并追加事件 |
| scrubToolArgs(args, state, meta) | 单向处理,返回 { args, incidents } |
| scrubToolResult(result, state, meta) | 单向处理,返回 { result, incidents } |
| appendIncidents(path, incidents) | 仅追加的 JSONL 写入 |
| readIncidents(path, limit) | 日志尾部,已脱敏 |
| countByPattern(incidents) | { pattern, count } 行 |
| PATTERNS | 上表,以数据形式呈现 |

scrub 会遍历字符串、数组和普通对象,包括对象键——以令牌为键的载荷,与包含该令牌的载荷一样会泄露。数组和对象会被重建而不是原地修改,循环会被处理,任何不是字符串、数组或普通对象的内容(Date、Map、类实例)都会原样传递,而不是被展平。

事件

仅追加的 JSONL,每次调用每个方向每个不同密钥一行:

{"ts":"2026-08-31T12:00:00.000Z","session":"s-1","tool":"read_file","pattern":"AWS_ACCESS_KEY_ID","placeholder":"$SECRET_AWS_ACCESS_KEY_ID_1a2b3c4d"}

五个字段,且仅这五个:ts、session、tool、pattern、placeholder。没有原像、没有前缀、没有长度、没有字符数——一个会泄露它所见过每个密钥一点信息的脱敏日志,比没有日志更糟。该文件以 0600 权限创建。

读取器在同样方向上也是防御性的:readIncidents 会将每一行缩减为恰好这五个字段,并丢弃任何 placeholder 不是占位符形态的行,因此由其他东西追加到日志中的内容,无法通过报告工具偷偷带出原像。

配置

| key | type | default | |
|---|---|---|---|
| incidentLog | string | .dsh-secret-scrub.jsonl | 仅追加 JSONL 日志的路径,相对于进程工作目录 |
解析顺序是先补丁配置,再环境变量,最后默认值:补丁行是部署的明确意图,因此不会被环境变量静默覆盖。环境变量回退项是 DSH_SECRET_SCRUB_LOG,仅在补丁行完全省略该键时使用。

- id: secret-scrub
config:
incidentLog: /var/log/dsh/secret-scrub.jsonl

报告工具

secret_scrub_report 回答一个问题:本次会话脱敏了什么?*

{ n: 20 } // 可选;默认 20,硬上限 200——更大的请求会被截断,而非拒绝

它返回 { incidents, counts, total, log, plugin }:最近的事件按从旧到新排列,该窗口内按模式统计的计数,以及被读取的日志路径。仅包含占位符和模式名称。没有任何工具、标志或导出会返回原像,因为原像根本不会被保留。

目录结构

package.json          清单 + dsh.bundle.patch——使其成为 bundle 的关键
cordis.patch.yml      bundle 的补丁层:一次插入,一行插件
src/scrub.js          库:模式、占位符、遍历、日志
src/index.js          插件:name、inject、apply(ctx, config)、工具
test/                 离线测试:遍历、包装器、日志、卫生扫描
package-lock.json     锁定的依赖树,CI 中由 npm ci 安装

测试

npm install
npm test

完全离线。无网络、无密钥扫描 API、无 profile 启动:apply 接收一个记录注册信息的桩上下文,工具通过注册表调用的同一个 execute 驱动,结果针对真实的 @deepseek-ai/dsh-tools 进行验证,每个事件日志都写入操作系统临时目录下的全新目录,并在之后删除。

测试夹具是假的,且在运行时组装

每个假密钥都是在测试内部由片段拼接而成,因此没有任何提交的文件会包含任何形式的连续令牌——GitHub 推送保护没有可拒绝的内容,其他扫描器也一样:

| 模式 | 片段 |
|---|---|
| GITHUB_PAT | ghp_ + fake + 32 × x |
| GITHUB_FINE_GRAINED_PAT | github + _pat_ + fake + 18 × 0 + _ + 59 × y |
| AWS_ACCESS_KEY_ID | AKIA + FAKESECRET + 111111 |
| SLACK_BOT_TOKEN | xoxb + - + 1111111111 + - + 2222222222 + - + fake + 20 × z |
| PEM_PRIVATE_KEY | 头部单词以空格连接,一个 TEST KEY 主体,以及尾部单词 |
| SK_LIVE_TOKEN | sk- + live + - + fake + 24 × K |

这些从来都不是真实凭据。它们只有正确的形状,仅此而已——这正是模式脱敏器的全部意义,也是它的全部局限。

test/hygiene.test.js 断言这一点,而非仅仅声称:它用脱敏器自己的模式表扫描自己的代码树,如果任何提交的文件包含完整令牌则失败;它扫描机器名、挂载路径和凭据变量名;检查 src/ 中不包含 socket、fetch 或子进程
构造时不导入任何东西,只导入固定版本的 tools 包及其使用的两个 Node 内置模块,并检查 CI 不需要任何凭据。

CI(.github/workflows/ci.yml)在 Node 22 和 24 上,从已提交的 lockfile 运行 npm ci 和 npm test,仅针对公共 registry。它不持有任何密钥,除该安装之外不访问任何网络。

范围之外

- 密钥的存储、签发或轮换。 上文已述:这不是一个 vault,而一次脱敏也不是一次修复。
- 恢复被脱敏的值。 在此处刻意不可能。如果你需要原始值,你需要在它到达模型之前就需要它。
- 熵或 ML 检测。 没有评分,没有分类器,没有“这大概是个密钥”。只做形状匹配,因此误报和漏报都是可预测且可审计的。
- 针对签发方验证匹配项。 永不联网。
- 脱敏除工具参数和结果之外的任何内容。 提示词、系统文本、附件和导出是其他接缝,由其他负责人处理。
- 替你做决定。 该包装器负责脱敏和记录。你的部署对已记录的事件采取什么措施——告警、轮换、吊销——是你部署自己的事。

许可证

MIT — 见 LICENSE。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入社群

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

DPharness QQ 群二维码,QQ 扫码进群
QQ 扫码进群
DPharness 飞书群二维码,飞书扫码进群
飞书扫码进群