dsh-permission-rules 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.es.md +139 -118
- package/README.hi.md +141 -120
- package/README.md +111 -90
- package/README.pt.md +140 -119
- package/README.zh.md +158 -137
- package/docs/rule-test-vectors/README.md +28 -0
- package/docs/rule-test-vectors/corpus.json +109 -0
- package/lib/client.js +4 -7
- package/lib/client.js.map +1 -1
- package/lib/index.js +10 -7
- package/lib/types/client/remote.d.ts +1 -1
- package/lib/types/client/section.d.ts +2 -2
- package/lib/types/config.d.ts +3 -3
- package/lib/types/index.d.ts +8 -8
- package/lib/types/network.d.ts +2 -2
- package/lib/types/proxy.d.ts +2 -2
- package/lib/types/remote-service.d.ts +2 -2
- package/lib/types/runtime.d.ts +14 -11
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/settings.d.ts +2 -2
- package/package.json +182 -178
- package/scripts/repair-session-logs.mjs +7 -2
- package/src/client/index.ts +5 -5
- package/src/runtime.ts +10 -7
package/README.zh.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# 🛡️ dsh-permission-rules
|
|
4
4
|
|
|
5
|
-
**DeepSeek Harness 的 Claude Code
|
|
5
|
+
**DeepSeek Harness 的 Claude Code 风格声明式权限规则。**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
*规则裁决已知的。评审模型裁决未知的。*
|
|
8
8
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://github.com/topics/dsh-plugin)
|
|
@@ -20,197 +20,218 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Compatibility
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
每次命中**和**每次透传都会以 `permissionRules/decision` 会话事件审计落盘(log-only,不向模型上下文注入任何额外内容)。
|
|
32
|
-
|
|
33
|
-
```text
|
|
34
|
-
tools/pre-execute waterfall approval/request waterfall(answerer 链)
|
|
35
|
-
│ │
|
|
36
|
-
dsh-permission-rules dsh-auto-review answerer
|
|
37
|
-
· 按文件顺序首条命中 ┌───────────────┴──────────────┐
|
|
38
|
-
· deny/ask 认领调用 │ 第二模型裁决 │ 否 ── next() ──▶ 人类 UI
|
|
39
|
-
· allow/透传 → next() └───────────────┬──────────────┘
|
|
40
|
-
│ deny ──▶ 被拒工具结果 │ allowed-once / rejected
|
|
41
|
-
│ ask ──▶ ctx.approval ────────────────────────┘
|
|
42
|
-
│
|
|
43
|
-
审计:permissionRules/decision → approval/asked → autoReview/verdict → approval/decided
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## 为什么既要有规则又要有 reviewer?
|
|
47
|
-
|
|
48
|
-
第二模型回答*"这次调用行不行"*有判断力,但每次都要一次模型往返,也可能判错。声明式规则确定性、即时、不跑模型——但只能覆盖管理员写下来的情形。两者组合就是 **"规则先行、AI 兜底"** 闭环:规则裁决已知,reviewer 裁决未知。
|
|
49
|
-
|
|
50
|
-
## 特性
|
|
25
|
+
| Surface | Status |
|
|
26
|
+
|---|---|
|
|
27
|
+
| Harness | DeepSeek Harness `0.1.0-rc.5`–`0.1.0-rc.6` |
|
|
28
|
+
| Node | `^22.19.0 || >=24.0.0` |
|
|
29
|
+
| Platforms | 全部(host + Web 设置客户端) |
|
|
30
|
+
| Model | 任意(deny/ask 原因经工具结果呈现) |
|
|
51
31
|
|
|
52
|
-
|
|
53
|
-
- ✅ **丰富匹配** — 工具名 glob(含 `mcp__*`)、代理身份选择器(`main` / `subagent` / `preset:*`)、参数键值 glob **或** 正则(支持 `!pattern` 否定与 `absent` 键维度)、工作区相对路径 glob(从文档化参数键在**任意嵌套深度**提取候选),以及 `when` 宿主条件(环境变量、平台)
|
|
54
|
-
- ✅ **分层规则文件** — 可选 `searchUp` 从会话 cwd 向上逐级合并每份 `.dsh/rules.yaml`(最近的优先),子项目可覆盖父级规则
|
|
55
|
-
- ✅ **规则元数据** — `enabled: false`、`description`、`tags`;`/rules` 会告警被前面通配规则遮蔽的规则
|
|
56
|
-
- ✅ **waterfall 安全** — `allow`/透传一律 `next()`;只有 `deny`/`ask` 短路
|
|
57
|
-
- ✅ **官方审批 seam** — `ask` 走 `ctx.approval`;不重复实现、绝不绕过
|
|
58
|
-
- ✅ **完整审计** — `permissionRules/decision` 事件记录规则动作、工作区 cwd 与最终裁决;`/rules decisions` 可在会话内回放审计轨迹;早于审计信封标记的宿主自动降级为关闭审计并一次性警告,绝不写出无法恢复的日志(`allowUnmarkedAudit` 可重新开启)
|
|
59
|
-
- ✅ **干跑上线** — `enforce: false` 只审计策略「本会做什么」(记录本会动作 + 下游真实结果,带 `dryRun` 标记),所有调用照常透传;可在生产环境安全试跑新策略
|
|
60
|
-
- ✅ **干跑测试** — `/rules test <工具> <json-参数>` 不执行任何东西地评估规则命中,支持 `--cwd`、`--env`、`--agent`、`--platform` 覆盖每个匹配维度
|
|
61
|
-
- ✅ **热更新** — Chokidar 监视 + 去抖;改坏了保留旧规则,绝不崩溃;会话中途创建的规则文件(项目文件或 fallback)自动生效,无需手动重载
|
|
62
|
-
- ✅ **响亮失败** — 非法 YAML、未知字段/action、坏 glob/正则、易回溯灾难的模式、超过 `maxRules` 都使加载失败
|
|
63
|
-
- ✅ **热路径有界** — 预编译匹配器,O(规则数 × 模式数),`maxRules` 封顶;glob 回溯度由 `maxGlobStars` 封顶
|
|
32
|
+
## What you get
|
|
64
33
|
|
|
65
|
-
|
|
34
|
+
`dsh-permission-rules` 在 `tools/pre-execute` waterfall 上为每次工具调用前置一个有序的 **`allow` / `deny` / `ask`** 规则列表——确定、即时、可审计,由你用纯 YAML 编写:
|
|
66
35
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
36
|
+
- **`deny`** 阻断调用;规则的 `reason` 成为模型可见的错误。
|
|
37
|
+
- **`ask`** 走官方审批接缝(挂载 `dsh-auto-review` 用第二模型作答,或由人作答;两者皆无时 harness 失败关闭)。
|
|
38
|
+
- **`allow`**(及未命中)严格经 `next()` 委托——下游监听器绝不会被短路。
|
|
70
39
|
|
|
71
|
-
|
|
72
|
-
pnpm pack
|
|
73
|
-
dsh plugin --profile web add ./dsh-permission-rules-0.4.1.tgz
|
|
40
|
+
每次命中**和**每次透传都作为 `permissionRules/decision` 会话事件审计落盘(仅日志——不会向模型上下文额外注入任何内容)。
|
|
74
41
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
42
|
+
- **丰富匹配** —— 工具名 glob(含 `mcp__*`)、agent 身份选择器(`main` / `subagent` / `preset:*`)、参数键/值 glob **或** 正则(含 `!pattern` 取反与 `absent` 键维度)、**任意嵌套深度**的工作区相对路径 glob,以及 `when` 宿主条件(环境变量、平台)。
|
|
43
|
+
- **分层规则文件** —— 可选 `searchUp` 从会话 cwd 到文件系统根合并每个 `.dsh/rules.yaml`,就近优先。
|
|
44
|
+
- **试运行上线** —— `enforce: false` 审计策略*会*做什么,同时放行每个调用。
|
|
45
|
+
- **热重载** —— Chokidar 监听带去抖;损坏的编辑保留旧规则、绝不崩溃。
|
|
46
|
+
- **失败大声** —— 非法 YAML、未知 action/字段、坏 glob/正则、易回溯模式或超过 `maxRules` 的规则在加载期失败。
|
|
78
47
|
|
|
79
|
-
|
|
48
|
+
## Rule syntax
|
|
80
49
|
|
|
81
50
|
```yaml
|
|
82
51
|
# <project>/.dsh/rules.yaml
|
|
83
52
|
rules:
|
|
84
53
|
- match: { tools: [bash, pwsh], params: { command: "git push*" }, paths: ["**/secrets/**"] }
|
|
85
54
|
action: deny
|
|
86
|
-
reason: "
|
|
55
|
+
reason: "No pushes from protected paths"
|
|
87
56
|
|
|
88
57
|
- match: { tools: [edit, write] }
|
|
89
58
|
action: ask
|
|
90
|
-
reason: "
|
|
59
|
+
reason: "File writes need confirmation"
|
|
91
60
|
```
|
|
92
61
|
|
|
62
|
+
- **匹配维度** —— `tools`(glob,含 `mcp__*`)、`agents`(`main` / `subagent` / `preset:<name>`;未知身份永不匹配——失败关闭)、`params`(键/值 glob 或正则,`!pattern` 取反,`absent` 键维度)、`paths`(任意嵌套深度抽取的工作区相对 glob)、`when`(`env` 变量 glob/正则 + 封闭 `platform` 列表),以及 `network`(`domains` / `ips` / `ports` / `schemes`——glob、通配符、CIDR、端口范围)。
|
|
63
|
+
- **动作** —— `allow` / `deny` / `ask`,按文件顺序求值,首个命中胜出。
|
|
64
|
+
- **规则元数据** —— `enabled: false`(可见但失效)、`description`、`tags`;未知字段加载失败。
|
|
65
|
+
- **Schema** —— JSON Schema 见 [docs/rules-format.schema.json](docs/rules-format.schema.json)(编辑器补全 `# yaml-language-server: $schema=...`);完整词汇表与 5 条安全基线见 [docs/rules-format.en.md](docs/rules-format.en.md)。
|
|
66
|
+
|
|
67
|
+
## Network policy
|
|
68
|
+
|
|
69
|
+
Codex 风格的**进程级网络策略**:shell 子进程流量经内置本地 **HTTP/CONNECT 代理**,每个连接由有序网络规则或映射到官方沙箱预设的三档模式裁决:
|
|
70
|
+
|
|
71
|
+
- **`deny-all`** —— 只读沙箱预设:阻断所有出站。
|
|
72
|
+
- **`whitelist`** —— workspace-write 预设:放行列表目标,其余按 `unlisted: ask`(或 `deny`)。
|
|
73
|
+
- **`allow-all`** —— danger-full-access 预设:放行一切。
|
|
74
|
+
- **`auto`**(默认)—— 跟随沙箱预设;无沙箱策略服务的宿主上解析为 `autoFallback`(`allow-all`)。
|
|
75
|
+
|
|
76
|
+
- **匹配** —— `match.network` 用 `domains` / `ips` / `ports` / `schemes`(glob、通配符、CIDR、端口范围;数值型 YAML 端口可接受)。`tools/pre-execute` 热路径上的 URL 候选抽取作用于 web 工具参数与嵌入 bash/pwsh 命令文本的 URL;回环目标可按 `loopback` 策略短路规则。
|
|
77
|
+
- **审计** —— 被拒连接向所属会话追加 `permissionRules/network`(同样的自适应 `ignorable` 门),块计数器与近期拦截在 `/rules network` 与设置页展示。
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
93
81
|
```sh
|
|
94
|
-
|
|
82
|
+
# 1. install the bundle into your profile
|
|
83
|
+
dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#main"
|
|
84
|
+
|
|
85
|
+
# or from npm (published releases)
|
|
86
|
+
dsh plugin --profile web add dsh-permission-rules
|
|
87
|
+
|
|
88
|
+
# 2. restart and verify the row
|
|
89
|
+
dsh --profile web --dump-config | grep -A4 'id: permission-rules'
|
|
95
90
|
```
|
|
96
91
|
|
|
97
|
-
|
|
92
|
+
## Install & uninstall
|
|
98
93
|
|
|
99
|
-
|
|
94
|
+
- **git channel**(最新 `main`):`dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#main"` —— `prepare` 脚本仅用生产依赖构建。
|
|
95
|
+
- **npm channel**(发布版本):`dsh plugin --profile web add dsh-permission-rules`。
|
|
96
|
+
- **tarball channel**:在本仓库执行 `pnpm pack`,然后 `dsh plugin --profile web add ./dsh-permission-rules-<version>.tgz`。
|
|
97
|
+
- **uninstall**:`dsh plugin --profile web remove dsh-permission-rules`。
|
|
100
98
|
|
|
101
|
-
|
|
99
|
+
## Configuration
|
|
102
100
|
|
|
103
|
-
|
|
101
|
+
所有可调项均为 Schemastery `Config` 字段(可在 cordis.yml 中修改)。按 id 覆盖会整行替换——重述你需要的每个键。
|
|
102
|
+
|
|
103
|
+
| Key | Default | Meaning |
|
|
104
104
|
|---|---|---|
|
|
105
|
-
| `rulesFile` | `.dsh/rules.yaml` |
|
|
106
|
-
| `fallbackPath` |
|
|
107
|
-
| `badFilePolicy` | `fail` |
|
|
108
|
-
| `maxRules` | `256` |
|
|
109
|
-
| `maxCachedWorkspaces` | `512` |
|
|
110
|
-
| `patternMode` | `glob` | `params`/`paths`/`when.env`
|
|
111
|
-
| `watch` | `true` | Chokidar
|
|
112
|
-
| `watchStabilityThresholdMs` | `200` |
|
|
113
|
-
| `language` | `en` | `/rules` 输出语言:`en`、`zh`、`es`、`pt`、`hi
|
|
114
|
-
| `caseInsensitivePaths` |
|
|
105
|
+
| `rulesFile` | `.dsh/rules.yaml` | 规则文件位置;相对 = 相对调用会话 cwd 解析,绝对 = 全局并在挂载时校验 |
|
|
106
|
+
| `fallbackPath` | *(none)* | 按 cwd 发现无果时使用的规则文件;挂载时校验 |
|
|
107
|
+
| `badFilePolicy` | `fail` | 坏规则文件:`fail` 响亮地令待处理工具调用出错;`ignore-with-warning` 警告并空继续 |
|
|
108
|
+
| `maxRules` | `256` | 生效源链上规则总数的硬上限 |
|
|
109
|
+
| `maxCachedWorkspaces` | `512` | 缓存的工作区规则加载硬上限(LRU 逐出) |
|
|
110
|
+
| `patternMode` | `glob` | `params`/`paths`/`when.env` 模式风格:`glob` 或 `regex`(工具名始终为 glob) |
|
|
111
|
+
| `watch` | `true` | Chokidar 监听 + 变更重载 |
|
|
112
|
+
| `watchStabilityThresholdMs` | `200` | 重载去抖窗口(毫秒) |
|
|
113
|
+
| `language` | `en` | `/rules` 输出语言:`en`、`zh`、`es`、`pt`、`hi` |
|
|
114
|
+
| `caseInsensitivePaths` | *(win32)* | `paths` 模式与工作区根比较忽略 ASCII 大小写;Windows 上为 `true` |
|
|
115
115
|
| `audit` | `all` | 审计粒度:`all` 记录每次命中与透传;`hits` 跳过透传事件 |
|
|
116
|
-
| `searchUp` | `false` | 从会话 cwd
|
|
117
|
-
| `maxGlobStars` | `2` | 每个 glob
|
|
118
|
-
| `enforce` | `true` | `false` =
|
|
119
|
-
| `allowUnmarkedAudit` | `false` |
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
| `searchUp` | `false` | 从会话 cwd 向上遍历父目录并合并每个找到的规则文件,就近优先 |
|
|
117
|
+
| `maxGlobStars` | `2` | 每个 glob 模式无界 `*`/`**` 量词的硬上限 |
|
|
118
|
+
| `enforce` | `true` | `false` = 试运行:deny/ask 命中带 `dryRun` 标记审计,每个调用都透传 |
|
|
119
|
+
| `allowUnmarkedAudit` | `false` | 前标记宿主丢弃 `ignorable` 标记;插件以一次性警告禁用会话日志审计。设 `true` 重新启用 |
|
|
120
|
+
| `network.enabled` | `true` | 代理、环境注入与 web 工具模式默认的总开关 |
|
|
121
|
+
| `network.mode` | `auto` | 策略模式:`auto` 跟随沙箱预设,或 `deny-all` / `whitelist` / `allow-all` |
|
|
122
|
+
| `network.autoFallback` | `allow-all` | `auto` 无沙箱策略服务时使用的模式 |
|
|
123
|
+
| `network.unlisted` | `ask` | 白名单模式下未命中规则目标:`ask` 或 `deny` |
|
|
124
|
+
| `network.proxyBind` | `127.0.0.1` | 本地代理绑定地址(仅回环) |
|
|
125
|
+
| `network.proxyPort` | `0` | 本地代理端口;`0` 选空闲临时端口 |
|
|
126
|
+
| `network.proxyMaxRecent` | `100` | 设置页保留的近期拦截记录上限 |
|
|
127
|
+
| `network.loopback` | `allow` | 回环目标:`allow`(Codex 对齐)或 `policy` |
|
|
128
|
+
| `network.injectEnv` | `true` | 是否为子进程注入代理环境变量 |
|
|
129
|
+
| `network.noProxy` | `clear` | 子进程 NO_PROXY 处理:`clear` 强制策略或 `preserve` |
|
|
130
|
+
|
|
131
|
+
## Tools & surfaces
|
|
132
|
+
|
|
133
|
+
| Surface | Kind | Notes |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `tools/pre-execute` | listener | 首个匹配的 allow/deny/ask 规则 + 网络 URL 候选抽取 |
|
|
136
|
+
| `/rules` | command | `list` · `reload` · `decisions [n]` · `test <tool> <json>` |
|
|
137
|
+
| `permissionRules/decision` | event | 每次命中与透传的仅日志审计 |
|
|
138
|
+
| `permissionRules/network` | event | 被拒连接的代理层审计 |
|
|
139
|
+
| HTTP/CONNECT proxy | service | 治理 shell 子进程流量的内置本地代理 |
|
|
140
|
+
| settings page | client | 网络模式编辑器、规则编辑器、块计数器、近期拦截 |
|
|
122
141
|
|
|
123
142
|
```
|
|
124
|
-
/rules
|
|
125
|
-
/rules list
|
|
126
|
-
/rules reload
|
|
127
|
-
/rules decisions [n]
|
|
128
|
-
/rules test
|
|
143
|
+
/rules list the active rules, their source files, and any last-reload error
|
|
144
|
+
/rules list explicit alias for the bare listing
|
|
145
|
+
/rules reload re-read the rule-file chain for this workspace
|
|
146
|
+
/rules decisions [n] show the last n permission decisions of this session (default 10)
|
|
147
|
+
/rules test <tool> <json> dry-evaluate the rules against a hypothetical call
|
|
129
148
|
```
|
|
130
149
|
|
|
131
|
-
`/rules test`
|
|
150
|
+
`/rules test` 也接受前置标志:`--cwd <dir>`、`--env KEY=VALUE`(可重复)、`--agent <selector>`(可重复)与 `--platform <name>`。在多文件链(如 `searchUp`)中,每条列出的规则行都归属到其自身源文件。
|
|
132
151
|
|
|
133
|
-
|
|
152
|
+
## Permissions & data
|
|
134
153
|
|
|
135
|
-
|
|
154
|
+
- **Permissions**:workshop 清单声明 `files:read`、`files:watch`、`files:write`、`session:append` 与 `network:outbound`。`ask` 决策走官方审批接缝——不重实现、不绕过。
|
|
155
|
+
- **Data**:规则文件从磁盘读取;不写任何规则数据。无模型调用、无评审子代理。
|
|
156
|
+
- **Session log**:`permissionRules/decision` 绝不注入模型上下文,并以信封的 `ignorable: true` 标记追加,任何 harness 构建都能加载日志。
|
|
136
157
|
|
|
137
|
-
|
|
138
|
-
- 已做集成测试(`test/integration.spec.ts`):`permissionRules/decision` → `approval/asked` → `autoReview/verdict` → `approval/decided`,reviewer 以脚本化 mock 替代。
|
|
139
|
-
- [官方 harness](https://github.com/deepseek-ai/deepseek-harness) 的 `never` 审批策略与全部 fail-closed 保证不受影响。
|
|
158
|
+
## Security boundaries
|
|
140
159
|
|
|
141
|
-
|
|
160
|
+
- **是策略,不是内核。** `paths` 候选只来自一组文档化的参数键(任意嵌套深度、有深度上限),且仅工作区相对路径匹配。
|
|
161
|
+
- **这里没有评审者。** 插件绝不生成子代理或调用模型——产出 `ask` 决策就是其工作的终点。
|
|
162
|
+
- **不改沙箱。** OS 级沙箱策略属于沙箱接缝,与本插件无关。
|
|
163
|
+
- **响亮地拒绝错误配置。** 未知 YAML 字段、未知 action 与坏模式在加载期被拒绝。
|
|
164
|
+
- **回溯界限。** glob 模式以 `maxGlobStars` 限制无界星号展开;正则模式拒绝嵌套无界量词与量化重叠字面交替。
|
|
142
165
|
|
|
143
|
-
|
|
144
|
-
- **不自己起 reviewer。**本插件不 spawn 子代理、不调模型——产出 `ask` 决定就是它工作的终点。
|
|
145
|
-
- **不改沙箱。**OS 级沙箱策略属于 sandbox seam,不属于本插件。
|
|
146
|
-
- **响亮拒绝错误配置。**未知 YAML 字段、未知 action、坏模式在加载期被拒绝,绝不静默忽略。
|
|
147
|
-
- **回溯有界。**glob 模式的无界星号量词数由 `maxGlobStars` 封顶;regex 模式拒绝嵌套无界量词与量化重叠字面量交替。(`\d+\.\d+\.\d+` 这类独立量词链仍然放行——regex 模式是逃生舱,glob 模式是受守护的默认。)
|
|
166
|
+
## Known limitations
|
|
148
167
|
|
|
149
|
-
|
|
168
|
+
- **前标记宿主上的审计标记。** `permissionRules/decision` 以 `ignorable: true` 追加;`Session.append` 早于该标记的宿主(`0.1.0-rc.6` 线)静默丢弃它,因此运行时以一次性警告禁用会话日志审计。设 `allowUnmarkedAudit: true` 重新启用;用 `scripts/repair-session-logs.mjs` 修复已写日志。
|
|
169
|
+
- **路径候选是启发式的。** 只有文档化的参数键参与路径匹配,且工作区相对匹配仅在 `caseInsensitivePaths` 开启时忽略 ASCII 大小写。
|
|
170
|
+
- **glob 是保守子集。** 无花括号展开——写两个模式,或用正则模式。
|
|
171
|
+
- **正则回溯守卫是结构性的、非穷尽的。** 对不可信文件优先用 glob 模式。
|
|
150
172
|
|
|
151
|
-
|
|
152
|
-
- `Drifter-yh/dsh-tool-policy` — deny-by-default 工具策略;在此声明差异化,避免重复实现。
|
|
153
|
-
- `dsh-auto-review` — 本插件所前置的"AI 兜底"半环。
|
|
173
|
+
## Collaborating with dsh-auto-review
|
|
154
174
|
|
|
155
|
-
|
|
175
|
+
- `dsh-permission-rules` 产出 `ask`;`dsh-auto-review` 在 `approval/request` waterfall 上以只读第二模型裁决作答(或委托给人)。两者都挂载即得完整闭环。
|
|
176
|
+
- 集成测试:`permissionRules/decision` → `approval/asked` → `autoReview/verdict` → `approval/decided`,评审者以脚本化 mock 替换。
|
|
177
|
+
- 官方 harness 的 `never` 审批策略与每个失败关闭保证保持不变。
|
|
156
178
|
|
|
157
|
-
|
|
158
|
-
- `paths` 候选是启发式的:只有文档化的参数键进入路径匹配;工作区相对匹配仅在 `caseInsensitivePaths` 开启时忽略 ASCII 大小写。
|
|
159
|
-
- glob 是保守子集(不支持花括号展开)——写两条模式,或用 regex 模式。
|
|
160
|
-
- regex 回溯守卫是结构性的而非穷尽的:不含字面量前缀的交替歧义(如精心构造的环视)由作者自负其责;不可信文件请优先用 glob 模式。
|
|
179
|
+
## Session log repair
|
|
161
180
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
`ignorable` 标记出现之前的会话日志可能被较新的 harness 构建拒绝(`SessionFormatUnsupportedError`)。随包发布的 `scripts/repair-session-logs.mjs` 只改写目标审计行、补上 `ignorable: true`,帧边界保留并自动备份:
|
|
181
|
+
在 `ignorable` 标记出现之前写入的会话日志可能被较新 harness 构建拒绝(`SessionFormatUnsupportedError`)。随附的 `scripts/repair-session-logs.mjs` 仅重写目标审计行以携带 `ignorable: true`,保帧、带备份:
|
|
165
182
|
|
|
166
183
|
```sh
|
|
167
|
-
node scripts/repair-session-logs.mjs scan [--home DIR] #
|
|
184
|
+
node scripts/repair-session-logs.mjs scan [--home DIR] # 报告外来行,不改任何内容
|
|
168
185
|
node scripts/repair-session-logs.mjs repair [--home DIR] [--dry-run]
|
|
169
186
|
```
|
|
170
187
|
|
|
171
|
-
`--home`
|
|
188
|
+
`--home` 默认 `$DSH_HOME/sessions`(或 `~/.dsh/sessions`)。
|
|
172
189
|
|
|
173
|
-
##
|
|
190
|
+
## Development
|
|
174
191
|
|
|
175
192
|
```sh
|
|
176
|
-
pnpm install
|
|
177
|
-
pnpm run typecheck
|
|
178
|
-
pnpm run lint
|
|
179
|
-
pnpm test
|
|
180
|
-
pnpm run test:coverage #
|
|
181
|
-
pnpm run build
|
|
182
|
-
pnpm run pack:check
|
|
183
|
-
node scripts/check-readme-sync.mjs
|
|
193
|
+
pnpm install # node ^22.19 || >=24
|
|
194
|
+
pnpm run typecheck # tsc, src + tests
|
|
195
|
+
pnpm run lint # eslint, src + tests + scripts
|
|
196
|
+
pnpm test # vitest: 139 tests, 9 suites
|
|
197
|
+
pnpm run test:coverage # coverage gate (90/80/90/90)
|
|
198
|
+
pnpm run build # tsc declarations + tsdown bundles (lib/)
|
|
199
|
+
pnpm run pack:check # build + pack (the published artifact)
|
|
200
|
+
node scripts/check-readme-sync.mjs # five-language README sync gate (also in CI)
|
|
184
201
|
```
|
|
185
202
|
|
|
186
|
-
|
|
203
|
+
无头端到端验证记录见 [VERIFICATION.md](VERIFICATION.md)。
|
|
204
|
+
|
|
205
|
+
## Topics
|
|
206
|
+
|
|
207
|
+
`dsh`, `dsh-plugin`, `deepseek-harness`, `permission`, `policy`, `allow-deny-ask`, `approval`, `safety`, `network`, `network-policy`, `proxy`
|
|
187
208
|
|
|
188
|
-
##
|
|
209
|
+
## Contributors
|
|
189
210
|
|
|
190
|
-
- [@PerryLink](https://github.com/PerryLink)
|
|
191
|
-
- [@22xuan](https://github.com/22xuan)
|
|
211
|
+
- [@PerryLink](https://github.com/PerryLink) —— 创建者与维护者:规则词汇与求值、运行时、HMR 监听、会话日志审计、网络策略 + 代理,以及五语文档。
|
|
212
|
+
- [@22xuan](https://github.com/22xuan) —— 关于 rc.6 宿主静默丢弃审计事件 `ignorable` 标记的详细报告([#2](https://github.com/PerryLink/dsh-permission-rules/issues/2))与上游 harness 讨论;v0.4.1 的运行时宿主能力检测与文档更正直接源自该分析。
|
|
192
213
|
|
|
193
|
-
## PerryLink DSH
|
|
214
|
+
## PerryLink DSH Plugin Family
|
|
194
215
|
|
|
195
|
-
本项目是 [PerryLink](https://github.com/PerryLink) 维护的 [15 个 DeepSeek Harness 插件](https://github.com/PerryLink)
|
|
216
|
+
本项目是 [PerryLink](https://github.com/PerryLink) 维护的 [15 个 DeepSeek Harness 插件](https://github.com/PerryLink) 之一。如果这个对你有用,其他插件多半也有用:
|
|
196
217
|
|
|
197
|
-
|
|
|
218
|
+
| Plugin | One-liner |
|
|
198
219
|
|---|---|
|
|
199
|
-
| [dsh-mcp-panel](https://github.com/PerryLink/dsh-mcp-panel) |
|
|
200
|
-
| [dsh-doublecheck](https://github.com/PerryLink/dsh-doublecheck) |
|
|
201
|
-
| [dsh-background-agents](https://github.com/PerryLink/dsh-background-agents) |
|
|
202
|
-
| [dsh-lsp-actions](https://github.com/PerryLink/dsh-lsp-actions) |
|
|
203
|
-
| [dsh-output-styles](https://github.com/PerryLink/dsh-output-styles) |
|
|
204
|
-
| [dsh-checkpoint-rewind](https://github.com/PerryLink/dsh-checkpoint-rewind) |
|
|
205
|
-
| **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | Claude Code
|
|
206
|
-
| [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) |
|
|
207
|
-
| [dsh-memento](https://github.com/PerryLink/dsh-memento) |
|
|
208
|
-
| [dsh-skill-pack-security](https://github.com/PerryLink/dsh-skill-pack-security) |
|
|
209
|
-
| [dsh-session-pin](https://github.com/PerryLink/dsh-session-pin) |
|
|
210
|
-
| [dsh-composer-history](https://github.com/PerryLink/dsh-composer-history) |
|
|
211
|
-
| [dsh-github](https://github.com/PerryLink/dsh-github) |
|
|
212
|
-
| [dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide) |
|
|
213
|
-
| [dsh-claude-move](https://github.com/PerryLink/dsh-claude-move) |
|
|
220
|
+
| [dsh-mcp-panel](https://github.com/PerryLink/dsh-mcp-panel) | Read-only MCP runtime panel: /mcp command + Settings tab with status, tools and errors |
|
|
221
|
+
| [dsh-doublecheck](https://github.com/PerryLink/dsh-doublecheck) | Engineering-discipline guard: requirements grill, test gates, adversary review |
|
|
222
|
+
| [dsh-background-agents](https://github.com/PerryLink/dsh-background-agents) | Durable background child agents with a Web UI sidebar, messaging and interrupt |
|
|
223
|
+
| [dsh-lsp-actions](https://github.com/PerryLink/dsh-lsp-actions) | LSP diagnostics, formatting, completion, code actions and rename over language servers |
|
|
224
|
+
| [dsh-output-styles](https://github.com/PerryLink/dsh-output-styles) | Claude Code outputStyles-equivalent runtime style switching |
|
|
225
|
+
| [dsh-checkpoint-rewind](https://github.com/PerryLink/dsh-checkpoint-rewind) | Claude Code /rewind-equivalent: snapshots, session forks, one-shot restore |
|
|
226
|
+
| **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | Claude Code-style declarative allow/deny/ask permission rules with audit |
|
|
227
|
+
| [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) | Second-model auto-review on the approval chain, fail-closed by default |
|
|
228
|
+
| [dsh-memento](https://github.com/PerryLink/dsh-memento) | Approval-gated cross-session memory: ctx.memory seam + SQLite + memory tool |
|
|
229
|
+
| [dsh-skill-pack-security](https://github.com/PerryLink/dsh-skill-pack-security) | Security-audit skill pack: secret scan, dependency and supply-chain review |
|
|
230
|
+
| [dsh-session-pin](https://github.com/PerryLink/dsh-session-pin) | Pin sessions in the Web sidebar with durable ordering |
|
|
231
|
+
| [dsh-composer-history](https://github.com/PerryLink/dsh-composer-history) | Terminal-style input history for the web composer: arrows, Ctrl+R search |
|
|
232
|
+
| [dsh-github](https://github.com/PerryLink/dsh-github) | GitHub PR/issues integration for DSH, every write gated by approval |
|
|
233
|
+
| [dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide) | Plugin-development knowledge base as an on-demand agent skill |
|
|
234
|
+
| [dsh-claude-move](https://github.com/PerryLink/dsh-claude-move) | Migrate Claude Code sessions, memory, skills and CLAUDE.md into DSH |
|
|
214
235
|
|
|
215
236
|
## License
|
|
216
237
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Shared rule-syntax test vectors
|
|
2
|
+
|
|
3
|
+
Machine-readable conformance vectors for every DSH gate that consumes the
|
|
4
|
+
`dsh-permission-rules` rule syntax. The corpus is **implementation-neutral**:
|
|
5
|
+
it declares rule text plus expected decisions, never gate internals, so any
|
|
6
|
+
second gate (or a future core seam) can prove "same syntax, same verdicts".
|
|
7
|
+
|
|
8
|
+
- **Schema** — `corpus.json`, `schema: dsh-rule-test-vectors/v1`. Each vector
|
|
9
|
+
is `{ id, purpose, rules (YAML text), cases[] }`; each case is
|
|
10
|
+
`{ tool, arguments, context?, expect }` where `expect` is `allow` | `ask` |
|
|
11
|
+
`deny` | `null` (null = passthrough). `context` carries `{ platform, env,
|
|
12
|
+
agents }` host facts — the same vocabulary as the `when`/`agents` match
|
|
13
|
+
dimensions.
|
|
14
|
+
- **The reference implementation** is this package: `test/test-vectors.spec.ts`
|
|
15
|
+
compiles every vector through `compileRulesChain` and asserts each case's
|
|
16
|
+
first-match decision, so the corpus is proven self-consistent against the
|
|
17
|
+
gate that defines the syntax. A second gate imports the same file and runs
|
|
18
|
+
the same table — a mismatch is a conformance failure in one of the two.
|
|
19
|
+
- **Extending the corpus** — add vectors for any syntax corner you rely on
|
|
20
|
+
(negation patterns, nested params, path candidates, network rules, chains).
|
|
21
|
+
Keep `rules` self-contained YAML and `cases` free of gate-specific fields.
|
|
22
|
+
Bump nothing: the corpus is additive, and both gates consume HEAD.
|
|
23
|
+
|
|
24
|
+
This corpus exists because two independent DSH permission gates
|
|
25
|
+
(`dsh-permission-rules` and sjh9714's gate, see
|
|
26
|
+
[PerryLink/dsh-permission-rules#4](https://github.com/PerryLink/dsh-permission-rules/issues/4))
|
|
27
|
+
agreed to share rule-syntax test vectors rather than re-derive them
|
|
28
|
+
separately — one corpus, two consumers, no drift.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "dsh-rule-test-vectors/v1",
|
|
3
|
+
"description": "Shared, implementation-neutral conformance vectors for DSH permission gates that consume the dsh-permission-rules rule syntax. Every vector carries its own rule text and the expected first-match decision for each case; gates that claim this syntax evaluate the same inputs to the same outputs. The reference implementation is dsh-permission-rules (see docs/rule-test-vectors/README.md).",
|
|
4
|
+
"workspace": "/workspace",
|
|
5
|
+
"vectors": [
|
|
6
|
+
{
|
|
7
|
+
"id": "exact-tool-deny",
|
|
8
|
+
"purpose": "an exact tool-name match denies; other tools pass through",
|
|
9
|
+
"rules": "rules:\n - match: { tools: [bash] }\n action: deny\n reason: shell is denied\n",
|
|
10
|
+
"cases": [
|
|
11
|
+
{ "tool": "bash", "arguments": { "command": "ls" }, "expect": "deny" },
|
|
12
|
+
{ "tool": "write", "arguments": { "path": "a.txt" }, "expect": null }
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "glob-tools-ask",
|
|
17
|
+
"purpose": "tool-name globs match whole names (mcp__*); non-matching tools pass through",
|
|
18
|
+
"rules": "rules:\n - match: { tools: [\"mcp__*\"] }\n action: ask\n reason: mcp tools need approval\n",
|
|
19
|
+
"cases": [
|
|
20
|
+
{ "tool": "mcp__github__search", "arguments": {}, "expect": "ask" },
|
|
21
|
+
{ "tool": "read", "arguments": {}, "expect": null }
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "params-command-glob",
|
|
26
|
+
"purpose": "a params pattern matched against the command string denies the destructive call only",
|
|
27
|
+
"rules": "rules:\n - match:\n tools: [bash, pwsh]\n params:\n command: [\"rm -rf*\", \"git push*--force*\"]\n action: deny\n reason: destructive command\n",
|
|
28
|
+
"cases": [
|
|
29
|
+
{ "tool": "bash", "arguments": { "command": "rm -rf /tmp/cache" }, "expect": "deny" },
|
|
30
|
+
{ "tool": "pwsh", "arguments": { "command": "git push origin --force" }, "expect": "deny" },
|
|
31
|
+
{ "tool": "bash", "arguments": { "command": "ls -la" }, "expect": null }
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "params-negation",
|
|
36
|
+
"purpose": "a negation-only pattern matches a present value that hits no negation",
|
|
37
|
+
"rules": "rules:\n - match:\n tools: [bash]\n params:\n command: [\"!*test*\"]\n action: deny\n reason: test commands are blocked\n",
|
|
38
|
+
"cases": [
|
|
39
|
+
{ "tool": "bash", "arguments": { "command": "pnpm build" }, "expect": "deny" },
|
|
40
|
+
{ "tool": "bash", "arguments": { "command": "pnpm test" }, "expect": null }
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "paths-protected",
|
|
45
|
+
"purpose": "workspace-relative path patterns deny secret files; unrelated paths pass",
|
|
46
|
+
"rules": "rules:\n - match:\n paths: [\"**/.env\", \"**/secrets/**\", \"**/*.pem\"]\n action: deny\n reason: protected secrets and credential paths are off-limits\n",
|
|
47
|
+
"cases": [
|
|
48
|
+
{ "tool": "read", "arguments": { "path": ".env" }, "expect": "deny" },
|
|
49
|
+
{ "tool": "edit", "arguments": { "file_path": "src/secrets/api.key" }, "expect": "deny" },
|
|
50
|
+
{ "tool": "read", "arguments": { "path": "src/index.ts" }, "expect": null }
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "when-platform",
|
|
55
|
+
"purpose": "a when.platform condition matches only the named platform",
|
|
56
|
+
"rules": "rules:\n - match:\n tools: [bash]\n when: { platform: [win32] }\n action: deny\n reason: shell denied on windows\n",
|
|
57
|
+
"cases": [
|
|
58
|
+
{ "tool": "bash", "arguments": {}, "context": { "platform": "win32" }, "expect": "deny" },
|
|
59
|
+
{ "tool": "bash", "arguments": {}, "context": { "platform": "linux" }, "expect": null }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "when-env",
|
|
64
|
+
"purpose": "a when.env condition matches only when the variable is present with a matching value",
|
|
65
|
+
"rules": "rules:\n - match:\n tools: [bash]\n when:\n env: { CI: \"true\" }\n action: deny\n reason: no shell inside CI\n",
|
|
66
|
+
"cases": [
|
|
67
|
+
{ "tool": "bash", "arguments": {}, "context": { "env": { "CI": "true" } }, "expect": "deny" },
|
|
68
|
+
{ "tool": "bash", "arguments": {}, "context": { "env": { "CI": "false" } }, "expect": null },
|
|
69
|
+
{ "tool": "bash", "arguments": {}, "context": { "env": {} }, "expect": null }
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "agents-subagent",
|
|
74
|
+
"purpose": "an agents-scoped rule fires only for the matching identity candidate and fails closed for unknown identity",
|
|
75
|
+
"rules": "rules:\n - match:\n tools: [bash]\n agents: [subagent]\n action: deny\n reason: subagents cannot run the shell\n",
|
|
76
|
+
"cases": [
|
|
77
|
+
{ "tool": "bash", "arguments": {}, "context": { "agents": ["subagent"] }, "expect": "deny" },
|
|
78
|
+
{ "tool": "bash", "arguments": {}, "context": { "agents": ["main"] }, "expect": null },
|
|
79
|
+
{ "tool": "bash", "arguments": {}, "context": {}, "expect": null }
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "absent-dimension",
|
|
84
|
+
"purpose": "an absent dimension fires when the listed keys are missing",
|
|
85
|
+
"rules": "rules:\n - match:\n tools: [write]\n absent: [dryRun]\n action: deny\n reason: write requires an explicit dryRun guard\n",
|
|
86
|
+
"cases": [
|
|
87
|
+
{ "tool": "write", "arguments": { "path": "a.txt" }, "expect": "deny" },
|
|
88
|
+
{ "tool": "write", "arguments": { "path": "a.txt", "dryRun": true }, "expect": null }
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "first-match-wins",
|
|
93
|
+
"purpose": "rules evaluate in written order; the first match settles the decision",
|
|
94
|
+
"rules": "rules:\n - match: { tools: [bash] }\n action: allow\n reason: shell is allowed first\n - match: { tools: [bash] }\n action: deny\n reason: unreachable for bash\n",
|
|
95
|
+
"cases": [
|
|
96
|
+
{ "tool": "bash", "arguments": {}, "expect": "allow" },
|
|
97
|
+
{ "tool": "read", "arguments": {}, "expect": null }
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "disabled-rule",
|
|
102
|
+
"purpose": "an enabled:false rule stays inert",
|
|
103
|
+
"rules": "rules:\n - match: { tools: [bash] }\n action: deny\n reason: currently disabled\n enabled: false\n",
|
|
104
|
+
"cases": [
|
|
105
|
+
{ "tool": "bash", "arguments": {}, "expect": null }
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -4639,13 +4639,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4639
4639
|
const remote = ctx.get("remote");
|
|
4640
4640
|
const slots = ctx.get("slots");
|
|
4641
4641
|
if (locale === void 0 || remote === void 0 || slots === void 0) return;
|
|
4642
|
-
ctx.effect(() => {
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
});
|
|
4647
|
-
return () => {};
|
|
4648
|
-
});
|
|
4642
|
+
ctx.effect(() => locale.register(NS, {
|
|
4643
|
+
zh,
|
|
4644
|
+
en
|
|
4645
|
+
}), "dsh-permission-rules: dictionaries");
|
|
4649
4646
|
await remote.$mount(PERMISSION_RULES_REMOTE);
|
|
4650
4647
|
ctx.inject(["remote.permissionRules"], (scope) => {
|
|
4651
4648
|
const t = locale.bind(NS);
|