dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Paste-time data masking plugin for DeepSeek Harness: sensitive values are masked before they reach the composer
粘贴到 DeepSeek Harness 对话框时自动剔除敏感信息的插件:手机号、身份证号、银行卡号、邮箱、API 密钥、数据库连接串、内网 IP 等在进入输入框之前就被替换掉,原文不会被发送给模型,也不会写进会话记录。
这是本仓库实现的完整插件(@local/dsh-plugin-data-mask),已经安装到 web profile 并启用。
你复制的文本 ──► 剪贴板 ──► 粘贴拦截器(本插件,在输入框之前)──► 脱敏文本 ──► 输入框 ──► 模型
│
└─► 输入框卡片上方的等宽提示条:「已脱敏 N 处」+ 按住查看原文 + 撤销脱敏
| 项目 | 说明 |
|---|---|
| 开发方式 | 由 AI 编程智能体完全自主开发(含设计、编码、测试、调试与验证) |
| 驱动模型 | DeepSeek V4.1 Flash Max |
| 开发平台 | DeepSeek Harness(本插件即为 DSH 的客户端插件,在 DSH 内开发、安装、调试与验证) |
| 人类参与 | 需求提出、使用反馈、缺陷报告与验收;代码由智能体编写 |
维护者:virzs(仓库所有者)。仓库内的代码、提交信息、测试与验证脚本均由上述智能体产出;如对实现有疑问或有改进意见,欢迎开 issue。
说明:本插件的开发过程如上;它运行时不调用任何模型 —— 脱敏完全在浏览器本地用正则与校验算法完成,不联网、不上传。
paste,先替换敏感信息,再把脱敏后的文本重新交给输入框,因此正常粘贴行为(撤销边界、引用芯片处理)保持不变。Switch / SegmentedControl),跟随主题与语言:
/正则/标志 => 替换文本,例如 /EMP-\d{6}/ => [工号];# 开头为注释,非法正则在行下直接报错| 方式 | 手机号示例 | 说明 |
|---|---|---|
| 整体替换(默认) | [手机号/固话] | 最安全,只保留类型 |
| 部分掩码 | 138****5678 | 保留可读上下文,隐藏部分定长补星号,不泄露原长度 |
| 星号覆盖 | *********** | 定长星号,隐藏长度 |
| 规则 | 说明 | 校验 |
|---|---|---|
| 证书/私钥 | PEM 私钥、证书块 | — |
| JWT 令牌 | 三段式 JWT | 头部必须是合法 base64url JSON |
| 数据库连接串 | scheme://user:pass@host、password=/api_key= 赋值 | — |
| 敏感字段值 | 键名命中关键词时,值无条件替换(短值也能处理) | 键名边界匹配 |
| API 密钥 | sk-、ghp_、AKIA、AIza、xoxb- 等前缀 | — |
| Authorization 头 | Bearer / Basic 凭据 | — |
| 银行卡号 | 13–19 位数字 | Luhn 校验 |
| 身份证号 | 18 位(含校验位)、15 位 | GB 11643 校验位 + 出生日期 |
| 邮箱地址 | 常见邮箱写法 | — |
| 手机号/固话 | 11 位手机号、带区号固话 | 号段格式 |
| MAC 地址 | 00:1A:2B:3C:4D:5E | — |
| 内网 IP 地址 | RFC 1918、回环、链路本地 | 私有网段判断 |
| IPv6 地址 | 含 :: 压缩写法 | 结构化校验(8 组或压缩) |
| QQ 号 | QQ: 12345678 | 默认关闭 |
值形态的正则对短值无能为力 —— "name":"12312"、"no":"1"、"phone":"123" 既不是手机号也不是身份证,靠值永远识别不出来。这类按键名处理:
| 键名命中 | 标签 | 例子 |
|---|---|---|
name / real_name | 姓名 | "name":"12312" → "name":"姓名" |
username / user_name / uname / nickname | 用户名 / 昵称 | "username":"123123" → "username":"用户名" |
work_no / workno / no / order_no | 工号 / 编号 | "work_no":"A1001" → "work_no":"工号" |
phone / mobile / tel | 手机号 / 电话 | "phone":"123132" → "phone":"手机号" |
email / mail | 邮箱 | "email":"12321" → "email":"邮箱" |
id_card / idcard / idno | 证件号 | "id_card":"…" → "id_card":"证件号" |
设计上的三个要点:
\w* 前缀吸收前缀,所以 work_no、order_no 都能命中 no;而 version、amount、count、support、country 不受影响(support 里的 sup 不是关键词,country 里的 un 也不是)。"no" : "1" 变成 "no" : "编号",脱敏后仍然可以直接 JSON.parse。优先级上这条规则排在所有值形态规则之前:字段名已经确定了敏感,值长什么样都无关。partial 模式下它按整值处理(短值做部分掩码等于把值露出来,与目的相反),redact 模式照常打星号。可在设置页逐条开关。
优先级按“越具体越先匹配”排列,命中区间不会被后面的规则二次替换:16 位银行卡不会被当成手机号,MAC 地址不会被当成 IPv6,敏感字段的值也不会被值形态规则二次改写。
localStorage。localStorage 的 dsh.data-mask.settings.v1 下。仓库需要打上 dsh-plugin topic 才会被 DSH 的插件列表收录(GitHub 仓库页右上角 ⚙ → Topics,或用命令行):
gh api --method PUT repos/<owner>/<repo>/topics -f "names[]=dsh-plugin"
# 当前仓库已打:dsh-plugin, deepseek-harness, dsh, cordis, data-masking, privacy
gh api repos/<owner>/<repo>/topics # 核对
# 安装(插件管理器会把它登记到 profile 的 dsh.profile.bundles)
# 在 Harness 里:plugin_manager install_bundle target = 本目录绝对路径
# 依赖:无。运行测试(含产物同步、清单一致性与“不阻塞启动”约束):
npm test
# 修改 engine.js 后把引擎重新内联进 client.js(tests 会检查两者是否同步)
npm run build
# 在真实浏览器里验证(需要一个运行中的实例 URL,含 token)
node scripts/check-web-boot.mjs "http://127.0.0.1:3080/?token=..."
node scripts/check-web-e2e.mjs "http://127.0.0.1:3080/?token=..."
node scripts/check-web-settings.mjs "http://127.0.0.1:3080/?token=..."
# 截图(仅本地视觉走查用;screenshots/ 已加入 .gitignore,不要提交)
# 截图会拍到运行中的 GUI,包含你自己的会话列表与对话标题,提交等于泄露。
node scripts/shoot-settings.mjs "http://127.0.0.1:3080/?token=..."
目录结构:
engine.js 规则、校验器、脱敏引擎(唯一事实来源,纯 ESM,可独立复用)
client.js 浏览器半边:内联引擎 + 粘贴拦截 + 提示条 + 设置页
index.js Host 半边(本插件功能全在浏览器端,仅保留 bundle 入口)
cordis.patch.yml bundle 补丁:插入 data-mask 行
scripts/build-client.mjs 把 engine.js 内联进 client.js 的生成脚本
scripts/repro-client.mjs 在 Node 里模拟浏览器模块表,复现客户端的加载错误
scripts/check-web-boot.mjs 无头 Chrome:抓真实控制台输出,确认启动门通过
scripts/check-web-e2e.mjs 无头 Chrome:验证粘贴脱敏、按住查看、撤销
scripts/check-web-settings.mjs 无头 Chrome:验证设置页渲染/切换持久化/开关生效
scripts/check-notice.mjs 无头 Chrome:验证提示条位置/按住查看/会话归属(切走与切回)
scripts/check-notice-align.mjs 无头 Chrome:验证提示条与输入框左右对齐等宽、真实指针连按三次
scripts/check-field-rule.mjs 无头 Chrome:验证按字段名脱敏(短值 JSON)在真实页面生效
scripts/check-session-flow.mjs 无头 Chrome:**在真实会话内**验证按住两次/撤销/切走切回
scripts/check-draft-deleted.mjs 无头 Chrome:**在真实会话内**验证删除草稿后提示条消失、显示原文期间保留
scripts/probe-session-identity.mjs 探针:确认切换会话时 composer 是否被复用(结论:逐字节相同)
scripts/probe-field-rule.mjs 本地探针:打印字段名规则在每个键上的判定(含误伤检查)
scripts/probe-field-idempotence.mjs 本地探针:验证「把脱敏结果再粘一次」不会二次改写
scripts/check-reveal-cycle.mjs 无头 Chrome:验证提示条宽度=输入框宽度、连续多次按住查看
scripts/check-page.mjs 无头 Chrome:页面现状排查(选择器找不到时用)
scripts/check-composer-dom.mjs 无头 Chrome:打印输入框区域的 DOM 与 data-* 标记
scripts/shoot-settings.mjs 无头 Chrome:截图到 screenshots/(仅本地,勿提交)
test/ node:test 用例(引擎行为 + 产物同步 + 清单一致性 + 启动安全约束)
都是踩过坑后固化的,测试会强制守护:
client.js 不能 require 本包的子路径。 浏览器模块表只解析包自身的 id 和
/client 子路径;@local/dsh-plugin-data-mask/engine 这类子路径解析失败会让
bundle 的 import 直接 reject,而客户端插件的 import 失败会触发 Web 启动门,
整个 GUI 停在 “Failed to load plugins” —— 用户连界面都进不去。所以引擎是
npm run build 内联进 client.js 的,test/package.test.mjs 会断言
require() 只允许模块表种子词(react、@deepseek-ai/dsh-client-ui-primitives)。inject 只声明 slots,locale 用 ctx.get('locale') 读取。 声明了却缺失的
服务会让 fiber 停在 pending,同样触发启动门。apply() 内每一步都包了 try/catch,
UI 渲染包了 error boundary:任何一步失败只降级功能,不会阻塞页面。ctx.effect(cb) 把 cb 的返回值当清理函数。 因此要交回清理函数,而不是在
ctx.effect 里执行副作用——写成 ctx.effect(installStyles()) 会“装好立刻拆掉”(样式
曾因此在浏览器里整个丢失)。[data-composer-card],不要用几何推导。 该卡片在
开始页只比视口窄约 27%、在会话页窄约 68%,且输入框自带一个内部滚动容器,任何宽高阈值
都无法同时命中两种布局(提示条曾因此压在输入框上)。提示条的宽度也取自同一张卡片的实测宽度,
不能写死。同理,函数不能重复声明:后声明的会静默覆盖前一个,noticeOffset 就这样被旧实现
盖掉过一次,测试因此加了重名断言。onPointerUp 加文档级监听会让一次抬起
执行两次写入(第二次变成“追加”而不是“替换”);而 setPointerCapture 会让第一次之后的
每次按下都收不到事件,表现为“只能按一次”。因此:松开只由文档监听负责,写入带幂等判断。
两条都有测试守住。diff: []),所以「节点还在」「结构指纹一样」
都无法证明还在同一个会话里 —— 提示条曾因此跟着用户跑到别的会话。唯一可靠信号是侧栏的
[data-row-key^="session:"][class*="selected"],并在侧栏上挂 MutationObserver 以便立刻响应。
同理,测试必须真的打开一个会话:开始页与真实会话是不同的子树,只在开始页验证会漏掉这类问题。开发提示:插件以 junction 链接方式安装在 C:\Users\virs9\.dsh\profiles\web\node_modules\@local\dsh-plugin-data-mask,直接指向本目录,改动即时生效(客户端代码刷新页面后加载)。
页面文案跟随 Harness 的语言设置(内置 zh / en 词典,通过 Client locale 服务注册;该服务不可用时自动回退到内置词典,语言以外壳写入的 document.documentElement.lang 为准)。