dsh-isolation-gate 0.0.0-stage → 0.2.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/CHANGELOG.md +34 -0
- package/LICENSE +21 -0
- package/README.en.md +214 -0
- package/README.md +278 -2
- package/bin/gate.mjs +473 -0
- package/cordis.patch.yml +37 -0
- package/docs/banner.svg +36 -0
- package/docs/screenshots/01-overview.png +0 -0
- package/docs/screenshots/README.md +9 -0
- package/lib/audit.js +92 -0
- package/lib/client.js +1329 -0
- package/lib/dirs.js +204 -0
- package/lib/guard.js +342 -0
- package/lib/index.js +417 -0
- package/lib/roster.js +355 -0
- package/lib/service.js +567 -0
- package/lib/store.js +509 -0
- package/lib/toolgate.js +372 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +72 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.1 — 2026-10-21
|
|
4
|
+
|
|
5
|
+
只改元数据,无行为变更。
|
|
6
|
+
|
|
7
|
+
- `package.json` 补 `repository` / `homepage` / `bugs`,npm 页面回链 GitHub
|
|
8
|
+
- 关键词补充 `agent-safety` / `access-control` / `windows`
|
|
9
|
+
- README 重写首屏:横幅、徽章、Star CTA、路线图、参与入口
|
|
10
|
+
- 补齐开源配套:CONTRIBUTING / SECURITY / CODE_OF_CONDUCT / Issue 与 PR 模板
|
|
11
|
+
- CI 拆成 ubuntu 语法检查 + windows 功能自测
|
|
12
|
+
# 变更记录 · dsh-isolation-gate
|
|
13
|
+
|
|
14
|
+
> 本包原名 `dsh-access-gate`(npm 上该名已被他人占用,与本插件无关系)。
|
|
15
|
+
> 公开发布起用现名,版本号沿用内部版本序列。
|
|
16
|
+
|
|
17
|
+
## 0.2.0 — 2026-10-21(首个公开发布版本)
|
|
18
|
+
|
|
19
|
+
### 新增
|
|
20
|
+
- **隔离区授权台**:默认禁 + 名单制 + 三级(禁 / 只读 / 可读写删);`D:\.agents\门禁-授权.json` 为唯一生效依据。
|
|
21
|
+
- **分组**:组头一键切三档;空组可先建(`POST /group action=create`);成员逐项归组。
|
|
22
|
+
- **拖拽归组**:对象行直接拖进组卡片归组,拖到「未分组」= 取消分组(与技能面板同款 HTML5 拖放)。
|
|
23
|
+
- **原生目录选择**:宿主无 picker 服务时,自调 Windows `FolderBrowserDialog`(`spawn powershell -STA`)。
|
|
24
|
+
- **工具执行门**:`ctx.tools.guard` 拦截 read / write / shell 类工具对受管路径的访问;fail-closed(名单不可用一律拒绝)。
|
|
25
|
+
- **通道判定**:`bin/gate.mjs check|traverse|status`,给脚本与 agent 自查用;`/toolcheck` 干跑不执行。
|
|
26
|
+
- **审计**:`~/.dsh/dsh-isolation-gate/access-log.jsonl`,只记时间/对象/级别/操作/允许与否/理由,不记内容。
|
|
27
|
+
- **分组 / 名单 / 状态**:v1 → v2 自动迁移;写盘原子、无 BOM;名单增删先备份。
|
|
28
|
+
|
|
29
|
+
### 已知边界
|
|
30
|
+
- 只拦**经过 dsh 工具层**的访问;其它程序、别的 harness 进程、已在运行的进程不受影响。
|
|
31
|
+
- 不拦绕过工具层的原生文件系统调用(见 README「已知边界」)。
|
|
32
|
+
|
|
33
|
+
### 自测
|
|
34
|
+
- `npm test` → 4 个套件 · **325 断言**全绿(数据层 85 / 通道 74 / 路由 84 / 客户端 82)。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-access-gate contributors
|
|
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.en.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Access Gate · dsh-isolation-gate
|
|
2
|
+
|
|
3
|
+
> An **isolation-zone authorization console** for DeepSeek Harness (DSH):
|
|
4
|
+
> one screen that answers "which directories on this machine are managed, what level does each have,
|
|
5
|
+
> who changed it, and how can the agent check it itself?" — **default-deny**, **roster-based**,
|
|
6
|
+
> **group batch switching**, **auditable**, **agent-readable**.
|
|
7
|
+
|
|
8
|
+
[中文 README](./README.md)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What it is (and is not)
|
|
13
|
+
|
|
14
|
+
It is not another allow/deny rule engine, and it is not a sandbox.
|
|
15
|
+
|
|
16
|
+
- Others ask **"how do I let the agent reach more directories?"** (multi-root / allowlist / write-protect)
|
|
17
|
+
or **"how do I block tools with rules?"** (permission-rules / safety-net).
|
|
18
|
+
- **We ask: which directories are off-limits, what level each has, who changed it, and how does the agent check it?**
|
|
19
|
+
|
|
20
|
+
So the product is the **management surface**:
|
|
21
|
+
|
|
22
|
+
| Capability | Detail |
|
|
23
|
+
|---|---|
|
|
24
|
+
| **Three levels** | `deny` (default) / `read` (read-only) / `full` (read-write-delete) |
|
|
25
|
+
| **Two object kinds** | **Name** (first-level directory under the drive root, e.g. `Zcode`) or **any absolute path** (e.g. `<drive-root>\Zcode`, `E:\data\shared\project`) |
|
|
26
|
+
| **Default deny + roster** | Only objects in the roster are managed; everything else is out of scope; new objects start at `deny` |
|
|
27
|
+
| **One-screen switches** | A **segmented control** per object; changes persist immediately and survive restarts |
|
|
28
|
+
| **Groups** | Named groups ("harness isolation", "cross-agent sharing"); one click applies a level to the whole group; groups live in the state file, the roster stays one-object-per-line |
|
|
29
|
+
| **Folder picker** | Uses the host's native directory picker when available; otherwise falls back to "unlisted first-level directories + path input" |
|
|
30
|
+
| **Two enforcement points** | ① the governed channel `bin/gate.mjs`; ② the host **tool pre-execution guard** `ctx.tools.guard()` |
|
|
31
|
+
| **Audit** | `~/.dsh/dsh-isolation-gate/access-log.jsonl`: time / object / level / op / allowed / reason only — **never file contents** |
|
|
32
|
+
| **Agent self-check** | `node bin/gate.mjs status` — the model can read the policy instead of guessing |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Where it blocks
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
model issues a tool call
|
|
40
|
+
│
|
|
41
|
+
├─ tools/pre-execute ← ★ tool guard (this plugin's ctx.tools.guard)
|
|
42
|
+
│ file-tool arguments / shell command text → three-level decision → denial reason returned to the model
|
|
43
|
+
│
|
|
44
|
+
├─ tool actually executes (read / write / edit / bash …)
|
|
45
|
+
│
|
|
46
|
+
└─ bin/gate.mjs ← ★ governed channel (agent explicitly reads/writes managed objects through it)
|
|
47
|
+
denied ⇒ not a single byte is touched
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Both paths call the **same** `checkAccess`, so the panel, the CLI, and the tool guard cannot disagree.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
### A. `dsh plugin add` (recommended)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
dsh plugin --profile desktop add dsh-isolation-gate
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> On the desktop build you can also use **Plugins → Add plugin** and enter the package name or a local path.
|
|
63
|
+
|
|
64
|
+
### B. profile `link:` (development / local use)
|
|
65
|
+
|
|
66
|
+
```jsonc
|
|
67
|
+
// ~/.dsh/profiles/<profile>/package.json
|
|
68
|
+
"dependencies": { "dsh-isolation-gate": "link:D:/plugins/dsh-isolation-gate" }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
# ~/.dsh/profiles/<profile>/cordis.patch.yml
|
|
73
|
+
- insert:
|
|
74
|
+
- id: isolation-gate
|
|
75
|
+
name: 'dsh-isolation-gate'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
⚠️ **Changing the composition requires a dsh restart.** Switching levels or editing the roster does **not**.
|
|
79
|
+
|
|
80
|
+
### Defaults
|
|
81
|
+
|
|
82
|
+
| Config | Default | Meaning |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `statePath` | `<data-root>/isolation-gate/state.json` | single source of truth |
|
|
85
|
+
| `rosterPath` | `<data-root>/isolation-gate/roster.txt` | roster, one object per line |
|
|
86
|
+
| `backupDir` | `<backup-dir>` | backup before roster edits |
|
|
87
|
+
| `root` | `<drive-root>\` | root whose first-level directories are listed |
|
|
88
|
+
| `auditPath` | `~\.dsh\dsh-isolation-gate\access-log.jsonl` | audit log |
|
|
89
|
+
| `toolGate` | `true` | false = channel-only enforcement |
|
|
90
|
+
| `suspectVocabulary` | built-in `['pi','codex',…]` | extra "suspected harness directory" names (legacy `harnessVocabulary` still read) |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## State file (v2)
|
|
95
|
+
|
|
96
|
+
```jsonc
|
|
97
|
+
{
|
|
98
|
+
"version": 2,
|
|
99
|
+
"defaultLevel": "deny",
|
|
100
|
+
"groups": { "harness isolation": { "note": "", "updatedAt": "…" } },
|
|
101
|
+
"objects": {
|
|
102
|
+
"Zcode": { "level": "deny", "note": "", "group": "harness isolation", "updatedAt": "…" },
|
|
103
|
+
"E:\\data\\shared\\project": { "level": "read", "note": "", "group": "", "updatedAt": "…" }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- **v1 is still readable**: legacy `harnesses: { key: { level } }` is migrated on read, **levels are preserved**;
|
|
109
|
+
the next write persists v2.
|
|
110
|
+
- Missing file = **deny everything**; corrupt JSON = **deny everything and never overwrite it**.
|
|
111
|
+
- No BOM; writes go through temp-file + rename (atomic).
|
|
112
|
+
- `state.harnesses` remains a read-only in-memory alias; it is no longer written to disk.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Decision semantics
|
|
117
|
+
|
|
118
|
+
1. For a path, compute the **real path** first (realpath of the target, or "deepest existing ancestor realpath + remainder") — junctions cannot smuggle you in;
|
|
119
|
+
2. Find the **longest path-segment prefix** among path objects (case-insensitive; `<drive-root>\a\b` does not match `<drive-root>\a\bc`);
|
|
120
|
+
3. If no path object matches, fall back to the **first segment under the drive root** against name objects;
|
|
121
|
+
4. If nothing matches, the object is **not managed** and this channel allows it;
|
|
122
|
+
5. **Roster missing / unreadable ⇒ deny everything** (fail-closed), never "allow all".
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Tool guard (`ctx.tools.guard`)
|
|
127
|
+
|
|
128
|
+
- Covers `read / read_image / grep / glob / write / edit / str_replace_editor / apply_patch / mkdir / move / copy / delete / bash / pwsh / …`
|
|
129
|
+
- Shell commands are lexically decomposed: `cd` + relative paths, redirections, pipelines and `bash -c "…"` are followed;
|
|
130
|
+
- Unknown commands that mention a managed absolute path are treated as **writes** (allowed only at `full`) — false denials are preferred to false allows;
|
|
131
|
+
- If the guard itself throws, it **fails closed**;
|
|
132
|
+
- Calls to `bin/gate.mjs` itself are **exempt** (the channel performs its own check; otherwise it would double-block).
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## CLI
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
node bin/gate.mjs status
|
|
140
|
+
node bin/gate.mjs policy
|
|
141
|
+
node bin/gate.mjs check --name Zcode --op read
|
|
142
|
+
node bin/gate.mjs check --path "<drive-root>\Zcode\some-file" --op delete
|
|
143
|
+
node bin/gate.mjs traverse --path "<drive-root>\Zcode"
|
|
144
|
+
node bin/gate.mjs read --path "<drive-root>\Zcode\some-file"
|
|
145
|
+
node bin/gate.mjs write --path "<drive-root>\Zcode\some-file" --text "content"
|
|
146
|
+
node bin/gate.mjs delete --path "<drive-root>\Zcode\some-file" # backed up to the recycle bin first
|
|
147
|
+
node bin/gate.mjs rmdir --path "<drive-root>\Zcode\empty-dir" # empty directories only, never recursive
|
|
148
|
+
node bin/gate.mjs log --limit 20
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Exit codes**: `0` allowed / success · `3` denied (reason on stderr) · `2` usage or environment error.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## HTTP routes
|
|
156
|
+
|
|
157
|
+
| Method | Path | Purpose |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| GET | `/__dsh-isolation-gate/ping` | identity + route list + capabilities (`toolGate` / `picker`) |
|
|
160
|
+
| GET | `/__dsh-isolation-gate/state` | full state (roster / levels / groups / diffs / audit / policy lines) |
|
|
161
|
+
| POST | `/__dsh-isolation-gate/level` | `{ name, level }` |
|
|
162
|
+
| POST | `/__dsh-isolation-gate/roster` | `{ action:'add'\|'remove', name }` |
|
|
163
|
+
| POST | `/__dsh-isolation-gate/group-level` | `{ group, level }` |
|
|
164
|
+
| POST | `/__dsh-isolation-gate/group` | `{ action:'assign'\|'delete', name?, group }` |
|
|
165
|
+
| POST | `/__dsh-isolation-gate/prune` | remove orphan state keys |
|
|
166
|
+
| POST | `/__dsh-isolation-gate/pick` | native directory picker (400 when unavailable) |
|
|
167
|
+
| POST | `/__dsh-isolation-gate/toolcheck` | `{ tool, args }` dry-run of the tool guard |
|
|
168
|
+
| POST | `/__dsh-isolation-gate/check` | `{ name?\|path?, op }` |
|
|
169
|
+
| GET | `/__dsh-isolation-gate/log?limit=` | recent audit |
|
|
170
|
+
|
|
171
|
+
External `Origin` → 403; wrong method → 405; bad JSON / invalid level → 400.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## How it relates to other plugins
|
|
176
|
+
|
|
177
|
+
| Plugin | Scope | Relationship |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| `dsh-house-guard` (built-in here) | **System assets**: a fixed set of protected paths writable only by whitelisted butler sessions | Different purpose, disjoint objects; whichever denies wins |
|
|
180
|
+
| `PerryLink/dsh-permission-rules` | Rule engine: allow/deny/ask + tool params / shell / network | **Complementary**: it has no read-only level; we never write its YAML and do not depend on it |
|
|
181
|
+
| `cherrchen/dsh-plugin-multi-root-workspace` | **Adds** extra workspace roots (read-only / read-write) enforced by the kernel sandbox | **Complementary**: it grants, we fence; it is stronger at enforcement, we are stronger at management |
|
|
182
|
+
| `azazo1/dsh-write-protect` | Read-only protection inside the workspace + extra writable roots | Complementary; its shell protection has a documented Windows gap |
|
|
183
|
+
|
|
184
|
+
> This plugin declares **no `@deepseek-ai/dsh*` peer dependencies** — a DSH upgrade cannot silently disable it through the compatibility-exemption gate.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Known boundaries (read this)
|
|
189
|
+
|
|
190
|
+
1. **Raw filesystem access and separate processes are not blocked.** Any program or script that bypasses DSH tool calls can read/write the disk directly.
|
|
191
|
+
2. **Shell scanning is heuristic.** Variable indirection, command substitution, encoding, symlinks and `cmd /c` wrappers can bypass it — the same known limitation as comparable in-process guards.
|
|
192
|
+
3. **Not a kernel boundary.** It changes no ACLs and does not take over the sandbox.
|
|
193
|
+
4. **The tool guard only covers host tools.** An MCP tool that spawns its own process is constrained only by its argument text.
|
|
194
|
+
5. **Windows-first.** Path comparison follows Windows semantics; other platforms are untested.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Tests
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
node test/run-all.mjs # 4 suites · 309 assertions · fully offline
|
|
202
|
+
node --check lib/*.js
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Acknowledgements
|
|
208
|
+
|
|
209
|
+
- Design inspired by `PerryLink/dsh-permission-rules` (Apache-2.0) and Claude Code `permissions` — **no code or README text was copied**.
|
|
210
|
+
- Coexists with `@dsh-external/dsh-house-guard`; see the boundary table above.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT · see [LICENSE](./LICENSE).
|
package/README.md
CHANGED
|
@@ -1,3 +1,279 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center"><img src="docs/banner.svg" alt="门禁 · dsh-isolation-gate" width="100%"></p>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# 门禁 · dsh-isolation-gate
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-isolation-gate)
|
|
6
|
+
[](https://www.npmjs.com/package/dsh-isolation-gate)
|
|
7
|
+
[](https://github.com/zhaorui-wu/dsh-isolation-gate/stargazers)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
[](https://github.com/zhaorui-wu/dsh-isolation-gate/actions/workflows/ci.yml)
|
|
10
|
+
[](https://nodejs.org)
|
|
11
|
+
|
|
12
|
+
**给 DeepSeek Harness(DSH)的隔离区授权台:默认禁、名单制、点一下生效、可审计、agent 自己能查。**
|
|
13
|
+
|
|
14
|
+
[安装](#安装) · [功能](#功能) · [它拦在哪一层](#它拦在哪一层) · [CLI](#命令行--agent-自查) · [English](./README.en.md) · [更新日志](./CHANGELOG.md)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
dsh plugin --profile desktop add dsh-isolation-gate
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
> ⭐ **如果它帮你少踩一次坑,点个 Star 能让更多人找到它** —— 这是这个项目唯一的「传播预算」。
|
|
21
|
+
> [](https://github.com/zhaorui-wu/dsh-isolation-gate)
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 这是什么(以及不是什么)
|
|
28
|
+
|
|
29
|
+
它不是又一个 allow/deny 规则引擎,也不是沙箱本身。
|
|
30
|
+
|
|
31
|
+
- **别人做的是「怎么让 agent 多访问几个目录」**(multi-root / allowlist / write-protect)或「怎么用规则拦住工具」(permission-rules / safety-net);
|
|
32
|
+
- **我们做的是「这台机器上哪些目录属于禁区、现在给什么级别、谁改的、agent 自己怎么查」**。
|
|
33
|
+
|
|
34
|
+
所以它的核心是**管理面**:
|
|
35
|
+
|
|
36
|
+
| 能力 | 说明 |
|
|
37
|
+
|---|---|
|
|
38
|
+
| **三级** | `deny` 禁(默认) / `read` 只读 / `full` 可读写删 |
|
|
39
|
+
| **两类受管对象** | **名字**(D 根一级目录名,如 `Zcode`)或 **任意绝对路径**(如 `<drive-root>\Zcode`、`E:\资料\合作档案\某项目`) |
|
|
40
|
+
| **默认禁 + 名单制** | 只有名单里的对象受管;未列出的不归它管;新增对象一律先按「禁」处理 |
|
|
41
|
+
| **一屏开关** | 每个对象一个**分段控件**(禁 / 只读 / 可读写删),点了即落盘,重启后保持 |
|
|
42
|
+
| **分组** | 「harness 隔离」「跨家合作」这类命名组;组头一键切三档;组只存在状态文件里,名单仍是"一行一对象" |
|
|
43
|
+
| **选择文件夹** | 有原生目录选择器就用它;没有就退回「未入名单的一层目录一键加入 + 文本框」 |
|
|
44
|
+
| **两个拦截点** | ① 受管辖通道 `bin/gate.mjs`;② **宿主工具执行门** `ctx.tools.guard()`(2026-10-21 新增) |
|
|
45
|
+
| **审计** | `~/.dsh/dsh-isolation-gate/access-log.jsonl`:只记时间/对象/级别/操作/允许与否/理由,**不记文件内容** |
|
|
46
|
+
| **agent 自查** | `node bin/gate.mjs status` —— 模型/脚本可以直接读策略,不用猜 |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 它拦在哪一层
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
模型发起工具调用
|
|
54
|
+
│
|
|
55
|
+
├─ tools/pre-execute ← ★ 工具执行门(本插件 ctx.tools.guard)
|
|
56
|
+
│ file tools 参数 / shell 命令文本 → 按三级判定 → 拒绝文案回到模型
|
|
57
|
+
│
|
|
58
|
+
├─ 工具真正执行(read / write / edit / bash …)
|
|
59
|
+
│
|
|
60
|
+
└─ bin/gate.mjs ← ★ 受管辖通道(agent 显式走它读写受管对象)
|
|
61
|
+
判定不通过 ⇒ 一个字节都不碰
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
两条路都调用**同一个** `checkAccess`,所以面板、CLI、工具门不会说两套话。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 安装
|
|
69
|
+
|
|
70
|
+
### 路线 A:`dsh plugin add`(推荐,profile 自动追加一行)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
dsh plugin --profile desktop add dsh-isolation-gate
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> 桌面版(Electron)也可以走「插件」页的**添加插件**,填包名或本地路径。
|
|
77
|
+
|
|
78
|
+
### 路线 B:profile `link:`(开发/自用)
|
|
79
|
+
|
|
80
|
+
```jsonc
|
|
81
|
+
// ~/.dsh/profiles/<profile>/package.json
|
|
82
|
+
"dependencies": { "dsh-isolation-gate": "link:/absolute/path/to/dsh-isolation-gate" }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
# ~/.dsh/profiles/<profile>/cordis.patch.yml
|
|
87
|
+
- insert:
|
|
88
|
+
- id: isolation-gate
|
|
89
|
+
name: 'dsh-isolation-gate'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
⚠️ **改装配 = 要重启 dsh**。**开关本身不需要重启**:改级别、改名单都是即时落盘。
|
|
93
|
+
|
|
94
|
+
### 零配置默认值
|
|
95
|
+
|
|
96
|
+
| 配置 | 默认 | 说明 |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `statePath` | `<data-root>/isolation-gate/state.json` | 状态唯一真身 |
|
|
99
|
+
| `rosterPath` | `<data-root>/isolation-gate/roster.txt` | 名单(一行一对象) |
|
|
100
|
+
| `backupDir` | `<backup-dir>` | 改名单前备份 |
|
|
101
|
+
| `root` | `<drive-root>\` | 列"一层目录"的根 |
|
|
102
|
+
| `auditPath` | `~\.dsh\dsh-isolation-gate\access-log.jsonl` | 审计 |
|
|
103
|
+
| `toolGate` | `true` | 关掉就只剩通道层 |
|
|
104
|
+
| `suspectVocabulary` | `['pi','codex',…]` 内置 | 追加"疑似受管目录"名谱(旧键 `harnessVocabulary` 仍读) |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 状态文件(v2)
|
|
109
|
+
|
|
110
|
+
```jsonc
|
|
111
|
+
{
|
|
112
|
+
"version": 2,
|
|
113
|
+
"defaultLevel": "deny",
|
|
114
|
+
"groups": {
|
|
115
|
+
"harness 隔离": { "note": "", "updatedAt": "…" }
|
|
116
|
+
},
|
|
117
|
+
"objects": {
|
|
118
|
+
"Zcode": { "level": "deny", "note": "", "group": "harness 隔离", "updatedAt": "…" },
|
|
119
|
+
"E:\\资料\\合作档案\\某项目": { "level": "read", "note": "", "group": "", "updatedAt": "…" }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- **v1 仍可读**:旧的 `harnesses: { key: { level } }` 读入时自动迁移成 `objects`,**级别不丢**;下一次写盘才落成 v2。
|
|
125
|
+
- 文件缺失 = **全禁**;坏 JSON = **全禁且不覆盖原文件**。
|
|
126
|
+
- 无 BOM;写盘走临时文件 + rename(原子替换)。
|
|
127
|
+
- `state.harnesses` 在内存里保留为只读别名,磁盘上不再写它。
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 判定语义(别改坏)
|
|
132
|
+
|
|
133
|
+
1. 给路径时先算**真身路径**(目标 realpath,或"最深已存在祖先 realpath + 剩余片段")⇒ 从别处 junction 绕进来也会被认出来;
|
|
134
|
+
2. 在**路径对象**里找**最长前缀命中**(按路径段比较、忽略大小写,`<drive-root>\a\b` 不会命中 `<drive-root>\a\bc`);
|
|
135
|
+
3. 没命中路径对象 ⇒ 退回"盘根一级目录名"匹配**名字对象**;
|
|
136
|
+
4. 都不中 ⇒ **不属受管对象**,本通道放行(它只管辖名单内的东西);
|
|
137
|
+
5. **名单缺失 / 读坏 ⇒ 一律拒绝**(fail-closed),不是"放行一切"。
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 工具执行门(`ctx.tools.guard`)
|
|
142
|
+
|
|
143
|
+
- 覆盖:`read / read_image / grep / glob / write / edit / str_replace_editor / apply_patch / mkdir / move / copy / delete / bash / pwsh / …`
|
|
144
|
+
- shell 命令会做词法分解:`cd` + 相对路径、重定向、管道、`bash -c "…"` 都会跟进去;
|
|
145
|
+
- 未知命令只要命令文本里出现受管绝对路径,就**按写处理**(只在 `full` 放行)——宁可误拒,不可漏放;
|
|
146
|
+
- 判定自身抛错 ⇒ **fail-closed**(按拒绝处理);
|
|
147
|
+
- `bin/gate.mjs` 自身的调用**豁免**工具门(它自己会判定,否则会双重拦截)。
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## CLI(agent / 脚本用)
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
node bin/gate.mjs status # 全部级别
|
|
155
|
+
node bin/gate.mjs policy # 紧凑策略行(可塞进提示词)
|
|
156
|
+
node bin/gate.mjs check --name Zcode --op read
|
|
157
|
+
node bin/gate.mjs check --path "<drive-root>\Zcode\某文件" --op delete
|
|
158
|
+
node bin/gate.mjs traverse --path "<drive-root>\Zcode"
|
|
159
|
+
node bin/gate.mjs read --path "<drive-root>\Zcode\某文件"
|
|
160
|
+
node bin/gate.mjs write --path "<drive-root>\Zcode\某文件" --text "内容"
|
|
161
|
+
node bin/gate.mjs delete --path "<drive-root>\Zcode\某文件" # 删前自动备份到回收站
|
|
162
|
+
node bin/gate.mjs rmdir --path "<drive-root>\Zcode\空目录" # 只删空目录(不递归)
|
|
163
|
+
node bin/gate.mjs log --limit 20
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**退出码**:`0` 放行 / 成功 · `3` 被拒(拒绝文案在 stderr)· `2` 用错或环境错误。
|
|
167
|
+
|
|
168
|
+
护栏:判定在 I/O 之前;只删文件不删目录;删除前自动备份,备份失败即中止;名单不可用一律拒绝。
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## HTTP 路由(宿主半 · 本机同源)
|
|
173
|
+
|
|
174
|
+
| 方法 | 路径 | 作用 |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| GET | `/__dsh-isolation-gate/ping` | 身份 + 路由清单 + 能力(`toolGate` / `picker`) |
|
|
177
|
+
| GET | `/__dsh-isolation-gate/state` | 全量状态(名单 / 级别 / 分组 / 差异 / 审计 / 策略行) |
|
|
178
|
+
| POST | `/__dsh-isolation-gate/level` | `{ name, level }` 改一个对象 |
|
|
179
|
+
| POST | `/__dsh-isolation-gate/roster` | `{ action:'add'\|'remove', name }` 名单增删 |
|
|
180
|
+
| POST | `/__dsh-isolation-gate/group-level` | `{ group, level }` 组级一键切 |
|
|
181
|
+
| POST | `/__dsh-isolation-gate/group` | `{ action:'assign'\|'delete', name?, group }` |
|
|
182
|
+
| POST | `/__dsh-isolation-gate/prune` | 清理孤儿键 |
|
|
183
|
+
| POST | `/__dsh-isolation-gate/pick` | 调原生目录选择器(无则 400,面板自动走退路) |
|
|
184
|
+
| POST | `/__dsh-isolation-gate/toolcheck` | `{ tool, args }` 工具门**干跑**(不执行工具) |
|
|
185
|
+
| POST | `/__dsh-isolation-gate/check` | `{ name?\|path?, op }` 判定(不读内容) |
|
|
186
|
+
| GET | `/__dsh-isolation-gate/log?limit=` | 最近审计 |
|
|
187
|
+
|
|
188
|
+
带外部 `Origin` 的请求一律 403;错误方法 405;坏 JSON / 非法级别 400。
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 与其它插件的分工
|
|
193
|
+
|
|
194
|
+
| 插件 | 它管什么 | 与本插件 |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `dsh-house-guard`(本机内建) | **体系资产**:固定几个受保护路径,只有白名单管家会话可写 | 目的不同、对象不重叠;两边都拒则拒(更严者胜) |
|
|
197
|
+
| `PerryLink/dsh-permission-rules` | 规则引擎:allow/deny/ask + 工具参数/shell/网络 | **互补**:它没有「只读」这一档;我们的面板不写它的 YAML,**不依赖**它 |
|
|
198
|
+
| `cherrchen/dsh-plugin-multi-root-workspace` | 给工作区**加**附加根(只读/读写),靠内核沙箱执行 | **互补**:它是"扩权",我们是"划禁区";它强在拦截强度,我们强在管理面与分组 |
|
|
199
|
+
| `azazo1/dsh-write-protect` | 工作区内只读保护 + 额外可写根 | 互补;Windows 上它的 bash 侧有明确缺口 |
|
|
200
|
+
|
|
201
|
+
> 本插件**不声明任何 `@deepseek-ai/dsh*` peer 依赖** —— dsh 升级不会因"版本豁免失效"被静默禁用。
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 已知边界(必须明说)
|
|
206
|
+
|
|
207
|
+
1. **raw fs 与独立进程拦不住。** 任何程序、脚本、别的 agent 不经过 DSH 工具调用就直接读写磁盘,本插件看不到。
|
|
208
|
+
它拦的是**经过 DSH 的工具调用**与**走 `bin/gate.mjs` 的通道**。
|
|
209
|
+
2. **shell 是文本启发式。** 变量间接、命令替换、编码、符号链接、`cmd /c` 套一层等都可能绕过;
|
|
210
|
+
与同类实现同一层级的已知限制。要更强,需要内核沙箱(Windows ACL / Seatbelt / bwrap)。
|
|
211
|
+
3. **不是内核级闸门。** 它不修改任何系统 ACL、不接管沙箱。
|
|
212
|
+
4. **工具门只覆盖宿主工具。** MCP 工具如果自己开进程读文件,同样只受其参数文本约束。
|
|
213
|
+
5. **Windows 优先**:路径比较按 Windows 语义(盘符、`\`、大小写不敏感);其他平台未验证。
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 自测
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
node test/run-all.mjs # 4 个套件 · 325 断言 · 全离线(不需要 GUI/dsh)
|
|
221
|
+
node --check lib/*.js
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
| 套件 | 覆盖 |
|
|
225
|
+
|---|---|
|
|
226
|
+
| `run-data-layer.mjs` | 状态 v1→v2 迁移 / 无 BOM / 坏 JSON 不覆盖 / 路径键 / 名单备份 / 分组 / 孤儿清理 |
|
|
227
|
+
| `run-guard.mjs` | 三级×四操作矩阵 / 最长前缀 / 同前缀不误伤 / fail-closed / 真实 I/O 闭环 |
|
|
228
|
+
| `run-routes.mjs` | 11 条路由 / 组级切 / 工具门挂载与干跑 / 选择器缺省 / 级别落盘 / 403/405/400 |
|
|
229
|
+
| `run-client.mjs` | 两座位注册 / 摘要条 / 分组卡片 / 分段控件 / `⋯` 菜单 / 空态 / 交互接线 |
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 致谢
|
|
234
|
+
|
|
235
|
+
- 设计受 `PerryLink/dsh-permission-rules`(Apache-2.0)与 Claude Code `permissions` 的**思路**启发;
|
|
236
|
+
**未复制其任何代码或 README 文字**。
|
|
237
|
+
- 与 `@dsh-external/dsh-house-guard` 同机共存,边界见上表。
|
|
238
|
+
|
|
239
|
+
## 许可
|
|
240
|
+
|
|
241
|
+
MIT · 见 [LICENSE](./LICENSE)。
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 路线图
|
|
247
|
+
|
|
248
|
+
**已在做 / 下一步**
|
|
249
|
+
|
|
250
|
+
- [ ] 分组卡片截图与 30 秒演示 GIF(欢迎贡献录屏)
|
|
251
|
+
- [ ] `gate export --csv`:把审计日志导出成表格
|
|
252
|
+
- [ ] 临时授权:给某对象一个到期时间,到点自动回到 `deny`
|
|
253
|
+
- [ ] 状态文件并发锁(多窗口同时改不互相覆盖)
|
|
254
|
+
- [ ] 拒绝原因解释器:告诉模型「是哪条规则、哪段路径命中的」
|
|
255
|
+
|
|
256
|
+
**明确不做**
|
|
257
|
+
|
|
258
|
+
- ❌ 内核级沙箱(该交给 Windows ACL / VM)
|
|
259
|
+
- ❌ 云端同步 / 账号体系(本插件只认本机状态文件)
|
|
260
|
+
|
|
261
|
+
有想法请开 [Issue](https://github.com/zhaorui-wu/dsh-isolation-gate/issues),说清处境比说清方案更有用。
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 参与
|
|
266
|
+
|
|
267
|
+
- 装不上 / 不会配 → [Discussions](https://github.com/zhaorui-wu/dsh-isolation-gate/discussions)
|
|
268
|
+
- 有 Bug → [Bug 报告](https://github.com/zhaorui-wu/dsh-isolation-gate/issues/new?template=bug_report.yml)
|
|
269
|
+
- 想加功能 → [功能建议](https://github.com/zhaorui-wu/dsh-isolation-gate/issues/new?template=feature_request.yml)
|
|
270
|
+
- 改代码 → [CONTRIBUTING.md](./CONTRIBUTING.md)(`npm test` 全绿即可提 PR)
|
|
271
|
+
- 安全问题 → [SECURITY.md](./SECURITY.md)(不要开公开 Issue)
|
|
272
|
+
|
|
273
|
+
## Star
|
|
274
|
+
|
|
275
|
+
如果这个插件帮你避免了一次误删、少配了一次 ACL、或者只是让你看清了「哪些目录其实一直开着」——
|
|
276
|
+
|
|
277
|
+
**点一下右上角的 ⭐ Star**。它不会改变代码,但会让更多人在搜索 `dsh plugin` 时看到它。
|
|
278
|
+
|
|
279
|
+
[](https://star-history.com/#zhaorui-wu/dsh-isolation-gate&Date)
|