dsh-risk-gate:给破甲补一道人肉闸门

前言

用 DSH 跑智能体时,很多人会装 dsh-purge 这类补丁,把审批改为自动放行、沙箱默认全开。代价是 git push、递归删除、改注册表这类高危动作不再有人拦。dsh-risk-gate 不撤销破甲,而是在这些动作上加一道机械闸:命中规则直接 deny,必须由用户本人回复一次性编号才放行。它由 oobitoooo 维护,分类为 admin-security,GitHub 上有 1 个星标,属于社区小工具,不是官方组件。

它解决什么问题

dsh-purge 的 patch #9 把 @deepseek-ai/dsh-user-approval 替换成「每次请求直接 allowed-once」:

// 宿主被打补丁后的实际行为
if (this.effectivePolicy(session) === "never") return "allowed-once";
return "allowed-once"; // 不再弹窗、不再 fail-closed

这意味着任何依赖 kind: 'ask' 的方案都等于自动同意。把 approval 配置改回 ask 也没用,代码层已被改写;让 purge 少打补丁也不行,它不支持按补丁禁用,升级还会重打。

本插件不走 approval,而是自己挂门:

  1. 挂 tools/pre-execute(可重排的 allow/deny 门禁),命中就 deny;
  2. 再挂 ctx.tools.guard() 做单调兜底,后续任何监听器想把 deny 翻成 allow,都会被再挡一次;
  3. 放行凭据是用户消息里出现的一次性编号,从 agent/pre-step 读取,模型自己编的编号无效;
  4. 编号一次性、同一条命令重试复用同一个编号、10 分钟过期。

它不做什么

  • 不修改宿主文件、不注入提示词人格、不碰 app.asar;
  • 不撤销破甲的其它行为,连续执行、工具上限、子代理深度等照旧;
  • 不联网,除了你自己的 policy.json,没有任何外发;
  • 不是沙箱、不是权限系统。它拦的是工具调用参数,不是意图。

安装

需要 DSH Desktop 或 dsh web,Node ≥ 18。

方式一:GitHub 直装(推荐)

dsh plugin --profile desktop add "https://github.com/oobitoooo/dsh-risk-gate/releases/latest/download/dsh-risk-gate-1.1.0.tgz"
# web profile 把 desktop 换成 web

想钉死版本,把 latest/download/ 换成下载具体版本号。

方式二:本地 clone / link(开发用)

powershell -ExecutionPolicy Bypass -File scripts\install.ps1            # 默认 desktop profile
powershell -ExecutionPolicy Bypass -File scripts\install.ps1 -Profile web

方式三:手动(不信任脚本时)

  1. 把仓库放到 $DSH_HOME\plugins\dsh-risk-gate;
  2. profile 的 package.json 增加依赖 "dsh-risk-gate": "file:../../plugins/dsh-risk-gate",并把 "dsh-risk-gate" 追加到 dsh.profile.bundles;
  3. 在 profile 目录 pnpm install,或手工建 node_modules\dsh-risk-gate 的 junction 指回源码;
  4. 重启客户端。

装完需要完全退出 DSH(含托盘图标)再打开。改 package.json 或 profile 的 cordis.patch.yml 会触发热重载。README 安装命令里写的是 1.1.0 的 tgz 路径,而仓库 package.json 当前版本是 1.3.1,使用时以实际 Release 文件名为准。

典型用法:确认流程

一次命中规则的调用会被拦截,宿主返回类似下面的内容:

Error: [risk-gate] 已拦截:递归删除 / 格式化文件(R1)
目标:Remove-Item -Recurse -Force "…\dist"

这是高危操作,需要用户本人确认。请把下面这段原样发给用户,然后停下等回复:
「…确认请回复:确认 8CFC6197」

用户回复 确认 8CFC6197 之后,原样重试同一条调用才会放行一次;再执行同一条命令又会被拦。编号只认用户消息,模型自己写「确认 xxx」不算,子代理和别的会话里确认也不通用。

批量任务:授权窗口

发版、迁移、批量改名这类连串动作里,逐条确认是反向升级。插件提供授权窗口:用户发「授权 30 分钟」后,30 分钟内高危操作直接放行,逐条写进 audit.jsonl(decision=allow-by-grant)。随时发「取消授权」立即关闭。上限 120 分钟,超出按 120 分钟算。窗口内不改变规则判断,只是跳过「要编号」这一步。

默认拦什么

