dsh-permission-rules

25Star2Fork3Issue0Watching

在 DSH 工具调用前立一道 YAML 规则闸门,按工具名/参数/路径/Agent/网络目标产生 allow/deny/ask;本地代理管控出网,全程只读审计。

语言
TypeScript
License
Apache-2.0
分支
main
ai-safetyallow-deny-askapprovalcordisdeepseekdeepseek-harnessdshdsh-plugin

安装

$ dsh plugin --profile web add github:PerryLink/dsh-permission-rules

在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程

一句话定位

在 DeepSeek Harness 的每个工具调用前面立一道 YAML 写成的允许/拒绝/询问闸门,按工具名、参数、路径、Agent 身份、网络目标做匹配;叠加一个本地 HTTP/CONNECT 代理统一管控子进程出网,所有命中都写只读审计日志,但模型看不到也不被阻断以外的副作用干扰。

核心能力

  • 有序规则链:按文件顺序对每条工具调用做"首匹配胜"评估,命中规则就产出 allow/deny/ask 三种动作;deny/ask 截断后续,allow 与无匹配严格 next() 放行,绝不阻断下游。
  • 丰富匹配维度:工具名 glob(支持 mcp__*)、Agent 身份选择器(main/subagent/preset:<name>,未知身份永不匹配——失败兜底)、参数键值 glob 或 regex(含 !pattern 否定与 absent 缺失维度)、工作区相对路径任意嵌套、when 主机条件(环境变量 + 平台白名单)。
  • 进程级网络策略:内置本地 HTTP/CONNECT 代理统一管控 shell 子进程出网,规则上可以用 match.networkdomains/ips/ports/schemes(glob、通配、CIDR、端口范围)做目标匹配;三种网络模式自动跟随官方沙箱预设(只读→全禁、工作区写入→白名单、danger-full-access→全放)。
  • 热重载与层次文件:Chokidar 监听规则文件改动,带防抖;编辑坏了保留旧规则不崩;可选 searchUp 把父目录的 .dsh/rules.yaml 一并合并(近的优先)。
  • 完整只读审计:每次命中和每次放行都写入 permissionRules/decision 会话事件(带 ignorable 标记),代理拦截另写 permissionRules/network;日志不进入模型上下文,仅供事后回溯。
  • 可视化设置面板:在 Web profile 增加一个"Permission Rules"设置页,展示网络模式摘要、拦截计数、最近拦截列表和规则编辑器,所有数据通过 Typert 远程接口获取。

技术实现

  • 语言: TypeScript(ESM,strict 模式;客户端 React 18)
  • 关键依赖: @deepseek-ai/cordis + @deepseek-ai/schemastery(插件加载与 Config 校验)、chokidar(规则文件监听)、yaml(规则文件解析)、react(Web 设置面板)
  • 架构模式: 函数式 cordis 插件(name/inject/Config/apply,无默认导出);apply 注册 tools/pre-execute 监听器、/rules 命令、设置 section、本地代理;Web 半包通过 Typert 远程接口与 host 通信。规则匹配为纯函数(无 I/O、可回放、可单测)。
  • 入口文件: src/index.ts(host 入口)、src/client/index.ts(Web 设置面板入口)、src/runtime.ts(核心装配与 apply)、cordis.patch.yml(profile bundle patch)

适用场景

适合需要给 AI Agent 工具调用加上"白/黑/灰名单"的项目:比如保护 secrets/.env 不被任意编辑、限定网络出站只能命中受信任 API、要求敏感操作走二次审批。典型用户是希望在不写代码的前提下、用纯 YAML 配置文件就给一个会话乃至整个组织加一道可审计、可热更新、可追溯规则来源的安全护栏的个人或团队。

前置依赖与兼容性

依赖最低版本说明
DeepSeek Harness>=0.1.0-rc.8 <0.2.0所有 dsh-* peerDependencies 与 devDependencies 锁 rc.8;workshop 清单声明兼容 rc.5/rc.7/rc.8;rc.8 起 Session.append 才会盖上 ignorable 标记
Node.js^22.19.0 || >=24.0.0package.json#engines.node 强制
平台跨平台host 端纯 JS(无原生模块);Web 设置面板跑在浏览器(web profile);caseInsensitivePaths 在 Windows 上默认开
原生模块不依赖 node-pty/node:sqlite 等原生接口

安装方式

dsh plugin --profile web add github:PerryLink/dsh-permission-rules

配置项

