@gavinc-cn/dsh-path-rules 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +162 -2
- package/lib/analyze.mjs +1078 -0
- package/lib/auto-whitelist.mjs +260 -0
- package/lib/classify.mjs +72 -0
- package/lib/index.mjs +634 -0
- package/lib/rules.mjs +139 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +30 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 @gavinc-cn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,163 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gavinc-cn/dsh-path-rules
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DSH 第五权限模式「路径规则」的逐调用闸门:挂在 `tools/pre-execute` waterfall 上,
|
|
4
|
+
先 `await next()` 取下游守卫(`dsh-git-guard` / `dsh-readonly-guard` 等)的结论,
|
|
5
|
+
再按「更严者胜」叠加自己的判定 —— 只可能更严,永不弱化既有守卫。
|
|
6
|
+
|
|
7
|
+
规则(用户 2026-10-13 定义):
|
|
8
|
+
|
|
9
|
+
- 项目内(= 会话工作区根)目录**可读可写**;
|
|
10
|
+
- 项目外目录**只读**,写入需要用户审批;
|
|
11
|
+
- **白名单**内的目录可读可写(直通,不弹框);
|
|
12
|
+
- **黑名单**内的目录读写**都要审批**(优先级最高,压过白名单与项目内);
|
|
13
|
+
- `bash` / `pwsh`:只读命令直接放行;写入目标可静态解析时按上述规则判定;
|
|
14
|
+
解析不出的命令默认拒绝并提示模型改写成可判定形式;
|
|
15
|
+
- **自动白名单**(可选,`autoWhitelist: true`):用户在审批框里批准的沙箱升权写入目标,
|
|
16
|
+
其目录会自动进入白名单(持久化),下次同目录的写入不再弹框。见下文。
|
|
17
|
+
|
|
18
|
+
## 激活条件
|
|
19
|
+
|
|
20
|
+
插件**只在预设身份为 `path-rules` 时介入**(`permissionPresets.current(session)`):
|
|
21
|
+
|
|
22
|
+
- 其它四档(`read-only` / `workspace-write` / `danger-full-access` / `auto`)行为一字不变;
|
|
23
|
+
- 子代理(`session.header.origin === 'subagent'`)沿 `parentSession` 上溯取祖先身份
|
|
24
|
+
(框架不向子代理继承非 `auto`/`danger-full-access` 的预设身份);
|
|
25
|
+
- 插件未加载 / `enabled: false` / 服务缺失 / 无 agent 的调用:一律透传,最坏退化成现状
|
|
26
|
+
(沙箱仍把写入限制在会话工作区 + 临时区),不会敞开。
|
|
27
|
+
|
|
28
|
+
## 判定链(先命中者胜)
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
黑名单命中 → ask 白名单命中 → allow 项目内 → allow
|
|
32
|
+
临时区(/tmp、os.tmpdir())→ allow 项目外读 → allow
|
|
33
|
+
项目外写 → ask
|
|
34
|
+
shell 判不出 → unanalyzable 策略(默认 deny + 改写指引)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`ask` 走框架标准审批流程(`ctx.approval.request`);会话审批通道为 `never`(子代理被框架钉成
|
|
38
|
+
`never`)时,插件在判定阶段就把 `ask` 改写成 `deny` 并提示回主会话执行 —— 那些会话里没有弹框
|
|
39
|
+
可弹,只能拒绝。
|
|
40
|
+
|
|
41
|
+
## 配置(插件 config;机器相关配置写在各 profile 的覆盖行)
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
- id: dsh-path-rules
|
|
45
|
+
name: "@gavinc-cn/dsh-path-rules"
|
|
46
|
+
config:
|
|
47
|
+
enabled: true # 总开关(false = 完全空转)
|
|
48
|
+
activationPreset: path-rules # 哪个预设身份激活本插件
|
|
49
|
+
whitelist: [] # 绝对路径;可读可写,压过"项目外",但被黑名单压过
|
|
50
|
+
blacklist: [] # 绝对路径;读写都要审批,压过一切
|
|
51
|
+
allowTempRoots: true # /tmp + os.tmpdir() 视为可写(与沙箱口径一致)
|
|
52
|
+
unanalyzable: deny # deny | ask:shell 判不出时的兜底
|
|
53
|
+
failMode: closed # closed | open:插件内部异常时的兜底
|
|
54
|
+
dryRun: false # true = 只记日志不拦(灰度用)
|
|
55
|
+
modelFallback: false # 预留字段:置 true 仅记一条 warn 日志,判定行为不变
|
|
56
|
+
autoWhitelist: false # true = 用户批准的沙箱升权写入目标自动进白名单(默认关闭)
|
|
57
|
+
autoWhitelistFile: <dsh home>/path-rules/auto-whitelist.json # 自动白名单状态文件(可选)
|
|
58
|
+
logPath: ~/.kimi-code/hooks/logs/path_rules.dsh.log
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
日志事件:`plugin-loaded / allow / ask / deny / pass / dry-run / error`,
|
|
62
|
+
以及自动白名单的 `auto-whitelist / auto-whitelist-skip / auto-whitelist-load /
|
|
63
|
+
auto-whitelist-save / auto-whitelist-error`。
|
|
64
|
+
|
|
65
|
+
## 自动白名单(`autoWhitelist`,2026-10-18 新增)
|
|
66
|
+
|
|
67
|
+
**用途**:路径规则档下,项目外写入的审批通路是"沙箱拒绝 → 模型带 `sandbox_permissions` 重试 →
|
|
68
|
+
框架弹审批框"。同一个目录反复写就会反复弹框。打开 `autoWhitelist` 后,用户**批准过一次**的写入目标,
|
|
69
|
+
其目录会被自动记进白名单;之后同一目录的升权请求由本插件直接答 `allowed-once`(不弹框)。
|
|
70
|
+
|
|
71
|
+
**触发条件**(缺一不可):
|
|
72
|
+
|
|
73
|
+
1. `autoWhitelist: true` 且 `enabled: true`、`dryRun: false`;
|
|
74
|
+
2. 会话当前处于「路径规则」档(`activationPreset` 命中,子代理沿父链上溯;其它四档不学习);
|
|
75
|
+
3. 审批类型是**沙箱升权**(`reason` 以 `escalate sandbox to ` 开头);
|
|
76
|
+
4. 用户在审批框点了同意(`approval/request` 瀑布下游返回 `allowed-once`;拒绝/取消/不可用都不学)。
|
|
77
|
+
|
|
78
|
+
**学什么**:从该次调用的参数静态解析写入目标 —— `write`/`edit` 的 `file_path`、`bash`/`pwsh` 的
|
|
79
|
+
写目标(复用 `analyze.mjs`);目标是已存在的目录就取它自己,否则取父目录。**解析不出就什么都不学**
|
|
80
|
+
(不猜):MCP/未知工具、判不出的 shell 命令、参数没被记住的调用都只记一条 skip 日志。
|
|
81
|
+
|
|
82
|
+
**生效方式**:条目并入内存白名单(下一条消息起系统提示里可见),此后:
|
|
83
|
+
|
|
84
|
+
- 该目录的写入由本插件直接 `allow`(但**沙箱仍会先拒绝首次写入** —— 跨层限制见下);
|
|
85
|
+
- 模型按提示带 `sandbox_permissions` 重试时,`approval/request` 命中白名单 ⇒ 插件答 `allowed-once`,
|
|
86
|
+
用户**不再看到弹框**。
|
|
87
|
+
|
|
88
|
+
命令类(`bash`/`pwsh`)升权请求的命中判定 2026-10-18 起改为**按静态写目标**:全部写目标都落在
|
|
89
|
+
白名单/项目内/临时区、且至少一个命中白名单才自动放行;任一目标命中黑名单或项目外就交给用户。
|
|
90
|
+
只有命令判不出写目标时才退回旧的「命令文本包含白名单路径」粗粒度口径。
|
|
91
|
+
(旧口径会把 `cp /white/f.txt /elsewhere/f.txt` 这类命令静默放行;自动白名单让白名单动态变化后必须收紧。)
|
|
92
|
+
|
|
93
|
+
**持久化**:默认状态文件 `<dsh home>/path-rules/auto-whitelist.json`(可用 `autoWhitelistFile` 覆盖,
|
|
94
|
+
支持 `~`),格式:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"version": 1,
|
|
99
|
+
"entries": [
|
|
100
|
+
{ "path": "/srv/data", "platform": "linux", "addedAt": "2026-10-18T06:00:00.000Z", "tool": "write", "callId": "call_..." }
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- 按 `process.platform` **分区存放**:容器(linux)与宿主(win32)共享同一份 `~/.dsh` 时互不串味,
|
|
106
|
+
写回时保留另一平台的条目;跨平台写法的路径在加载时直接忽略。
|
|
107
|
+
- 原子写(临时文件 + rename)、坏文件容错(只记 error 日志,不影响插件加载)、上限 200 条
|
|
108
|
+
(超出淘汰最旧)。
|
|
109
|
+
- 用户可直接编辑/删除该文件;**生效时机 = 插件重新加载**(改本行 config 是 patch 层热加载约 2 秒;
|
|
110
|
+
改代码需重启 dsh)。`loader` 日志里 `auto-whitelist loaded=N` 可确认读到了多少条。
|
|
111
|
+
|
|
112
|
+
**安全护栏**(自动加入前逐条过滤,丢弃原因写进日志):
|
|
113
|
+
|
|
114
|
+
| 跳过原因 | 含义 |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `blacklist` | 目标/目录命中黑名单 ⇒ 不学(黑名单优先级最高,加了也无效) |
|
|
117
|
+
| `no-need` | 目标在项目内或临时区,本来就直通,无需白名单 |
|
|
118
|
+
| `already-whitelisted` | 目录已被现有白名单覆盖 |
|
|
119
|
+
| `protected` | 与插件状态/日志文件或 DSH home 相关(防"自动白名单自己改写自己") |
|
|
120
|
+
| `sensitive` | 目录是系统根(`/`、`/etc`、`/usr`、`/var`、`/opt` …)或用户 home **自身或其祖先** |
|
|
121
|
+
|
|
122
|
+
即:批准一个文件**不会**被放大成整个 home/系统目录的长期信任;`/root/data` 这类更深层目录可以自动加入,
|
|
123
|
+
但 `/root` 本身不行(要用就手工写进 config 的 `whitelist`)。
|
|
124
|
+
|
|
125
|
+
**跨层限制(重要)**:自动白名单**不突破沙箱**。工作区外第一次写入仍会被 `workspace-write` 拒绝一次,
|
|
126
|
+
模型需按错误提示带 `sandbox_permissions` + `justification` 重试;区别只在于重试的审批请求
|
|
127
|
+
由插件自动放行、不再弹框。详见下文「已知盲区 8」。
|
|
128
|
+
|
|
129
|
+
## 已知盲区(未覆盖,别当安全)
|
|
130
|
+
|
|
131
|
+
1. **长驻进程自行写盘**:`bash` 的 `run_in_background`、`job_*` 起的进程在命令文本之外写文件。
|
|
132
|
+
2. **未受控工具透传**:MCP 工具与未来新增工具若自行写盘,本闸门与沙箱都可能覆盖不到。
|
|
133
|
+
3. **`glob`/`grep` 的 `pattern` 不是路径**:只对 `path` 参数做黑名单判定。
|
|
134
|
+
4. **子代理没有审批通道**(框架钉 `approval: never`)⇒ 只能"拒绝",不能"弹框"。
|
|
135
|
+
5. **编码/不可判定命令**:`-EncodedCommand`、`eval`、超过 3 层的 `bash -c` 嵌套、未闭合引号一律
|
|
136
|
+
按 `unanalyzable` 处理。
|
|
137
|
+
6. **读操作不受沙箱保护**:黑名单读的防护 100% 依赖本插件处于激活状态。
|
|
138
|
+
7. **大小写敏感按平台**:Linux 下按大小写敏感匹配;Windows 桌面端目前**未**实现大小写不敏感回退
|
|
139
|
+
(目录大小写不同但实际相同的路径可能漏判黑名单,已知未覆盖)。
|
|
140
|
+
8. **跨层分工**:插件批准**不能**突破 `workspace-write` 沙箱(可写根 = 会话工作区 + `/tmp` + `os.tmpdir()`)。
|
|
141
|
+
项目外/白名单(工作区外)的写会被沙箱拦下,模型需带 `sandbox_permissions` + `justification`
|
|
142
|
+
重试同一调用以触发框架审批;白名单目标的升权请求由本插件自动答 `allowed-once`(无弹框)。
|
|
143
|
+
详见 spec §4.4。
|
|
144
|
+
9. **自动白名单只学"能静态解析写目标"的调用**:MCP/未知工具、`unanalyzable` 的 shell、参数未被记忆的
|
|
145
|
+
调用都不学习(只记 skip 日志);`bash` 的间接产物(脚本内部、后台进程)同既有盲区。
|
|
146
|
+
10. **自动白名单是父目录粒度**:批准 `/a/b/f.txt` 会把整个 `/a/b` 加入白名单(用户语义如此);
|
|
147
|
+
敏感根保护只挡系统根与 home 自身/祖先,不挡更深的目录。
|
|
148
|
+
11. **命令判不出写目标时升权放行退回子串口径**:`bash`/`pwsh` 命令若静态分析判不出(`unanalyzable`、
|
|
149
|
+
参数未记忆),仍按"命令文本包含白名单路径"粗粒度放行 —— 这是旧插件沿用口径,无法在不分析的前提下收紧。
|
|
150
|
+
|
|
151
|
+
## 测试
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
cd packages/runtime/dsh-path-rules
|
|
155
|
+
node --test test/rules.test.mjs test/classify.test.mjs test/analyze-lex.test.mjs \
|
|
156
|
+
test/analyze-bash.test.mjs test/analyze-pwsh.test.mjs test/decide.test.mjs \
|
|
157
|
+
test/plugin.test.mjs test/analyze-security.test.mjs test/auto-whitelist.test.mjs
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
(Node v24 下 `node --test test/` 目录形式会失败,必须显式给出文件;`npm test` 已包含全部 9 个文件。)
|
|
161
|
+
|
|
162
|
+
设计文档:`docs/superpowers/specs/2026-10-16-dsh-path-rules-permission-mode-design.md`;
|
|
163
|
+
实现计划:`docs/superpowers/plans/2026-10-17-dsh-path-rules-permission-mode.md`。
|