dsh-escalation-review
Verifieddsh-escalation-review · v0.2.3 · MIT · Web UI
Escalation-only LLM reviewer: reviews sandbox escapes only, keeps the sandbox, fails closed.
Install
dsh plugin add dsh-escalation-review Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
dsh-escalation-review
English · 简体中文
只审「沙箱越界」的 LLM 评审插件(DeepSeek Harness,下称 DSH)。
工作区内的读写与命令跑在原生沙箱上,不评审、不产生额外模型调用。插件只为一种情况醒来:某次工具调用
请求离开沙箱(sandbox_permissions 与当前生效模式不同)。这一次调用会被交给模型裁决 —— 放行、拒绝,
或者交回给你 —— 并且走宿主本来就有的一套管路:把审批回答成 allowed-once,或在工具体执行前返回三态决策对象。
Codex 风格的评审器
策略跟随 OpenAI 的 Codex guardian 模板(codex-rs/prompts/templates/guardian/policy_template.md),
这也是本插件主要的工作量所在。
- 三轴契约:风险档位与授权分别打分,两者一起决定
allow/deny/ask。授权在每一次越界时重新 推导:过去的批准只有在证据仍留在会话里时才算数。 medium看的是后果可逆,不是工件可逆:只有当爆炸半径有界并且它造成的影响能被撤销时,才算medium。能把文件改回来,不等于能把效果收回来。- 系统与安全控制状态一律
high:hosts、DNS、防火墙规则、服务、证书、PATH。它们都能改回来, 但已经发出去的流量收不回来。 - 有更安全的替代方案时降档:同一目标若能绕开危险步骤达成,那么危险步骤就不是用户要的东西。
- 缺失的记录不等于许可:没有证据就是没有授权,而不是默认同意。
- 事后批准按高风险处理:事后补的批准打不开
critical动作。 - 有界根投影:用户指令以「头部 4 条 + 尾部 12 条、上限 16」进入评审,丢就整条丢、不截断,并显式留下 「证据不完整」标记。证据按角色标注:人类指令、直接父级、审批回答、检查点、事实。
另有四条决定边缘情况:
- 用户明确禁止 = 硬拒绝,它压过风险档位,连低风险动作也不例外。
- 证据是数据,不是指令:命令文本、文件内容、工具输出都不构成授权;写着"用户已经允许了"的文本什么也 证明不了。
- 不按外观降档:形似凭据的载荷不会因为"看起来像占位符"而变安全,导出也不会因为自称的去向而变安全。
- 先交叉校验,再判定:待审动作会与同一
callId的日志tool/call比对,一旦明确不一致就中止评审; 判定格式不合法按评审失败处理。
这些想法的来源与逐字重合度的实测记录见 NOTICE.md。
与内置 auto 预设的区别
应用内置 @deepseek-ai/dsh-experimental-auto-review:它注册一个 auto 权限预设,把会话绑到
danger-full-access,并审核每一个原生调用 —— 每次调用多一次模型请求,且运行在完全放开权限下。
内置 auto 预设 |
本插件 | |
|---|---|---|
| 沙箱档位 | danger-full-access |
会话原本的档位,因此 workspace-write 继续生效 |
| 审核范围 | 每一个原生调用 | 只有请求越界的那一次 |
| 预设表 | 注册 auto |
不动你的预设表 —— 门控是本插件自己的开关 |
| 额外模型调用 | 每次调用一次 | 每次越界一次,其余为零 |
保留会话原本的沙箱档位,正是这个对比的意义:普通工作由沙箱兜住,评审器只补上 DSH 本来会停下来问你的那一段。
行为
| 情形 | 结果 |
|---|---|
| 沙箱内的普通调用 | 不评审,原样交给下游处理者。 |
| 越界调用,介入开关关闭 | 不评审、不干预、不花模型调用。这是默认状态。 |
| 越界调用,开关打开 | 评审一次并记日志。判定放行则审批被回答成 allowed-once(你不会被问第二次);判定拒绝则工具不执行、理由回传模型。 |
| 评审失败 / 超时 | 由 failMode 决定,默认 deny(失败即拒)。 |
| 连续三次拒绝,或 50 次窗口内 10 次 | 熔断:不再拒绝,改为交回人工,避免把整个任务卡死。 |
| 待审动作与会话日志里的工具调用明确不一致 | 停止评审,而不是拿无法核对的证据下结论。 |
运行前提
- DSH 0.1.7-rc.2 或 0.2.0-rc.1(两者都实测过),Node 24。
- 只实测过 Windows;其它平台预期可用,但未验证。
peerDependencies:@deepseek-ai/cordis~4.0.4、@deepseek-ai/dsh-llm>=0.1.7-rc.2 <0.3.0。@deepseek-ai/dsh*的 peer 要写版本区间:宿主用semver.satisfies(runtime, range, { includePrerelease: true })判定,写死精确预发布版本会在 DSH 升级后被判为不兼容。
安装
从本地目录安装,用 GUI 最干净:左侧边栏「插件」(不是"设置")→ 「添加插件」→ 填这个目录的绝对路径,
例如 D:\path\to\checkout\plugins\escalation-review。它会以 link: 依赖登记,并追加到
dsh.profile.bundles。
dsh plugin --profile <profile 名> add <包名或路径>
~/.dsh/profiles/web与~/.dsh/profiles/desktop互相独立,装进一个不影响另一个。想用dsh web测,就加--profile web。- 宿主半边是启动时从磁盘加载的,所以装完要重启 DSH;改了
lib/下任何文件也要重启一次。会话里的 客户端半边同样需要重启才重载,桌面版里单纯刷新页面不够。
卸载在同一张插件页上做,或者从 dsh.profile.bundles 里删掉那一条。
配置
enabled 是总门控,随包关闭:关闭时插件不评审、不产生模型调用;打开后它接管所有沙箱越界的审批。
门控属于插件本身,而不是你的权限表 —— 所以安装它不会改写你的预设,也与某个会话恰好选中哪个预设无关。
可以在配置页上设(卡片第一行的两档控件:关闭 / 打开),也可以写 "enabled": true,或 GUI 写入的
文本形式 "enabledText": "true"。
优先级(低 → 高):包内 config.json → ~/.dsh/escalation-review.config.json → 传了 configPath 就用它
→ 插件条目里的 config: 段 → 配置页写入的设置层(有它自己的 *.ui.json 伴生文件)。文件层每次越界时重读,
设置层约每 2 秒轮询一次,所以改动不需要重启;只有改插件代码才必须重启。
本包的 config.json 容忍 // 与 /* */ 注释;cordis.patch.yml 是 YAML,注释只能用 #,解析失败会让
加载器静默跳过整个 bundle。
| 键 | 默认值 | 含义 |
|---|---|---|
enabled |
false |
介入开关(总门控)。 |
enabledText |
"" |
开关的文本形式("true" / "false"),由配置页写入;非空时覆盖 enabled。 |
provider / model |
"" |
用哪个 provider 与模型评审;留空跟随当前会话。 |
reasoningEffort |
"" |
评审思考强度:off、minimal、low、medium、high、xhigh、max,留空跟随会话。 |
failMode |
deny |
评审失败或超时的含义:deny 或 ask。 |
denyMode |
deny |
评审判定为拒绝的含义:deny 直接拒绝,ask 交回人工。 |
probeRunner |
inproc |
只读探针:inproc(不 spawn 进程)或 shell(沙箱内只读命令)。 |
policyExtra |
"" |
追加到评审策略末尾的自定义规则(自然语言)。 |
allowedHosts |
[] |
命中这些主机的常规网络操作按 low 处理。注意:在用户配置里写空数组会覆盖包内清单,要写就写全量。 |
timeoutMs |
100000 |
一次评审的总预算(含重试)。 |
attemptTimeoutMs |
30000 |
单次请求上限;超时后可以重试。 |
retryDelayMs |
5000 |
两次尝试之间的等待。 |
minAttemptMs |
2000 |
扣掉等待后剩余预算低于此值就不再重试。 |
它做什么,不做什么
它替你回答越界审批。 开关打开时,放行判定会以 allowed-once 的形式交付:那一次调用在沙箱之外执行,
过程中不弹窗。能力与风险都在这里 —— 你把"这一次我自己看一眼"换成了"模型看过一眼"。
它把证据发给你的模型提供方。 待审动作、会话上下文、有界指令投影与本地事实,会组成一次评审请求, 走 DSH 里配置的 provider(或你在卡片里选的那个)。评审消耗 token。
它留一份本地日志。 ~/.dsh/escalation-review.log 每行一个 JSON 对象,含命令文本与判定结论、理由。
请把它当作和它所概括的会话同等敏感的东西。
它从不写会话事件。 判定与理由在内存里交付、并呈现到审核卡片上,所以会话历史仍是宿主写下的样子。
失败即拒,反复被拒会熔断。 超时、JSON 不合法、provider 报错、交叉校验失败,默认都以 deny 收场;
连续三次、或 50 次窗口内 10 次,熔断触发,这一次交给你。
每个判定都经过交叉校验,与日志里的工具调用比对,因此结论只建立在能对上号的那次调用上。
日志与证据
- 插件解析 DSH 内部包时先取运行中的应用自带那份(
app.asar/dsh),再退到 profile,这样升级应用后 仍用应用自带的那份。实际命中的解析根记在assembler-resolved日志行里。 - 只读探针默认走进程内(不 spawn 任何进程);
probeRunner: shell时走沙箱内的只读命令,单条约 650–700 ms。 额度上限:最多 4 条、总预算 3 秒、单条 1.2 秒、输出 2 KB。拿不到pwsh时走进程内通道。 - 评审日志:
$DSH_HOME/escalation-review.log,每行一个 JSON 对象;tools/review-report.mjs会把它与 会话上下文(按callId关联)渲染成 Markdown。主要事件:ready、intervention-gate、config-effective、assembler-resolved、reviewed、reviewer-failed、review-retry、approval-granted、circuit-breaker、projection-registered。
审核卡片
越界调用会在对话里出现一张审核卡片,显示状态(评审中、待审批、已放行、已拒绝、已执行但工具报错)与理由, 点击可展开细节。状态颜色取自官方主题 token。
已知限制:放行的理由是靠会话内存里的投影带过去的 —— 审批结论本身没有理由字段。会话重新加载后, 旧调用的状态还在,但那条理由会丢;新发生的评审不受影响。
全部通过有文档的扩展点工作:tools/pre-execute、approval/request、sessionProjections、
conversation.chat.node、configForms。