配置类型说明默认值
rulesFile字符串规则文件位置,相对路径按会话 cwd 解析,绝对路径全局生效.dsh/rules.yaml
fallbackPath字符串工作区下找不到规则时用的兜底文件,绝对路径或相对 process.cwd()未设置
badFilePolicy枚举规则文件解析/编译失败时的处理:fail 让当前调用直接报错;ignore-with-warning 警告并按空规则继续fail
maxRules整数整个有效规则链的最大条数,超过则装载失败256
maxCachedWorkspaces整数缓存的工作区规则加载数(LRU 淘汰)512
patternMode枚举params/paths/when.env 的匹配语法:globregexglob
watch布尔是否监听规则文件改动并自动重载true
watchStabilityThresholdMs整数监听重载防抖窗口(毫秒)200
language枚举/rules 命令输出语言:en/zh/es/pt/hien
caseInsensitivePaths布尔paths 与工作区根比对是否忽略 ASCII 大小写Windows 上自动 true
audit枚举审计粒度:all 记录命中与放行;hits 只记录命中all
searchUp布尔是否向上遍历父目录合并所有 .dsh/rules.yaml(近的优先)false
maxGlobStars整数单条 glob 中无界 */** 数量的上限(回溯度防护)2
enforce布尔是否真正阻断:false 进入 dry-run,所有命中只写审计不阻断true
allowUnmarkedAudit布尔在不识别 ignorable 标记的旧 host 上仍写入审计(默认会降级为不写以保会话可恢复)false
network.enabled布尔网络策略总开关,控制代理、子进程环境变量注入与 Web 工具模式默认true
network.mode枚举网络策略模式:auto 跟随沙箱预设,或固定 deny-all/whitelist/allow-allauto
network.autoFallback枚举auto 模式在找不到沙箱服务时使用的兜底模式allow-all
network.unlisted枚举whitelist 模式下未匹配目标的处理:ask(默认)或 denyask
network.proxyBind字符串本地代理绑定地址(仅回环地址,不会公开监听)127.0.0.1
network.proxyPort整数本地代理端口;0 表示自动分配空闲临时端口0
network.proxyMaxRecent整数设置页"最近拦截"列表保留条数100
network.loopback枚举回环目标处理:allow(与 Codex 一致,默认放行本地开发服务)或 policy(按规则评估)allow
network.injectEnv布尔是否为子进程注入 HTTP_PROXY 等代理环境变量true
network.noProxy枚举子进程 NO_PROXY 处理:clear 强制策略生效,preserve 保留原有clear

常见问题

Q: 安装后规则还没生效?

A: 装上只是把插件挂到 profile 里,必须重启 profile 才会出现 id: permission-rules 行;之后还要在工作目录放一份 .dsh/rules.yaml(或通过 fallbackPath 配置兜底文件),没规则就等同于"全部放行"。

Q: 跟 dsh-auto-review 是替代关系还是互补?

A: 互补。本插件产出 ask 走审批通道,dsh-auto-review 在审批通道上挂第二个 AI 模型做裁决;只装本插件时 ask 走人类或 host 默认行为,两个一起装才形成"规则筛选 + 模型复核"的闭环。

Q: 想先观察新策略会不会误伤怎么办?

A: 在 cordis.ymlenforce 临时设成 false 进入 dry-run:所有 deny/ask 命中只写带 dryRun 标记的审计日志,不阻断调用,事件照常流向下游;观察完再切回 true

Q: 写错的 YAML 会怎样?

A: 默认 badFilePolicy: 'fail',等待执行的工具调用直接报错,HMR 重载场景下保留上一次成功加载的规则、绝不崩;想要宽容一点,改成 ignore-with-warning 即可警告并按空规则继续。

Q: 网络策略默认怎么走?要怎么改?

A: 默认 network.enabled: truenetwork.mode: 'auto',跟随官方沙箱预设(只读→全禁、工作区写入→白名单、danger-full-access→全放),host 没沙箱服务时回落到 autoFallback(默认 allow-all)。想固定就把 mode 显式写成 deny-all/whitelist/allow-all 之一。

Q: 装了为什么旧 host 上看不到审计事件?

A: 0.1.0-rc.6 及之前的 host 会丢弃 ignorable 标记,导致审计事件不带标记、破坏会话在新 host 上的恢复。插件会主动降级为不开会话日志审计并打印一次性警告;想保留就把 allowUnmarkedAudit: true 设上,但旧日志可能需要跑一次 scripts/repair-session-logs.mjs repair 修一遍。

Q: 怎么查看当前生效的规则和最近决策?

A: 在会话里跑 /rules 列出全部规则与来源文件,/rules reload 强制重载当前工作区的规则链,/rules decisions [n] 看最近 n 条本会话决策(默认 10),/rules test <tool> <json> 还能不真触发工具调用就干跑一次规则比对。

Q: 怎么卸载?

A: 一行 dsh plugin --profile web remove dsh-permission-rules 即可,事务式卸载,不影响 profile 之外的其它插件或会话状态。

上手难度

入门 — 配置以纯 YAML 为主,配置文件存放在工作区根目录即可立即生效;遇到复杂匹配维度(agent 选择器、CIDR、回溯防护)再回头查文档。

已知问题与限制

  • 旧 host 的审计降级0.1.0-rc.10.1.0-rc.7 系列的 Session.append 不识别 ignorable 标记,插件主动降级为不写会话日志审计并打印一次性警告;保留审计需设 allowUnmarkedAudit: true,并接受旧会话在新 host 上可能需要 scripts/repair-session-logs.mjs 修复。
  • 路径候选是启发式的:只有文档列举的参数键(url/path/file_path 等)参与路径匹配,且仅匹配工作区相对路径;非约定键、绝对路径之外的引用不会被规则覆盖。
  • Glob 是保守子集:不支持花括号展开,需要写两条规则或切到 regex 模式;maxGlobStars(默认 2)会拒绝过多无界星号的 glob 以防回溯。
  • Regex 回溯防护是结构性的而非穷举的:能挡住常见的灾难模式(嵌套无界量词、量化字面量重叠交替),但不是完整的 ReDoS 检测,不受信文件建议留在 glob 模式。
  • 代理层 ask 不能进入交互式审批通道:本地代理没有会话上下文,遇到需要审批的目标只能以结构化消息拦截并写 permissionRules/network 审计,模型通过 shell 工具的错误输出看到原因;只有 Web 工具的 ask 走真正的交互审批。