Skip to content
dsh.fish
Bundle

dsh-warden

Adversary review gate for DeepSeek Harness: an independent small model reviews destructive tool calls before they run, and reports every verdict.

Source
dingchenhui0618-arch
License
MIT
Updated
Updated 8 days ago

Readme

# dsh-warden

给 DeepSeek Harness 的**对抗式审查门**:危险的工具调用在执行前由一个独立的小模型复核,并在 Web GUI 里实时显示每一次判定。

## 为什么要有它

DSH 自带的工具审批只有一种粒度——弹窗问人。要么每次都问(打断心流),要么整个关掉(危险操作彻底没人把关)。warden 给第三种:**平时零打扰,只在真正危险的那一类调用上花一次便宜的模型调用**。

它挂在 DSH 留给插件的唯一前置钩子上:

```
tools/pre-execute  ->  { kind: 'allow' } | { kind: 'deny', reason } | { kind: 'ask' }
```

## 它拦什么

两层,第二层只对第一层的命中者触发:

**第一层(零延迟,本地正则)**
- 破坏性命令:`rm -rf`(任意旗标顺序)、`Remove-Item -Recurse`、`rmdir /s`、`format`、`mkfs`、`dd if=`、`DROP TABLE`/`DROP DATABASE`、`git push --force`、`git reset --hard`、`git clean -f`、`shutdown`、`Clear-Disk` …
- 敏感路径写入:`.env`、`credential`、`id_rsa`、`.ssh`、`.git/`、`settings.*`、`cordis*.yml`、`AGENTS.md`、`$DSH_HOME` 下任何文件

**第二层(一次模型调用)**
把「工具名 + 风险类别 + 完整参数 + 当前会话目标」交给审查模型,按自然语言规则判该不该拦。

普通读写(`read`/`grep`/`glob`)完全不进这套逻辑;普通命令和普通文件写入只计数、不送审。**日常工作的额外延迟是 0。**

## 安装

已发布到 npm,装到的是**预构建**版本(`lib/` 是入库的构建产物,不是装的时候现编),不需要授权任何构建脚本:

```sh
dsh plugin --profile <你的 profile> add dsh-warden
```

也可以直接从 GitHub 装:

```sh
dsh plugin --profile <你的 profile> add github:dingchenhui0618-arch/dsh-warden
```

装好后重启 DSH,对话视图里出现「审查」Tab。(桌面端在应用里安装插件,profile 由应用自己管理,不用手敲上面的命令。)

## 规则

内置一份默认规则(默认放行,只拦不可逆破坏与明显跑偏)。要改就写 `$DSH_HOME/adversary.md`——**文件存在时完全覆盖内置规则**,且按 mtime 自动重载,改完点面板上的「重载规则」即可,不用重启。

## 面板

对话视图 →「审查」Tab:

- 计数:工具调用 / 有风险类别 / 送审 / 放行 / 拦截 / 异常降级
- 最近 60 条判定:时间、工具名、风险类别、耗时、实际使用的模型、拦截理由
- 当前规则来自内置还是文件、文件路径、规则预览

![审查面板:一次真实会话里的计数与判定列表](docs/screenshot-1.png)

## 审计

每一次**送审**的调用追加一行 JSON 到 `$DSH_HOME/warden-audit.jsonl`(时间、工具、类别、决定、理由、模型、耗时)。未送审的普通调用只进内存环形缓冲,不落盘——否则审计会被普通命令淹掉。

## 运行时卸载

dsh 0.1.6 起,插件依赖在运行时解析,并且支持在会话中途卸载插件。所以这个插件的**每一处注册都有对应的拆除**:

- `tools/pre-execute` 监听器随 fiber 回收
- `/dsh-warden` 路由与注入的 `<style>` 各自持有 disposer
- client 的共享轮询计时器有一条**独立**的清理路径——它活在模块作用域,卸载时不能只指望 React 卸载

面板是可丢弃的那一半:如果路由注册失败(`webServer` 契约变动、宿主没有 web server),插件会吞掉这个失败继续跑。**闸门必须活着,面板没有就没有。**

`test/host.test.mjs` 里有一组测试专门钉这件事:跑完全部 disposer 之后路由和监听器都必须消失;`webServer.register` 抛异常时闸门仍要能拦截;一次跨过卸载点的审查既不能抛异常也不能把自己重新注册回去。

## 它不做什么(重要)

- **不阻塞正常工作。** 任何异常路径一律放行:模型不可用、上游报错、流被取消、输出无法解析、参数无法序列化、目标查询抛异常……全部 `allow`。这个插件最坏的表现是「什么都不做」。
- **永远不返回 `ask`。** 审批弹窗关闭时 `ask` 会退化成拒绝,一个犹豫不决的审查员会静默掐死用户的正常工作。
- **分类基于工具名 + 参数模式**,不是工具自报的类型。这是刻意的:插件作用域里读不到 agent 作用域的工具注册表。未知工具靠参数里的破坏性特征兜底。
- **不是安全边界。** 它降低误操作概率,不防恶意代码——同一进程内的插件本来就能绕过它。

## 开发

```sh
node scripts/build.mjs   # 零依赖构建(不需要打包器),产出 lib/
node --test              # 30 个测试
```

测试分两组:`classify` 是纯函数覆盖;`host` 用 stub ctx 驱动真实的 `apply()`,把四个结局都钉住——拦截、放行、五种失败降级、以及「惰性调用一次模型都不调」。

`lib/` 是构建产物,改 `src/` 后必须重新 build。

## License

MIT

Install

dsh plugin --profile web add github:dingchenhui0618-arch/dsh-warden

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source