# dsh-permission-rules

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

## Metadata

- Author: [@PerryLink](https://github.com/PerryLink)
- Repo: <https://github.com/PerryLink/dsh-permission-rules.git>
- GitHub: [PerryLink/dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)
- Stars: 25
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Homepage: <https://www.npmjs.com/package/dsh-permission-rules>
- Topics: `ai-safety`, `allow-deny-ask`, `approval`, `cordis`, `deepseek`, `deepseek-harness`, `dsh`, `dsh-plugin`, `permission`, `policy`, `safety`
- Forks: 2
- Open Issues: 3
- Last push: 2026-08-21T05:53:38.000Z
- Added: 2026-08-17T00:00:00.000Z

## Install

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

## Wiki

## 一句话定位
在 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.network` 的 `domains`/`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.0` | `package.json#engines.node` 强制 |
| 平台 | 跨平台 | host 端纯 JS（无原生模块）；Web 设置面板跑在浏览器（web profile）；`caseInsensitivePaths` 在 Windows 上默认开 |
| 原生模块 | 无 | 不依赖 `node-pty`/`node:sqlite` 等原生接口 |

## 安装方式
```bash
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` 的匹配语法：`glob` 或 `regex` | `glob` |
| `watch` | 布尔 | 是否监听规则文件改动并自动重载 | `true` |
| `watchStabilityThresholdMs` | 整数 | 监听重载防抖窗口（毫秒） | `200` |
| `language` | 枚举 | `/rules` 命令输出语言：`en`/`zh`/`es`/`pt`/`hi` | `en` |
| `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-all` | `auto` |
| `network.autoFallback` | 枚举 | `auto` 模式在找不到沙箱服务时使用的兜底模式 | `allow-all` |
| `network.unlisted` | 枚举 | `whitelist` 模式下未匹配目标的处理：`ask`（默认）或 `deny` | `ask` |
| `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.yml` 把 `enforce` 临时设成 `false` 进入 dry-run：所有 `deny`/`ask` 命中只写带 `dryRun` 标记的审计日志，不阻断调用，事件照常流向下游；观察完再切回 `true`。

**Q: 写错的 YAML 会怎样？**

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

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

A: 默认 `network.enabled: true` 且 `network.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.1`–`0.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` 走真正的交互审批。

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-permission-rules](https://deepseek-plugin.org/plugins/PerryLink/dsh-permission-rules)
Wiki generated by AI (model: `MiniMax-M3`)
