前言¶
DeepSeek Harness(DSH)在本地运行时,需要控制 agent 对文件系统的访问边界。常见做法是配置 allow/deny 规则(dsh-permission-rules、dsh-permissions 等),按工具/命令/参数来限制。这类规则引擎回答的是「这次调用准不准」,并不直接回答「这个目录开不开」。
dsh-isolation-gate 提供的是另一个视角:以目录为对象,管理「这台机器上哪些目录属于受管范围,各自允许什么操作级别」。它默认禁用、只对名单内的对象生效,支持分组批量切换、审计日志与 agent 自查。它的核心是管理面,拦截点落在 DSH 的工具调用(ctx.tools.guard)和显式通道(bin/gate.mjs)两层。
功能概览¶
三级访问控制¶
deny:禁用(默认)read:只读full:可读写删
两类受管对象¶
- 名字对象:D 根一级目录名,如
Zcode - 路径对象:任意绝对路径,如
<drive-root>\Zcode、E:\资料\合作档案\某项目
默认禁 + 名单制¶
- 只有名单内的对象受管
- 未列出的不归它管
- 新增对象一律先按「禁」处理
一屏开关与分组¶
- 每个对象一个分段控件(禁 / 只读 / 可读写删),点击即落盘,重启后保持
- 支持命名组(如「harness 隔离」「跨家合作」),组头一键切三档
- 组只存在于状态文件里,名单仍是「一行一对象」
目录选择器¶
- 有原生目录选择器时优先使用
- 无原生选择器时退回「未入名单的一层目录一键加入 + 文本框」
两个拦截点¶
- 受管辖通道:
bin/gate.mjs(agent 显式走它读写受管对象) - 宿主工具执行门:
ctx.tools.guard(2026-10-21 新增)
审计与自查¶
- 审计日志:
~/.dsh/dsh-isolation-gate/access-log.jsonl,只记时间/对象/级别/操作/允许与否/理由,不记文件内容 - agent 自查:
node bin/gate.mjs status,模型/脚本可直接读策略
安装¶
推荐方式¶
dsh plugin --profile desktop add dsh-isolation-gate
桌面版也可以通过「插件」页的「添加插件」填写包名或本地路径。
开发/自用方式(profile link)¶
// ~/.dsh/profiles/<profile>/package.json
"dependencies": { "dsh-isolation-gate": "link:/absolute/path/to/dsh-isolation-gate" }
# ~/.dsh/profiles/<profile>/cordis.patch.yml
- insert:
- id: isolation-gate
name: 'dsh-isolation-gate'
改装配需要重启 dsh;开关本身不需要重启。
零配置默认值¶
| 配置 | 默认 | 说明 |
|---|---|---|
statePath |
<data-root>/isolation-gate/state.json |
状态唯一真身 |
rosterPath |
<data-root>/isolation-gate/roster.txt |
名单(一行一对象) |
backupDir |
<backup-dir> |
改名单前备份 |
root |
<drive-root>\ |
列「一层目录」的根 |
auditPath |
~\.dsh\dsh-isolation-gate\access-log.jsonl |
审计 |
toolGate |
true |
关掉就只剩通道层 |
suspectVocabulary |
['pi','codex',…] 内置 |
追加「疑似受管目录」名谱(旧键 harnessVocabulary 仍读) |
状态文件(v3)¶
{
"version": 3,
"defaultLevel": "deny",
"groups": { "harness 隔离": { "note": "", "updatedAt": "…" } },
"objects": {
"Zcode": { "level": "deny", "note": "", "group": "harness 隔离", "updatedAt": "…" },
"E:\\资料\\合作档案\\某项目": { "level": "read", "note": "", "group": "", "updatedAt": "…" }
},
"profiles": { "严格交付": { "mode": "allowlist", "allow": [{ "path": "E:\\资料\\库", "level": "read" }], "network": "deny", "unknownTool": "deny" } },
"sessionOverrides": { "<sessionId>": "严格交付" }
}
- 旧格式仍可读:v1 的
harnesses: { key: { level } }与 v2 的objects读入时自动收敛成 v3,级别不丢;profiles/sessionOverrides缺省为空;下一次写盘才落成 v3 - 文件缺失 = 全禁;坏 JSON = 全禁且不覆盖原文件
- 无 BOM;写盘走临时文件 + rename(原子替换)
state.harnesses在内存里保留为只读别名,磁盘上不再写它
判定语义¶
- 给路径时先算真身路径(目标 realpath,或「最深已存在祖先 realpath + 剩余片段」),junction 绕进来也会被认出来
- 在路径对象里找最长前缀命中(按路径段比较、忽略大小写,
<drive-root>\a\b不会命中<drive-root>\a\bc) - 没命中路径对象 ⇒ 退回「盘根一级目录名」匹配名字对象
- 都不中 ⇒ 不属受管对象,本通道放行(它只管辖名单内的东西)
- 名单缺失 / 读坏 ⇒ 一律拒绝(fail-closed)
工具执行门(ctx.tools.guard)¶
覆盖的工具包括:read / read_image / grep / glob / write / edit / str_replace_editor / apply_patch / mkdir / move / copy / delete / bash / pwsh / …
- shell 命令会做词法分解:
cd+ 相对路径、重定向、管道、bash -c "…"都会跟进去 - 未知命令只要命令文本里出现受管绝对路径,就按写处理(只在
full放行)——宁可误拒,不可漏放 - 判定自身抛错 ⇒ fail-closed(按拒绝处理)
bin/gate.mjs自身的调用豁免工具门(它自己会判定,否则会双重拦截)
CLI(agent / 脚本用)¶
node bin/gate.mjs status # 全部级别
node bin/gate.mjs policy # 紧凑策略行(可塞进提示词)
node bin/gate.mjs check --name Zcode --op read
node bin/gate.mjs check --path "<drive-root>\Zcode\某文件" --op delete
node bin/gate.mjs traverse --path "<drive-root>\Zcode"
node bin/gate.mjs read --path "<drive-root>\Zcode\某文件"
node bin/gate.mjs write --path "<drive-root>\Zcode\某文件" --text "内容"
node bin/gate.mjs delete --path "<drive-root>\Zcode\某文件" # 删前自动备份到回收站
node bin/gate.mjs rmdir --path "<drive-root>\Zcode\空目录" # 只删空目录(不递归)
node bin/gate.mjs log --limit 20
退出码:0 放行 / 成功 · 3 被拒(拒绝文案在 stderr)· 2 用错或环境错误。
护栏:判定在 I/O 之前;只删文件不删目录;删除前自动备份,备份失败即中止;名单不可用一律拒绝。
HTTP 路由(宿主半 · 本机同源)¶
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | /__dsh-isolation-gate/ping |
身份 + 路由清单 + 能力(toolGate / picker) |
| GET | /__dsh-isolation-gate/state |
全量状态(名单 / 级别 / 分组 / 差异 / 审计 / 策略行) |
| POST | /__dsh-isolation-gate/level |
{ name, level } 改一个对象 |
| POST | /__dsh-isolation-gate/roster |
{ action:'add'\|'remove', name } 名单增删 |
| POST | /__dsh-isolation-gate/group-level |
{ group, level } 组级一键切 |
| POST | /__dsh-isolation-gate/group |
{ action:'assign'\|'delete', name?, group } |
| POST | /__dsh-isolation-gate/prune |
清理孤儿键 |
| POST | /__dsh-isolation-gate/pick |
调原生目录选择器(无则 400,面板自动走退路) |
| POST | /__dsh-isolation-gate/toolcheck |
{ tool, args } 工具门干跑(不执行工具) |
| POST | /__dsh-isolation-gate/check |
{ name?\|path?, op } 判定(不读内容) |
| GET | /__dsh-isolation-gate/log?limit= |
最近审计 |
带外部 Origin 的请求一律 403;错误方法 405;坏 JSON / 非法级别 400。
与其它插件的分工¶
| 插件 | 它管什么 | 与本插件 |
|---|---|---|
dsh-house-guard(本机内建) |
体系资产:固定几个受保护路径,只有白名单管家会话可写 | 目的不同、对象不重叠;两边都拒则拒(更严者胜) |
PerryLink/dsh-permission-rules |
规则引擎:allow/deny/ask + 工具参数/shell/网络 | 互补:它没有「只读」这一档;本插件面板不写它的 YAML,不依赖它 |
cherrchen/dsh-plugin-multi-root-workspace |
给工作区加附加根(只读/读写),靠内核沙箱执行 | 互补:它是「扩权」,本插件是「划禁区」;它强在拦截强度,本插件强在管理面与分组 |
azazo1/dsh-write-protect |
工作区内只读保护 + 额外可写根 | 互补;Windows 上它的 bash 侧有明确缺口 |
本插件不声明任何 @deepseek-ai/dsh* peer 依赖 —— dsh 升级不会因「版本豁免失效」被静默禁用。
1.0 的稳定性承诺¶
从 1.0 起,以下只增不破;要破坏会走 2.0 并附迁移说明:
| 面 | 承诺 |
|---|---|
| 状态文件 | 磁盘格式 v3;v1 / v2 一律读时兼容、写时升级,级别不丢;新增字段只追加,不改已有字段含义 |
| CLI | bin/gate.mjs 的子命令与退出码(0 放行 / 3 拒绝 / 2 用错或环境错误)不改;新参数只增加 |
| HTTP | /__dsh-isolation-gate/* 现有路径与方法不改;新路由只增加 |
| 判定语义 | 三级(deny / read / full)、最长前缀命中、默认禁、fail-closed —— 这四项冻结;改它们等于换产品 |
不承诺:面板的视觉与文案、审计日志的字段可以随版本演进;修 bug 不算破坏性变更。
已知边界¶
- raw fs 与独立进程拦不住。任何程序、脚本、别的 agent 不经过 DSH 工具调用就直接读写磁盘,本插件看不到。它拦的是经过 DSH 的工具调用与走
bin/gate.mjs的通道 - shell 是文本启发式。变量间接、命令替换、编码、符号链接、
cmd /c套一层等都可能绕过;与同类实现同一层级的已知限制。要更强,需要内核沙箱(Windows ACL / Seatbelt / bwrap) - 不是内核级闸门。它不修改任何系统 ACL、不接管沙箱
- 工具门只覆盖宿主工具。MCP 工具如果自己开进程读文件,同样只受其参数文本约束
适用场景与注意¶
适合需要以目录为单位管理访问边界的 DSH 用户,特别是需要在「只读」与「读写删」之间精细控制的场景。
插件以当前 dsh 进程权限运行,安装前应检查源码与许可证。
小结¶
dsh-isolation-gate 提供了给 DSH 的目录访问门禁:默认禁、名单制、一屏开关、分组切换、审计与 agent 自查。它按「目录」立对象,与按工具/命令立规则的插件分工互补。
- 目录页:https://www.skillhub.cn/plugins/zhaorui-wu/dsh-isolation-gate
- GitHub:https://github.com/zhaorui-wu/dsh-isolation-gate