在工具执行前拦截 edit/write 写入内容里匹配配置正则的字符,默认匹配全角圆括号,同一回合命中后连带拦截后续 pwsh,报错文案与正则均可配置。
安装
# npm 包(预构建)
dsh plugin --profile web add dsh-write-rule-guard
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:better-er/dsh-write-rule-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
在写入类工具执行前按配置的正则拦截写入内容,命中即拒绝并把 pwsh 切到禁用态,防止改用 pwsh 绕过;被拦截工具一旦成功执行,本回合内即解除禁用,回合结束恢复允许。
默认规则与工具范围由 cordis.patch.yml 随安装注入,匹配全角圆括号,默认覆盖 edit / write / edit_remote / write_remote,都可在配置文件里改。host 单半身,代码不内置默认规则,rules 为空即不拦截。
工作方式
host 半身 lib/index.js 按 edit / write / edit_remote / write_remote 的成败维护「禁 pwsh」状态机。pre-execute 里拦截 edit / write / edit_remote / write_remote 的写入内容,按配置的 enabled / rules 逐条检查,命中即视为失败并进入禁 pwsh 态;post-execute 里按真实执行结果收口状态——返回错误视为失败继续禁用,执行成功则解除禁用。pwsh 的放行与否由当前禁态决定。
拦截范围
- 工具:常态拦
edit和write的写入内容,patch 默认再拦 dsh-remote-file-system 的edit_remote与write_remote,也可自行配置更多工具;被拦截工具失败后,后续的pwsh进入禁用态被拦,文案由pwshMessage决定,缺省用内置默认。此后某次写入工具真实执行成功即解除禁用,回合结束一律回到允许。 - 字段:
edit的new_string、write的content;extraTools的工具按各自contentKey提取新内容,默认edit_remote取new_string、write_remote取content。 - 场景:全局所有会话生效,
run_code内嵌的edit/write子调用同样被捕获。
宣传片
四件一起讲的宣传片,中文与日语配音两版同在一个投稿里:
| 文件四件套宣传片 · 一个核心,三个辅助 |
|---|
安装
从 GitHub 安装:源码在 src/,lib/ 不入仓库,安装时 npm 会触发 prepare 脚本现场构建。
dsh plugin --profile web add github:better-er/dsh-write-rule-guard
从 npm 安装:包内已含构建产物 lib/index.js,安装时不再构建。
dsh plugin --profile web add dsh-write-rule-guard
两种方式装完都会自动挂载,重启 DSH web 后启用,无需手工编辑任何文件。
卸载
dsh plugin --profile web remove dsh-write-rule-guard
彻底移除,重启 DSH web 后不再加载。
配置
插件通过 cordis 配置注入,默认 enabled: true。规则列表 rules 由 cordis.patch.yml 随安装带入一条匹配全角圆括号的默认规则;代码不兜底默认,rules 为空即不拦截。extraTools 由 cordis.patch.yml 随安装带入 dsh-remote-file-system 的 edit_remote 与 write_remote,给空数组则只拦内置的 edit / write。
配置文件位于 ~/.dsh/profiles/web/cordis.patch.yml,在此文件中添加对本插件的 config 覆盖即可。
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true | 是否启用拦截 |
joiner |
单个空格 | 多规则命中时拼接各规则文案的分隔符,可填空格或 \n 等,由配置决定形态 |
pwshMessage |
内置默认文案 | 处于禁 pwsh 态时拦截 pwsh 的文案,支持 {reason} 占位符嵌入最近一次失败理由;空则回落到内置默认。禁 pwsh 态指本回合内某次被拦截工具失败 |
rules |
patch 注入的单条匹配全角圆括号的默认规则,为空则不拦截 | 规则列表,每条含 enabled / pattern / message |
extraTools |
patch 注入的 edit_remote 与 write_remote |
额外拦截的工具列表,每项含 name 和 contentKey;给空数组则只拦截内置的 edit / write。文件路径参数固定按 file_path 读取,路径字段名不同的工具命中规则时 {file} 会填成「未知路径」;与内置 edit / write 同名的条目会覆盖内置映射 |
安装即随 cordis.patch.yml 注入一条默认规则,可直接对照修改;rules 为空则不拦截。每条规则独立检查、任一命中即拦,缺 pattern 的条目被忽略。单条规则命中的输出即该规则 message 填占位符后的单行文本;多条命中时用顶层 joiner 拼接,不再自动追加明细。
pwshMessage 缺省或为空时回落代码内置默认文案,patch 也注入了同款,可在配置文件里改成别的写法;想带上原写入理由就在文案里加 {reason} 占位符。
配置示例,等价于 patch 注入的默认规则与工具范围:
patch 的
config是整块替换而不是逐键合并,所以在 profile 里覆盖本插件的 config 时必须重述extraTools,否则会退回空数组。
- id: dsh-write-rule-guard
config:
enabled: true
joiner: ' '
pwshMessage: 'あー!坏孩子被抓住啦!用 pwsh 绕过可不行哦!先改对了再来吧!'
rules:
- enabled: true
pattern: '[\uFF08\uFF09]'
message: 本次写入未遵循用户偏好,已被用户拒绝写入。请修改为不使用括号的描述方式。行:{lines};文件:{file}
extraTools:
- name: edit_remote
contentKey: new_string
- name: write_remote
contentKey: content
pattern 支持任意合法正则;若某条 pattern 是非法正则,该条保守放行不阻断。message 支持 {file}、{count}、{lines}、{pattern} 占位符,其中 {lines} 填充违规所在不重复行号的逗号分隔纯数字列表,如 3, 7。输出即填占位符后的结果,完全由 message 决定;需单行时 message 别写换行即可。
状态机与已知局限
对每个会话,按被拦截工具的结果维护一个只在当前回合内有效的开关:
| 事件 | 结果 |
|---|---|
| 被拦截工具内容命中规则被拦 | 进入禁 pwsh 态,记录该次规则理由 |
| 被拦截工具真实执行返回错误 | 进入禁 pwsh 态,记录该错误信息 |
| 被拦截工具真实执行成功 | 解除禁 pwsh 态,本回合后续 pwsh 放行 |
| 回合开始或结束 | 一律重置为允许 pwsh |
「成功」按真实执行结果判定,需要内容先过规则检查、再在 post-execute 看到无错误的返回值才算数;纯内容通过但执行阶段失败仍算失败。规则拦掉的调用也会以错误结果流经 post-execute,与执行失败走同一收口,语义一致。
为什么不直接检查 pwsh 要写什么:做不到。重定向、Out-File、Set-Content、变量、管道、运行时拼出来的命令,写文件的写法无穷多,很多内容还要等运行时才算得出来,静态检查拿不到,列黑名单必漏。所以这里不去猜命令的语义,只在「本回合已经出过一次写入失败」时把整条命令行先关上。规则查的始终是写入工具的内容,pwsh 本身从不参与内容匹配。
已知局限:禁用 pwsh 是为了拦住「直接用 pwsh 写文件绕过内容规则」这条路。本设计下模型只要先做一次成功写入把状态解开,之后仍可改用 pwsh 写入违规内容,从而绕开守卫——这是取舍后的有意行为:一次真实成功写入被当作「模型已遵守规则」的信号,代价是放开了这条潜在绕过路径。extraTools 配置的远程工具同样受规则检查,但远程操作的成功/失败判定由远端宿主决定。需要严格防绕过时不建议依赖本插件,应改用更底层、可逐命令校验的写保护。
构建
源码为 TypeScript,host 半身 src/index.ts,用 esbuild 打包到 lib/index.js。host 产物无外部运行时依赖,esbuild 仅作 devDependency。
pnpm install
pnpm build # 生成 lib/index.js
pnpm watch # 监听 src/index.ts 增量重建
导出
compilePattern(pattern):编译用户正则,非法时返回 null。findMatches(content,pattern):扫描文本,返回匹配位置清单。collectLines(hits):提取命中所在的不重复行号,升序排列。fillMessage(template, ctx):替换报错文案占位符,ctx 含 file / count / line /pattern。buildReason(message, file, hits,pattern):构造单条规则的单行拒绝原因。
License
链接
同类插件
yjh051108/dsh-routing-suite★ 6997
一个仓库三件套:DSH 插件包的运行时注入器(注入、热重载、卸载、开发侧挂区一键转正、路由自愈,外带设置页插件管理:列出、卸载、拖入文件夹内化)、任务感知的思维模式路由 agent 预设(router-standard / router-spec / router-react)、以及分级两级任务协议(commit_star / lock_stage / revise_do / edit_plan / mark_task / redteam_verdict 六个工具,任务状态落盘)。注入器实现直接在库内,安装的是它自己的行为而不是一份依赖清单。
strukto-ai/mirage#dsh★ 3696
把文件系统与 bash 提供者换成 mirage 虚拟工作区:文件工具与 shell 命令作用于挂载的资源(RAM、S3、Redis、Slack、Gmail、Notion、Postgres)而非宿主磁盘,支持按挂载点设置读/写/执行模式、按命令选择沙箱(进程内 monty、pyodide、quickjs;远程 docker、e2b、daytona),并可在虚拟终端中安装 CLI(git、gh、slack、linear、ntn、gws,或自行注册的程序树)作为命令头词。
first-fluke/oh-my-agent#dsh★ 1344
在工具执行前后以及尝试结束当前轮次时运行项目 OMA 检查。随插件安装 OMA CLI 依赖,并在首次使用时初始化未配置的项目。需要 Node.js 22+;首次初始化需要网络连接和项目目录写入权限。
hust-open-atom-club/oh-dsh★ 322
社区发行版:TUI、桌面端与 Web UI 统一体验,分层安装、一步到位。
weijiafu14/pi2dsh★ 217
Pi Host ABI 兼容引擎:装一次之后,npm 上的 Pi 扩展原包经 `dsh plugin add <pi-package>` 直接作为 DSH 原生插件挂载。已在官方 DSH 上端到端验证 pi-mcp-adapter(完整 MCP 管理面:OAuth、resources、prompts、MCP Apps、elicitation、sampling)、@tintinweb/pi-subagents、pi-code、pi-hermes-memory、pi-background-tasks;`pi2dsh inspect` 在安装前报告一个包的兼容情况。
Fishquito7/dsh-skill-mcp-panel★ 207
在 DSH Web 设置中管理技能与 MCP 服务器:技能卡片热启停、工作区作用域、分组、批量迁移与拖拽导入,以及 stdio/HTTP MCP 增删改查、连接测试、密钥脱敏,并附带统一 dsh-panel 命令行。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。