规则 内容
R1 递归删除/格式化:Remove-Item -Recurse、rm -rf、rmtree、diskpart、Format-Volume
R2 git push、reset --hard、clean -f、改远端、删 release/repo
R3 注册表、服务、计划任务、防火墙、执行策略、Defender 排除、hosts、takeown/icacls
R4 装/卸全局包与插件:npm/pnpm -g、winget、choco、pip install、dsh plugin add
R5 对外连接/外发:ssh/scp/nc/chisel/frp/ngrok、带 body 的 POST
R6 下载即执行/编码混淆:\| iex、DownloadString、-EncodedCommand、FromBase64String
R7 强杀进程/提权:Stop-Process -Force、taskkill /f、runas、psexec、sudo
R8 run_code 里起子进程:child_process、subprocess、os.system
R9 删除或写关键路径:C:\Windows、Program Files、ProgramData、resources\app、.ssh、.credentials

R1 有例外:目标落在允许根内且条目数不超过 maxEntries(默认 200)时直接放行;带 ; | && 拼接的不享受例外。

配置

运行时读 $DSH_HOME/risk-gate/policy.json(Windows 为 %USERPROFILE%\.dsh\risk-gate\policy.json),改完热读,不用重启。首次启动会自动落盘默认值。

{
  "ttlMs": 600000,
  "rules": [
    { "id": "R1", "label": "递归删除 / 格式化文件", "flags": "i",
      "pattern": "remove-item[^\\n]*-recurse|\\brm\\s+-rf\\b|shutil\\.rmtree" }
  ],
  "protectedRoots": ["c:\\\\windows", "\\\\resources\\\\app", "\\\\.ssh"],
  "recursiveDelete": {
    "allowRoots": ["C:\\\\work\\\\my-project"],
    "maxEntries": 200,
    "useWorkdirAsRoot": false
  },
  "allow": ["^\\s*git\\s+(status|diff|log|show|branch)\\b"]
}
字段 作用
rules[] 命中任意一条即拦截(正则,flags 默认 i)
protectedRoots[] write/edit 命中这些路径片段视为高危;删除类命令也用它做 R9
recursiveDelete.allowRoots R1 的例外根
recursiveDelete.maxEntries 条目上限,超了重新要确认
recursiveDelete.useWorkdirAsRoot 把 workdir 也当作允许根(默认关)
allow[] 读类命令白名单,只在命令没有链式拼接时生效
ttlMs 编号有效期,默认 10 分钟

其它产物:risk-gate/audit.jsonl(每次 block / allow-after-confirm / warn,2 MB 轮转)、risk-gate/boot.log、risk-gate/pending.json、risk-gate/last-user.json。

审计与风险分级

审计用的 HMAC 链式结构来自社区项目 dsh-guardwall:每行带 prev 与 h(HMAC-SHA256,密钥 risk-gate/chain.key),每 20 行落一次 chain.checkpoint.json,跑 risk_gate action=verify 能发现改行、换密钥、尾部截断。落盘前会脱敏 ghp_、github_pat_、sk-、AKIA、Bearer …、password=…,命令里带令牌也不会原样进日志。规则可写 risk: 1–10,配 thresholds.block(默认 7)和 thresholds.warn(默认 4)。

验证与开发

node --check lib/index.js      # 语法
node test/smoke.mjs            # 纯逻辑回归(假 ctx,不启动 DSH),33 条
node scripts/livecheck.mjs     # 用当前 policy.json 跑 20 条样例
node scripts/pack.mjs          # 生成 dsh-risk-gate-<version>.tgz

Windows 上另有 scripts\verify.ps1(四层接线体检)、scripts\status.ps1、scripts\uninstall.ps1。新会话里可以直接让模型「跑一下 risk_gate status」,插件注册了 risk_gate 工具(status / rules / pending / selftest / audit)。

适用场景与注意

适合已经在用 dsh-purge/purge-community、担心模型自作主张执行高危命令的 DSH 用户,以及想给团队智能体加一道人工确认环节的开发者。插件以当前 DSH 进程的权限运行,安装前建议先读一遍源码和 MIT 许可证,确认规则和审计行为符合你的预期。它是一道按规则 + 编号的护栏,不是沙箱,也不隔离文件系统和网络。

边界要清楚:拦的是调用参数,不是意图;python -c "print(1)" 放行,用 python 调 os.system 会被 R8 拦。规则是正则,绕法无穷。它也不是锁,谁有本机写权限就能改 policy.json 把规则删空。设计上误报优先于漏报,宁可多问一次。

小结

dsh-risk-gate 的价值在于:不与破甲对抗,只在高危动作上加一道可追溯的人肉闸门,编号只认用户消息,审计日志可验证且脱敏。如果你已经在用 dsh-purge,值得一试。

目录页:https://www.skillhub.cn/plugins/oobitoooo/dsh-risk-gate
GitHub:https://github.com/oobitoooo/dsh-risk-gate

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