在工具真正执行前,用这个工具自己的 parameters 校验入参,被拒时给出精确的字段路径与类型。宿主只把 parameters 渲染进 prompt,从不按它校验调用,所以类型错误的值会一路走到工具体内:一个数字进了 path.join,调用方拿到的是一句原始 TypeError 而不是回答。本插件挂在 tools/pre-execute 上,取出工具定义, 返回 deny,reason 形如「demo_tool.dir: expected string, got number (123)」。覆盖原始类型与联合类型、 required、enum/const、嵌套 properties、数组 items、additionalProperties:false、oneOf/anyOf/allOf。 故意 fail-open:自身出错、工具未注册、或遇到读不懂的 schema 构造,一律放行。mode 可选 deny 或 warn, 另有 skip 名单。两个只读工具:arg_guard_status 与 arg_guard_check。
安装
# npm 包(预构建)
dsh plugin --profile web add @bushizhanghan/dsh-arg-guard
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:bushizhanghan/dsh-arg-guard
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
DSH 校验工具输出:
output.schema对不上就抛ToolOutputError。 它从不校验工具输入:parameters只是渲染进模型提示词的一张纸。 于是错类型一路走到工具函数体里,变成一句裸 Node TypeError——The "path" argument must be of type string、p.replace is not a function、slug.toLowerCase is not a function。对 5 个已发布插件做 148 次参数模糊调用, 47 次以这种裸错误结束,来自 12 个不同入口点。
这个插件补上那道缺口:在 host 的 tools/pre-execute 瀑布里,按工具自己声明的
parameters 校验入参;违规就拒绝,并把确切的 JSON 路径 + 期望类型交给模型。
模型看到的不再是崩溃,而是一句能照着改的错。
缺口的位置(读 host 源码确认,不是推测)
| 位置 | 事实 |
|---|---|
_dsh-tools.js:3321 |
const gate = await this.ctx.waterfall(carrier, "tools/pre-execute", exec, () => ({ kind: "allow" })) —— 唯一的入参关卡 |
_dsh-tools.js:3345 |
拒绝理由原样变成模型看到的 Error: <reason> |
_dsh-tools.js:3091 / 3107 |
get(name, scope) / resolveExecution(name, scope, nested) 拿得到定义,定义上带 parameters |
| 输出侧 | output.schema 有强制校验,会抛 ToolOutputError |
也就是说:schema 写给模型看,但没人照着检查。 每个插件作者只能自己手写
typeof 守卫,而手写的总会漏——上面 47 次里没有一次是"作者知道但选择不防"。
它做什么
注册一个 tools/pre-execute 处理器,对每次工具调用:
{"dir":123} → {"kind":"deny","reason":"publish_gate.dir: expected string, got number (123)"}
{"mode":"fix"} → {"kind":"deny","reason":"publish_gate.dir: required but missing"}
{"dir":"E:/p","mode":"slow"} → {"kind":"deny","reason":"publish_gate.mode: expected one of \"check\", \"fix\", got string (\"slow\")"}
{"dir":"E:/p","extra":1} → {"kind":"deny","reason":"publish_gate.extra: unexpected property (allowed: dir, mode)"}
{"dir":"E:/p","mode":"fix"} → {"kind":"allow"}
(上面是真实输出,用 publish_gate 的 schema 驱动。)
拒绝理由的格式固定为 <工具名>.<JSON 路径>: expected <类型>, got <类型> (<值>),
根级错误省掉点号:publish_gate: expected object, got string ("…")。
安装
dsh plugin --profile desktop add @bushizhanghan/dsh-arg-guard
重启 DSH 后生效。只读:不写磁盘、不发网络请求,计数器只在内存里。
配置
- id: arg-guard
name: "@bushizhanghan/dsh-arg-guard"
config:
enabled: true # 总开关,默认 true
mode: deny # deny = 拦截;warn = 只记录、放行
skip: [] # 不检查的工具名,例如 [bash]
上线顺序建议:先 mode: warn 跑一段时间,用 arg_guard_status 看
violations 里有没有误报(工具声明的 schema 比真实契约更严时会误报),
确认干净再切 deny。
Agent 工具
| 工具 | 作用 |
|---|---|
arg_guard_status |
hook 是否挂上、mode、skip、计数器(checked/allowed/denied/warned/skipped/errors)、最近 10 条违规 |
arg_guard_check |
{tool, args} 干跑:返回和真实 hook 完全一致的 verdict 与理由,不执行、不计数 |
arg_guard_check 的典型用法:某次调用被拒了,把当时的参数丢进去看为什么;
或者发请求前先自检一遍,省一次往返。
校验哪些规则
| 规则 | 例 |
|---|---|
| 原始类型 | type: string / number / integer / boolean / null |
| 类型联合 | type: ["string","null"] |
| 必填 | required: ["dir"](传 undefined 等同于没传) |
| 枚举 / 常量 | enum、const |
| 嵌套对象 | properties 递归,路径用点号连起来 |
| 数组元素 | items(对象或元组形式) |
| 封闭对象 | additionalProperties: false(patternProperties 命中的键放行) |
| 变体 | oneOf / anyOf / allOf |
不校验什么(明确的边界)
- 不做类型强转:schema 说
integer,"3"就是错的。守卫的职责是让模型改, 不是替它猜。 - 不认识的 JSON Schema 关键字一律忽略:
$ref、if/then/else、format、dependencies、自定义关键字——看不懂就不判定,因为看不懂不等于调用是错的。 - 不校验输出:host 已经在做了。
- 不做跨进程:只作用于注册在同一个 host 里的工具。
为什么它不会把工具调用搞坏
这是这个插件的第一原则:守卫弄坏的调用,比没有守卫更糟。
- 整个处理器包在
try/catch里,任何异常都 fall through 到allow; - 解析不到定义(工具没注册、被 scope 限制、
resolveExecution抛错)→allow; - 工具没声明
parameters.properties→allow(没有契约就不存在违约); - 类型名不认识 → 不判定;
- schema 本身畸形(不是对象、是数组)→
allow; - 有
skip名单,随时能放行某个工具; - 有
mode: warn,可以先只观测。
validateArguments 本身也是纯函数 + 全 try/catch:它是 38 个测试里唯一被直接
断言的对象,包括"定义查询抛错仍然放行"这条。
开发
npm test # node --test,38 个用例
lib/engine.js 是纯函数(validateArguments(parameters, args) → null 或一句理由),
lib/index.js 只负责挂 hook、查定义、记账、注册两个只读工具。
License
MIT
链接
同类插件
kenryu42/cc-safety-net★ 1900
执行前守卫,在工具调用运行前拦截破坏性 Git 与文件系统命令,以及对 SSH 密钥、.env 等机密文件的访问。
ADWMC/helm-d#helmd★ 984
DeepSeek Harness 的安全分析插件:33 个工具覆盖六大安全域、证据/报告与工具集,并提供案件生命周期、路由和工具账本;按会话绑定的案件工作区支持证据链、E 编号校验与可复现结论(repro_cmd);内置 362 篇按需参考、三档可热切换人设,以及带案件状态恢复的破甲 / Case / 拦截 / 关于四页工作台。
toby-bridges/api-relay-audit★ 904
从 DeepSeek Harness 对 AI API 中转站和 LLM 代理运行本地安全审计,生成 Markdown 报告,覆盖提示词注入、模型替换信号、工具调用改写、错误泄漏、流完整性和按 profile 启用的 Web3 风险。
SeaOf0/dsh-redteam-model★ 807
面向授权安全研究的 DSH 合集:九个工作模式(redteam 总控、渗透测试、代码审计、二进制分析、攻防评估、免杀对抗、应急溯源、云安全攻防、CTF 解题)与十五个运行时插件,设置页管理台支持一键部署、安装、更新与卸载。
howmp/dsh-pentest★ 616
面向 DeepSeek Harness 的授权渗透模式:以探索链路记录目标、线索、资产与漏洞,并在 Web 中可视化展示。
PerryLink/dsh-auto-review★ 244
审批链上的第二模型自动审查:只读审查子代理返回带理由的 allow/deny 结构化裁决,默认 fail-closed。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。