dsh-perm-gate 2.0.0 → 2.1.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/INSTALL.ja.md +57 -1
  3. package/INSTALL.ko.md +54 -1
  4. package/INSTALL.md +53 -1
  5. package/INSTALL.zh.md +48 -1
  6. package/README.ja.md +86 -12
  7. package/README.ko.md +83 -12
  8. package/README.md +120 -24
  9. package/README.zh.md +88 -10
  10. package/cordis.patch.yml +34 -2
  11. package/dsh.plugin.json +22 -0
  12. package/lib/agent-identity.d.ts +43 -0
  13. package/lib/agent-identity.js +95 -0
  14. package/lib/client.js +114 -0
  15. package/lib/client.js.map +1 -1
  16. package/lib/compiler.d.ts +39 -0
  17. package/lib/compiler.js +106 -0
  18. package/lib/config.d.ts +95 -2
  19. package/lib/config.js +28 -3
  20. package/lib/engine.js +9 -2
  21. package/lib/evaluate.d.ts +9 -0
  22. package/lib/evaluate.js +189 -0
  23. package/lib/events.d.ts +13 -0
  24. package/lib/events.js +31 -0
  25. package/lib/index.d.ts +1 -1
  26. package/lib/index.js +235 -6
  27. package/lib/network-lifecycle.d.ts +107 -0
  28. package/lib/network-lifecycle.js +180 -0
  29. package/lib/network.d.ts +109 -0
  30. package/lib/network.js +196 -0
  31. package/lib/proxy.d.ts +160 -0
  32. package/lib/proxy.js +529 -0
  33. package/lib/risk.js +25 -3
  34. package/lib/rule-chain.d.ts +39 -0
  35. package/lib/rule-chain.js +222 -0
  36. package/lib/rule-dims.d.ts +139 -0
  37. package/lib/rule-dims.js +194 -0
  38. package/lib/rule.d.ts +25 -0
  39. package/lib/rule.js +15 -1
  40. package/lib/runtime.d.ts +87 -0
  41. package/lib/runtime.js +327 -2
  42. package/lib/session-sweep.d.ts +45 -0
  43. package/lib/session-sweep.js +153 -0
  44. package/lib/shadow.d.ts +34 -0
  45. package/lib/shadow.js +143 -0
  46. package/lib/watch.d.ts +42 -0
  47. package/lib/watch.js +170 -0
  48. package/package.json +8 -2
  49. package/scripts/patch-permission-glyph.mjs +209 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,117 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.1.2] - 2026-09-15
9
+
10
+ ### Added
11
+
12
+ - **The composer glyph patch now ships with the package.** `scripts/patch-permission-glyph.mjs`
13
+ is whitelisted in `files` and exposed as the **`dsh-perm-gate-patch-glyph`** bin, so re-applying
14
+ the host-bundle patch after a DSH upgrade no longer needs a source checkout:
15
+
16
+ ```sh
17
+ npx dsh-perm-gate-patch-glyph # apply
18
+ npx dsh-perm-gate-patch-glyph --check # report only; exits 1 if the glyph is missing
19
+ ```
20
+
21
+ In a checkout, `npm run patch:glyph` and `npm run patch:glyph:check` do the same.
22
+
23
+ It is deliberately **not** a `postinstall`. The script edits a **host** package, and a plugin
24
+ must not rewrite the harness it is installed into without being asked. (pnpm 10+ blocks
25
+ install scripts by default unless they are allowlisted, so a `postinstall` would also have
26
+ been a promise that silently never runs — worse than an explicit command.)
27
+
28
+ ### Changed
29
+
30
+ - The script is now testable: its pure parts are exported (`sliceEntry`, `applyGlyphPatch`,
31
+ `candidateBundles`, `findBundle`) and pinned by `test/glyph-patch.spec.ts` (11 cases). The
32
+ bracket-balance invariant is the one that matters — the first version of the slicer scanned
33
+ from the wrong bracket and produced an unparseable bundle, which only the script's own
34
+ `node --check` guard caught.
35
+ - `candidateBundles` additionally probes the hoisted per-profile scope under `$DSH_HOME`, and the
36
+ CLI is a no-op when imported (the main block runs only when the file is the entry point).
37
+
38
+ ## [2.1.1] - 2026-09-15
39
+
40
+ ### Added
41
+
42
+ - **Network execution surface (opt-in, off by default).** A local HTTP/CONNECT proxy adjudicates
43
+ the outbound traffic of *shell subprocesses* against the same rules file, plus an interactive
44
+ approval path for targets no rule covers. A `deny` rule is never escalated — approval widens
45
+ reach for an unlisted target, but can never override a rule that says no. New config:
46
+ `networkEnabled` (default `false`), `networkMode`, `networkUnlisted`, `networkUnattributed`,
47
+ `networkBind`, `networkPort`, `networkNoProxy`, `networkInjectEnv`, `networkAskTimeoutMs`,
48
+ `networkGrantTtlMs`. Diagnostics at `GET /api/dsh-perm-gate/network`. Covered by
49
+ `test/network.spec.ts`, `test/proxy-errors.spec.ts`, `test/network-lifecycle.spec.ts` and
50
+ `test/network-approval.spec.ts`.
51
+ - **Two 自动审查 tiers.** `permissive` keeps the built-in file sandbox; `permissive-full`
52
+ (label 自动审查(高权限)) pairs the same approval behaviour with `danger-full-access`. A preset's
53
+ `sandbox` and `approval` are independent knobs, and coupling them forced a trade: the
54
+ `workspace-write` sandbox also denies the named pipes a child process needs to start, so
55
+ `git clone`, MSYS2/Cygwin `sh.exe` and ConPTY failed under the only tier the gate was active in.
56
+ Both tiers are in the default `gatePresets`.
57
+ - **Rule model expansion.** Six new match dimensions (`params` / `absent` / `agents` / `when` /
58
+ `argv` / `network`), a multi-file rule chain with `searchUp` and a fallback path, and shadow
59
+ detection for unreachable rules. New config: `searchUp`, `fallbackPath`, `badFilePolicy`,
60
+ `maxChainLength`.
61
+ - **Rule hot reload.** A chokidar watcher reloads the effective rule files, with debounce, LRU
62
+ eviction across workspaces, and candidate-file watching through the deepest existing ancestor so
63
+ a rules file created mid-session is adopted. New config: `watch` (default `true`),
64
+ `watchDebounceMs`.
65
+
66
+ ### Fixed
67
+
68
+ - **A blocked connection could kill the host.** When the proxy answered a CONNECT with 403, the
69
+ client's RST produced an `ECONNRESET` with no handler attached yet, which escalated to an
70
+ unhandled `'error'` event and terminated the DSH process. Socket error handlers are now attached
71
+ at connection time, with `clientError` and handler-rejection fallbacks behind them. A policy
72
+ proxy must never take down its host.
73
+ - **Built-in tools prompted for approval.** The auto-allow roster was hand-maintained, so tools
74
+ added to DSH since drifted out of it and a read-only call could raise an approval prompt:
75
+ `advanced_search`, `platform_search`, `free_search_test`, `context_compression_retrieve`,
76
+ `memory_search_graph`, `memory_expand_graph_node`, `memory_import`, `memory_ruminate`,
77
+ `memory_ruminate_cancel`, `memory_ruminate_status`.
78
+ - **Proxy lifecycle races.** `close()` now awaits an in-flight bind instead of racing it, is
79
+ bounded, and concurrent `start()` calls share one bind. DNS lookups are time-bounded, the
80
+ connection-setup phase is bounded, concurrent connections are capped, and a throwing logger can
81
+ no longer escalate into a crash. Proxy teardown is registered before the bind is awaited, so an
82
+ early dispose cannot leak a bound port or a rewritten `process.env`.
83
+ - **`permissive-full` had no icon in the composer picker.** The picker's glyph map is closed and,
84
+ by its own comment, gives host-configured names none, so 自动审查(高权限)rendered text-only
85
+ while 自动审查 showed shield+eye. `scripts/patch-permission-glyph.mjs` adds the missing entry to
86
+ that **host** map: idempotent, bracket-depth slicing, and it `node --check`s its own output and
87
+ restores the backup on failure. It patches a host package, so it must be re-run after every DSH
88
+ upgrade — reinstalling the plugin does **not** restore it, because `dsh plugin … add` only writes
89
+ the profile's own `node_modules`. Documented in all four INSTALL guides.
90
+
91
+ ### Changed
92
+
93
+ - `networkUnattributed` defaults to `allow`: traffic with no shell attribution is DSH's own client
94
+ (a built-in network tool, the LLM transport), and reviewing it would let the host block itself.
95
+ Set `deny` for the stricter behaviour.
96
+ - `networkUnlisted` defaults to `ask`.
97
+ - `DEFAULT_GATE_PRESETS` is now `['permissive', 'permissive-full']`.
98
+
99
+ ### Documented
100
+
101
+ - **The network proxy is a cooperative policy layer, not an enforcement boundary.** It only sees
102
+ clients that read the proxy environment. Measured, not assumed: `curl` routes through it, while
103
+ `node` `http`/`https`/`fetch` connect directly, as do raw sockets, DNS, QUIC, literal-IP targets
104
+ and Java/.NET default clients. Stated in all four READMEs.
105
+
106
+ - **Session sweep — the authorization chain now follows the session lifecycle.** On plugin startup
107
+ and every hour, the gate reads DSH's workspace store (`$DSH_HOME/storages/workspace.json`,
108
+ read-only) and classifies every session it holds gate data for. Sessions DSH has archived
109
+ (`global.archivedSessionIds`) or no longer tracks at all have their decision events dropped from
110
+ `$DSH_HOME/perm-gate/events.jsonl` (atomic tmp+rename rewrite, only when something is removed)
111
+ and their pre-change snapshot files deleted from `$DSH_HOME/perm-gate/snapshots/`. Live sessions
112
+ are untouched; unattributable rows (empty session id, unparsable line/file) are never deleted;
113
+ every I/O failure is fail-open (the round is skipped and retried an hour later); the hourly
114
+ timer is `unref`'d and disposed with the plugin. New config: `sessionSweep` (default `true`),
115
+ `workspaceStoreFile` (default `<dshHome>/storages/workspace.json`). Note that restoring an
116
+ archived session does not restore its swept history. Covered by `test/session-sweep.spec.ts`
117
+ (13 unit cases) and `test/session-sweep-apply.spec.ts` (end-to-end wiring).
118
+
8
119
  ## [2.0.0] - 2026-09-11
9
120
 
10
121
  ### Fixed
package/INSTALL.ja.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- `dsh-perm-gate` バージョン **2.0.0**。判定チェーン、ルールファイル形式、自动审查
15
+ `dsh-perm-gate` バージョン **2.1.2**。判定チェーン、ルールファイル形式、自动审查
16
16
  ティアについては [日本語 README](./README.ja.md) を参照してください。
17
17
 
18
18
  ## 必要条件
@@ -87,6 +87,56 @@ dsh plugin --profile web update dsh-perm-gate
87
87
  dsh profile reload --profile web
88
88
  ```
89
89
 
90
+ ## **DSH** アップグレード後:入力欄アイコンのパッチを再適用
91
+
92
+ 「自动审查(高权限)」が「自动审查」と同じ盾+目のアイコンを表示するのは、
93
+ `scripts/patch-permission-glyph.mjs` が **DSH ホストパッケージ内の閉じた Map** に
94
+ その項目を追加したからにすぎません。DSH はプラグイン提供ティアに**設計上アイコンを
95
+ 与えません** —— その Map 自身のコメントが *"host-configured names outside the design
96
+ set get none"* と述べています。プラグインが影響できる option オブジェクトは
97
+ `{value, name, description}` しか運ばないため、代わりに使えるプラグイン側のシームは
98
+ 存在しません。
99
+
100
+ このパッチは**ホスト**のファイルを書き換えるので、**DSH のアップグレードや再インストールで
101
+ 失われます**。*本プラグイン*のアップグレードでは失われません。アイコンは元々プラグインの
102
+ ものではなく、プラグイン自身の寄与(`cordis.patch.yml` の `name:` / `description:`)は
103
+ パッケージに同梱されています。
104
+
105
+ ```sh
106
+ npx dsh-perm-gate-patch-glyph # パッチを適用
107
+ npx dsh-perm-gate-patch-glyph --check # 確認のみ。グリフが失われていれば終了コード 1
108
+ dsh profile reload --profile web
109
+ ```
110
+
111
+ スクリプトは**このパッケージに同梱**されています —— `scripts/patch-permission-glyph.mjs`
112
+ としても、`dsh-perm-gate-patch-glyph` bin としても —— ソースの checkout は不要です。
113
+ **意図的に `postinstall` には接続していません**:これはホストパッケージを書き換えるもので、
114
+ プラグインが許可なく自分の harness を書き換えてよい理由はありません。適用の有無にかかわらず
115
+ DSH の動作に影響はなく、ティア自体はそのまま機能します。
116
+
117
+ このスクリプトは冪等(2 回目は no-op)で、バックアップは 1 度だけ取り、壊れたスライスを
118
+ 書き込もうとしません —— 結果に対して `node --check` を実行し、失敗すればバックアップを
119
+ 復元します —— したがって DSH アップグレードのたびに無条件で再実行して安全です。
120
+ 実行中の `node` バイナリからパッケージを逆算するため、nvm のバージョン変更や
121
+ インストール symlink の貼り替えでも壊れません。
122
+
123
+ **プラグインを再インストールしてもアイコンは戻りません。**
124
+ `dsh plugin --profile web add …` は profile ディレクトリ内の pnpm に引数を転送し、
125
+ profile 自身の `node_modules` しか書き換えません(`-w` = `--workspace-root` ですが、
126
+ この profile の workspace は `packages: ['.']` そのものなので関係ありません)。
127
+ アイコンは DSH インストール側にあります。標準的なインストールでは、以下の 3 つの
128
+ パスは 3 つのコピーではなく **1 つの物理ファイル**です:
129
+
130
+ | パス | 実体 |
131
+ |------|------|
132
+ | `dirname(node)/node_modules/@deepseek-ai/dsh` | DSH インストール先(symlink のことがある) |
133
+ | `<profile>/node_modules/@deepseek-ai/dsh-client-ui-conversation` | そこへの**ジャンクション** |
134
+ | `<dsh>/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js` | パッチ対象のファイル |
135
+
136
+ 見るべき症状:ティアは動作しゲートも効いたままですが、入力欄のドロップダウンの行に
137
+ アイコンがなく、折りたたまれたトリガーは他のティアがアイコン+文字なのに対して
138
+ 文字だけになります。
139
+
90
140
  ## 分割プラグインからの移行
91
141
 
92
142
  `dsh-perm-gate` は、これまで `dsh-permission-rules` / `dsh-auto-mode` /
@@ -144,6 +194,12 @@ DSH の bundle patch は `permission.config.presets` を key 単位でマージ
144
194
  します。profile を再読込して `cordis.patch.yml` を再適用し、後から読み込まれる
145
195
  プラグインが `presets` を上書きしていないか確認してください。
146
196
 
197
+ **「自动审查(高权限)」の入力欄アイコンが消えた。**
198
+ DSH のアップグレードまたは再インストールが、パッチを当てたホスト bundle を置き換え
199
+ ました。プラグインを再インストールしても戻りません。
200
+ `npx dsh-perm-gate-patch-glyph` を実行して再読込してください。
201
+ ティア自体は影響を受けません —— パッチなしでもラベルとゲートは動作します。
202
+
147
203
  **ルールファイルがあるのに `--list` が `ruleCount: 0` を返す。**
148
204
  `rulesFile` はプラグイン ディレクトリではなく Harness プロセスの CWD から解決されます。
149
205
  絶対パスを使うか、シェルの CWD を確認してください。不正なドキュメントは読み込み時に
package/INSTALL.ko.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- `dsh-perm-gate` 버전 **2.0.0**. 결정 체인, 규칙 파일 형식, 自动审查 티어는
15
+ `dsh-perm-gate` 버전 **2.1.2**. 결정 체인, 규칙 파일 형식, 自动审查 티어는
16
16
  [한국어 README](./README.ko.md)를 참고하세요.
17
17
 
18
18
  ## 요구 사항
@@ -88,6 +88,53 @@ dsh plugin --profile web update dsh-perm-gate
88
88
  dsh profile reload --profile web
89
89
  ```
90
90
 
91
+ ## **DSH** 업그레이드 후: 입력창 아이콘 패치 재적용
92
+
93
+ 「自动审查(高权限)」가 「自动审查」와 같은 방패+눈 아이콘을 보여 주는 것은
94
+ `scripts/patch-permission-glyph.mjs`가 **DSH 호스트 패키지 안의 닫힌 Map**에 그 항목을
95
+ 추가했기 때문일 뿐입니다. DSH는 플러그인이 제공한 티어에 **설계상 아이콘을 주지
96
+ 않습니다** —— 그 Map 자체의 주석이 *"host-configured names outside the design set get
97
+ none"*이라고 말합니다. 플러그인이 영향을 줄 수 있는 option 객체는
98
+ `{value, name, description}`만 나르므로, 대신 쓸 수 있는 플러그인 측 이음새가 없습니다.
99
+
100
+ 이 패치는 **호스트** 파일을 고치므로 **DSH 업그레이드나 재설치에서 사라집니다**.
101
+ *이 플러그인*을 업그레이드할 때는 사라지지 않습니다. 아이콘은 애초에 플러그인 소유가
102
+ 아니었고, 플러그인 자신의 기여(`cordis.patch.yml`의 `name:` / `description:`)는
103
+ 패키지에 함께 실려 나갑니다.
104
+
105
+ ```sh
106
+ npx dsh-perm-gate-patch-glyph # 패치 적용
107
+ npx dsh-perm-gate-patch-glyph --check # 확인만. 글리프가 사라졌으면 종료 코드 1
108
+ dsh profile reload --profile web
109
+ ```
110
+
111
+ 스크립트는 **이 패키지에 함께 실려 나갑니다** —— `scripts/patch-permission-glyph.mjs`로도,
112
+ `dsh-perm-gate-patch-glyph` bin으로도 —— 소스 checkout이 필요 없습니다.
113
+ **의도적으로 `postinstall`에 연결하지 않았습니다**: 이 스크립트는 호스트 패키지를 고치며,
114
+ 플러그인이 허락 없이 자신이 설치된 harness를 다시 쓸 이유는 없습니다. 적용 여부와 무관하게
115
+ DSH 동작에는 영향이 없고 티어 자체는 그대로 기능합니다.
116
+
117
+ 이 스크립트는 멱등이며(두 번째 실행은 no-op), 백업은 한 번만 뜨고, 잘못 잘린 조각을
118
+ 쓰지 않습니다 —— 결과에 `node --check`를 돌려 실패하면 백업을 복원합니다 —— 따라서
119
+ DSH 업그레이드 때마다 무조건 다시 실행해도 안전합니다. 실행 중인 `node` 바이너리에서
120
+ 패키지를 역산하므로 nvm 버전 변경이나 설치 symlink 재지정에도 깨지지 않습니다.
121
+
122
+ **플러그인을 재설치해도 아이콘은 돌아오지 않습니다.**
123
+ `dsh plugin --profile web add …`는 profile 디렉터리 안의 pnpm으로 인자를 넘기고
124
+ profile 자신의 `node_modules`만 씁니다(`-w` = `--workspace-root`인데, 이 profile의
125
+ workspace는 `packages: ['.']` 그 자체이므로 달라지는 것이 없습니다). 아이콘은 DSH
126
+ 설치 쪽에 있습니다. 표준 설치에서 아래 세 경로는 사본 세 개가 아니라
127
+ **하나의 물리 파일**입니다:
128
+
129
+ | 경로 | 실체 |
130
+ |------|------|
131
+ | `dirname(node)/node_modules/@deepseek-ai/dsh` | DSH 설치 위치(symlink일 수 있음) |
132
+ | `<profile>/node_modules/@deepseek-ai/dsh-client-ui-conversation` | 그곳을 가리키는 **정션(junction)** |
133
+ | `<dsh>/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js` | 패치 대상 파일 |
134
+
135
+ 관찰할 증상: 티어는 여전히 동작하고 게이트도 그대로지만, 입력창 드롭다운의 그 행에
136
+ 아이콘이 없고 접힌 트리거가 다른 티어의 아이콘+텍스트와 달리 텍스트만 표시됩니다.
137
+
91
138
  ## 분리된 플러그인에서 이전하기
92
139
 
93
140
  `dsh-perm-gate`는 `dsh-permission-rules`, `dsh-auto-mode`, `dsh-auto-review`,
@@ -143,6 +190,12 @@ DSH 번들 패치는 `permission.config.presets`를 key 단위로 병합하지
143
190
  합니다. profile을 다시 불러와 `cordis.patch.yml`을 재적용하고, 더 늦게 로드되는
144
191
  플러그인이 `presets`를 덮어쓰지 않는지 확인하세요.
145
192
 
193
+ **「自动审查(高权限)」의 입력창 아이콘이 사라졌습니다.**
194
+ DSH 업그레이드나 재설치가 패치해 둔 호스트 번들을 교체했습니다. 플러그인을
195
+ 재설치해도 되돌아오지 않습니다. `npx dsh-perm-gate-patch-glyph`를 실행하고
196
+ 다시 불러오세요. 티어 자체는 영향받지 않습니다 —— 패치가 없어도 라벨과 게이트는
197
+ 정상 동작합니다.
198
+
146
199
  **규칙 파일이 있는데 `--list`가 `ruleCount: 0`을 표시합니다.**
147
200
  `rulesFile`은 플러그인 디렉터리가 아니라 Harness 프로세스의 CWD에서 해석됩니다.
148
201
  절대 경로를 쓰거나 셸의 CWD를 확인하세요. 잘못된 문서는 로드 시 loud-fail하며
package/INSTALL.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- `dsh-perm-gate` version **2.0.0**. Continue with the [README](./README.md) for the
15
+ `dsh-perm-gate` version **2.1.2**. Continue with the [README](./README.md) for the
16
16
  decision chain, the rules file format and the 自动审查 tier.
17
17
 
18
18
  ## Requirements
@@ -88,6 +88,52 @@ Then re-apply the profile so the patch file is re-read:
88
88
  dsh profile reload --profile web
89
89
  ```
90
90
 
91
+ ## After a **DSH** upgrade: re-apply the composer glyph patch
92
+
93
+ The 自动审查(高权限) tier shows the same shield+eye glyph as 自动审查 only because
94
+ `scripts/patch-permission-glyph.mjs` added it to a **closed map inside a DSH host
95
+ package**. DSH gives host-configured tiers no glyph by design — the map's own
96
+ comment reads *"host-configured names outside the design set get none"* — and the
97
+ option objects a plugin can influence carry only `{value, name, description}`, so
98
+ there is no plugin-side seam to use instead.
99
+
100
+ That patch edits a **host** file, so a DSH upgrade or reinstall erases it. Upgrading
101
+ *this plugin* does not: the plugin never owned the glyph, and its own contribution
102
+ (`name:` / `description:` in `cordis.patch.yml`) ships inside the package.
103
+
104
+ ```sh
105
+ npx dsh-perm-gate-patch-glyph # apply the patch
106
+ npx dsh-perm-gate-patch-glyph --check # report only; exits 1 if the glyph is gone
107
+ dsh profile reload --profile web
108
+ ```
109
+
110
+ The script **ships inside this package** — as `scripts/patch-permission-glyph.mjs` and as
111
+ the `dsh-perm-gate-patch-glyph` bin — so no source checkout is needed. It is deliberately
112
+ **not** wired to `postinstall`: it edits a host package, and a plugin must not rewrite its
113
+ harness uninvited. Running DSH is unaffected either way; the tier works with or without it.
114
+
115
+ It is idempotent (a second run is a no-op), backs the bundle up once, and refuses to
116
+ write a mis-sliced bundle — it runs `node --check` on the result and restores the
117
+ backup if that fails — so it is safe to run unconditionally after every DSH upgrade.
118
+ It locates the bundle from the running `node` binary, so an nvm version change or a
119
+ re-pointed install symlink does not break it.
120
+
121
+ **Reinstalling the plugin does not restore the glyph.** `dsh plugin --profile web
122
+ add …` forwards to pnpm inside the profile directory and writes the profile's own
123
+ `node_modules` only; `-w` (`--workspace-root`, and this profile's workspace is just
124
+ `packages: ['.']`) changes nothing about that. The glyph lives in the DSH install.
125
+ On a stock install these are not three copies but **one physical file**:
126
+
127
+ | Path | What it is |
128
+ |------|------------|
129
+ | `dirname(node)/node_modules/@deepseek-ai/dsh` | the DSH install (may be a symlink) |
130
+ | `<profile>/node_modules/@deepseek-ai/dsh-client-ui-conversation` | a **junction** into it |
131
+ | `<dsh>/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js` | the patched file |
132
+
133
+ Symptom to look for: the tier still works and still gates correctly, but its row in
134
+ the composer dropdown has no icon and the collapsed trigger renders plain text
135
+ where the other tiers render glyph + text.
136
+
91
137
  ## Migrate from the split plugins
92
138
 
93
139
  `dsh-perm-gate` merges the gate, the approval seam and the (optional) classifier
@@ -145,6 +191,12 @@ The DSH bundle patch replaces the whole `permission.config.presets` map instead
145
191
  merging per key. Reload the profile so `cordis.patch.yml` is re-applied, and make
146
192
  sure no later plugin overwrites `presets`.
147
193
 
194
+ **自动审查(高权限) lost its icon in the composer.**
195
+ A DSH upgrade or reinstall replaced the host bundle the glyph was patched into;
196
+ re-running the plugin install will not bring it back. Run
197
+ `npx dsh-perm-gate-patch-glyph` and reload. The tier itself is
198
+ unaffected — its label and its gating keep working without the patch.
199
+
148
200
  **`--list` reports `ruleCount: 0` although my rules file exists.**
149
201
  `rulesFile` is resolved against the process CWD of the harness, not the plugin
150
202
  directory. Prefer an absolute path or confirm the shell CWD. A malformed document
package/INSTALL.zh.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- `dsh-perm-gate` 版本 **2.0.0**。裁决链、规则文件格式与自动审查档位请见
15
+ `dsh-perm-gate` 版本 **2.1.2**。裁决链、规则文件格式与自动审查档位请见
16
16
  [中文 README](./README.zh.md)。
17
17
 
18
18
  ## 前置条件
@@ -85,6 +85,48 @@ dsh plugin --profile web update dsh-perm-gate
85
85
  dsh profile reload --profile web
86
86
  ```
87
87
 
88
+ ## **DSH** 升级后:重打输入区图标补丁
89
+
90
+ 「自动审查(高权限)」之所以和「自动审查」一样显示盾+眼图标,只是因为
91
+ `scripts/patch-permission-glyph.mjs` 把这一项加进了 **DSH 宿主包里的一张封闭 Map**。
92
+ DSH 对插件档位**设计上就不给图标** —— 那张表自己的注释写着 *"host-configured
93
+ names outside the design set get none"*;而插件能影响的 option 对象只携带
94
+ `{value, name, description}`,所以插件侧没有可用的接缝。
95
+
96
+ 该补丁改的是**宿主**文件,因此 **DSH 升级或重装会把它抹掉**。升级*本插件*不会:
97
+ 图标从来不属于插件,而插件自己的贡献(`cordis.patch.yml` 里的 `name:` /
98
+ `description:`)随包发布。
99
+
100
+ ```sh
101
+ npx dsh-perm-gate-patch-glyph # 应用补丁
102
+ npx dsh-perm-gate-patch-glyph --check # 只检查;图标已丢失时退出码为 1
103
+ dsh profile reload --profile web
104
+ ```
105
+
106
+ 脚本**随本包分发** —— 既是 `scripts/patch-permission-glyph.mjs`,也是一个
107
+ `dsh-perm-gate-patch-glyph` bin —— 所以不需要源码 checkout。它**故意没有**挂在
108
+ `postinstall` 上:它改的是宿主包,插件不该未经许可改写自己所在的 harness。有没有它
109
+ 都不影响 DSH 运行,档位本身照常工作。
110
+
111
+ 脚本是幂等的(重复执行是 no-op),只备份一次,且拒绝写坏切片 —— 它会对结果跑
112
+ `node --check`,失败即还原备份 —— 所以每次 DSH 升级后无条件重跑都是安全的。
113
+ 它从运行中的 `node` 二进制反推包路径,因此 nvm 换版本或安装软链重指都不会让它失效。
114
+
115
+ **重装插件并不会恢复图标。**`dsh plugin --profile web add …` 只是把参数转发给
116
+ profile 目录里的 pnpm,只写 profile 自己的 `node_modules`;`-w`
117
+ (`--workspace-root`,而本 profile 的 workspace 就是 `packages: ['.']`)对此没有
118
+ 任何改变。图标住在 DSH 安装里。在标准安装上,下面三条路径不是三份副本,而是
119
+ **同一个物理文件**:
120
+
121
+ | 路径 | 实际是什么 |
122
+ |------|-----------|
123
+ | `dirname(node)/node_modules/@deepseek-ai/dsh` | DSH 安装位(可能是软链) |
124
+ | `<profile>/node_modules/@deepseek-ai/dsh-client-ui-conversation` | 指向它的**目录联接(junction)** |
125
+ | `<dsh>/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js` | 被补丁的那个文件 |
126
+
127
+ 要观察的现象:档位依然工作、门禁依然生效,但它在输入区下拉里那一行没有图标,
128
+ 收起后的触发器显示纯文字,而同排其他档位是图标+文字。
129
+
88
130
  ## 从分裂的插件迁移
89
131
 
90
132
  `dsh-perm-gate` 把原先散落在 `dsh-permission-rules`、`dsh-auto-mode`、
@@ -138,6 +180,11 @@ DSH 的 bundle patch 是整体替换 `permission.config.presets`,而非逐 key
138
180
  请重载 profile 让 `cordis.patch.yml` 重新生效,并确认没有更晚加载的插件覆盖了
139
181
  `presets`。
140
182
 
183
+ **「自动审查(高权限)」在输入区丢了图标。**
184
+ DSH 升级或重装替换了被打了补丁的宿主 bundle;重装插件不会把它带回来。执行
185
+ `npx dsh-perm-gate-patch-glyph` 后重载即可。档位本身不受影响 ——
186
+ 没有补丁,它的标签与门禁照常工作。
187
+
141
188
  **规则文件存在但 `--list` 显示 `ruleCount: 0`。**
142
189
  `rulesFile` 按 harness 进程的 CWD 解析,而不是插件目录。建议使用绝对路径,
143
190
  或确认 shell 的 CWD。格式错误的文档在加载时 loud fail,绝不会静默失效。
package/README.ja.md CHANGED
@@ -26,8 +26,8 @@
26
26
  >
27
27
  > | DSH バージョン | ブランチ | バージョン | npm タグ |
28
28
  > | --- | --- | --- | --- |
29
- > | 0.1.0-rc.7 ~ 0.1.1-rc.x | `legacy` | `1.x` | `@legacy` / `@dsh-0.1.1` |
30
- > | 0.1.2-alpha.1+(0.1.5-rc.2を含む) | `main` | `2.x` | `@latest` / `@2.x` / `@dsh-0.1.2` |
29
+ > | 0.1.0-rc.7 ~ 0.1.1-rc.x | `legacy` | `1.x` | `@legacy` |
30
+ > | 0.1.2-alpha.1+(0.1.5-rc.2を含む) | `main` | `2.x` | `@latest` / `@dsh-0.1.2` (`@2.x` は範囲) |
31
31
  >
32
32
  > バージョン系列は **DSH ライン**を追います(`1.x` = DSH ≤ 0.1.1、`2.x` = DSH 0.1.2+)。
33
33
  > メジャー同士が互いを遮断するため、`^1.x` のインストールが `2.x` を解決することは
@@ -45,7 +45,7 @@
45
45
  > メソッドであるため、`typeof` プロブで読み取り、欠落時やエラー時には「ポリシー
46
46
  > 不明」にフォールバックします。
47
47
 
48
- バージョン **2.0.0** — 変更履歴は [日本語 changelog](./CHANGELOG.ja.md) を参照。
48
+ バージョン **2.1.2** — 変更履歴は [日本語 changelog](./CHANGELOG.ja.md) を参照。
49
49
 
50
50
  DeepSeek Harness 向けの、単一・自己完結・決定論優先・fail-closed な権限ゲートです。
51
51
 
@@ -86,7 +86,7 @@ DSH の安全まわりのエコシステムでは、この役割が `dsh-permiss
86
86
  モード**。汎用の「自動承認」でもなく、包括的な権限付与でもありません。フロントが露出する
87
87
  のは**単一スイッチ**(`permissive`)だけで、バックエンドの 4 つの戦略は**組み合わせ
88
88
  可能**でプラグイン設定から制御されます。P0 に対しては依然 fail-closed です。
89
- 権限ピッカーと設定行はいずれも製品名「自动审查」で表示し、アイコンは描きません。
89
+ 権限ピッカーと設定行はいずれも製品名「自动审查」で表示します。アイコンについては後述(組み込みティアは標準装備、プラグイン提供ティアはパッチが必要)。
90
90
  - **サンドボックス昇格の自動回答**(`trustEscalation`)— サンドボックス昇格は shell /
91
91
  pwsh / edit ツールの**内部**(`tools/pre-execute` 後)から発生するためゲートはそれを
92
92
  見ておらず、ゲートが自動許可した呼び出しでも確認プロンプトが表示されます。この戦略を
@@ -117,7 +117,7 @@ dsh plugin --profile web add dsh-perm-gate
117
117
  rulesFile: ./permissions.yaml # 任意。既定は $DSH_HOME/perm-gate/rules.yml
118
118
  dshHome: $DSH_HOME # 保護対象チェックのルート固定
119
119
  defaultAction: ask # allow | ask | deny
120
- gatePresets: [permissive] # ゲートが有効なティア(既定)
120
+ gatePresets: [permissive, permissive-full] # ゲートが有効なティア(既定)
121
121
  ```
122
122
 
123
123
  ### ルール ファイル
@@ -147,6 +147,56 @@ permissions:
147
147
 
148
148
  完全な例: [examples/permissions.example.yaml](./examples/permissions.example.yaml)
149
149
 
150
+ ### ネットワークポリシー(任意で有効化)
151
+
152
+ ローカル HTTP/CONNECT プロキシです。**同じルールファイル**で **shell サブプロセス**の
153
+ 外向き通信を審査し、どのルールにも該当しない宛先には承認経路を提供します。**既定は無効** ——
154
+ 有効にするとループバックポートを確保し、子プロセスのプロキシ環境変数を書き換えるため、
155
+ 暗黙に有効化されることはありません。
156
+
157
+ ```yaml
158
+ - id: dsh-perm-gate
159
+ config:
160
+ networkEnabled: false # マスタースイッチ(既定 false)
161
+ networkMode: whitelist # deny-all | whitelist | allow-all
162
+ networkUnlisted: ask # ask | deny —— 未登録の宛先の扱い
163
+ networkUnattributed: allow # allow | deny —— shell 帰属のない通信
164
+ networkInjectEnv: true # 子プロセスに HTTP(S)_PROXY / ALL_PROXY を注入
165
+ networkAskTimeoutMs: 120000 # 承認待ちの上限。超過時は拒否
166
+ networkGrantTtlMs: 1800000 # 1 回の承認が有効なセッション期間
167
+ ```
168
+
169
+ **段階的な動作**:許可ルールがなければ、どの宛先にも到達できません。未登録の宛先は、
170
+ その接続を開いた shell コマンドのセッションに対して対話的承認へエスカレートし、承認すると
171
+ その宛先が当該セッションで許可されます。**`deny` ルールが承認に回ることはありません** ——
172
+ 承認は「どのルールも禁止していない宛先」の到達性を広げられますが、禁止しているルールを
173
+ 覆すことはできません。
174
+
175
+ **境界 —— 依存する前に必ず読んでください**:このプロキシは**協調型**のポリシー層であり、
176
+ 強制境界ではありません。プロキシ環境変数を**読む**クライアントの通信しか見えません。
177
+
178
+ | クライアント | 遮断できるか |
179
+ |--------------|--------------|
180
+ | `curl`、`wget`、`git`、Go `net/http`、Python `requests` | ✅ |
181
+ | **Node.js `http` / `https` / `fetch`** | ❌ **直接接続。プロキシから見えない** |
182
+ | Java(`-D` プロキシ指定なし)、.NET `HttpClient` | ❌ |
183
+ | 生ソケット、独自 TCP | ❌ |
184
+ | DNS、QUIC/HTTP3、非 HTTP プロトコル | ❌ |
185
+ | リテラル IP への接続 | ❌ |
186
+
187
+ したがって `node -e "require('http').get('http://host/')"` のようなコマンドは**遮断されません**。
188
+ これは「誤操作を防ぐガードレールであり、意図を宣言する場所」と考えてください。密閉された
189
+ サンドボックスではありません。
190
+
191
+ DSH **自身**の通信 —— 組み込みネットワークツールと LLM トランスポート —— は意図的に対象外
192
+ です。これらの接続には shell 帰属がなく、`networkUnattributed: allow`(既定)がそのまま
193
+ 通します。審査するとホストが**自分自身を遮断**してしまい、取り逃すよりはるかに悪い障害に
194
+ なります。ホストのクライアントがプロキシ環境変数を読まないと確信できる場合のみ `deny` に
195
+ してください。
196
+
197
+ 稼働状態の照会:`GET /api/dsh-perm-gate/network`(モード / バインド / ポート / プロキシ稼働 /
198
+ 環境注入状態 / 遮断カウンタ / 直近の遮断)。
199
+
150
200
  ## 自动审查ティア(マシン値 `permissive`)
151
201
 
152
202
  自动审查は権限ピッカーの中で 読み取り専用 / ワークスペース内変更 / 完全権限 /
@@ -154,11 +204,33 @@ permissions:
154
204
  発行することもありません。人間/LLM のシーム**の前**で判定を狭めたり広めたりする
155
205
  だけで、P0 ハード拒否は単調かつ交渉不能のままです。
156
206
 
157
- ピッカーの表示名は**ホストが供給する製品名**で、言語別辞書の項目ではありません。DSH 0.1.2 は
207
+ **2 つのバリアントを同梱します。** プリセットの `sandbox` と `approval` は**独立した**ノブであり、
208
+ 両者を結び付けると悪いトレードオフを強いられるためです。
209
+
210
+ | ピッカーの表示名 | マシン値 | sandbox | approval |
211
+ |------------------|----------|---------|----------|
212
+ | 自动审查 | `permissive` | `workspace-write` | `ask` |
213
+ | 自动审查(高権限) | `permissive-full` | `danger-full-access` | `ask` |
214
+
215
+ 通常のティアは組み込みのファイルサンドボックスを維持します。そのサンドボックスは子プロセスの
216
+ 起動に必要な名前付きパイプも拒否するため、`git clone`、MSYS2/Cygwin の `sh.exe`、ConPTY は
217
+ `Win32 error 5` / `couldn't create signal pipe` で失敗します。しかもゲートは
218
+ **`gatePresets` に列挙したティアでしか動作しない**ため、ゲートを使いたい場合はこの制限を受け入れる
219
+ 必要がありました。「自动审查(高権限)」はこの結合を解きます —— **承認動作は同一のまま、ファイル
220
+ サンドボックスの制限だけを外します**。どちらも既定の `gatePresets` に含まれるため、どちらを選んでも
221
+ P0–P4 の全チェーンが動作します。ゲートはプリセットの**名前**だけを読み、sandbox モードは読みません。
222
+
223
+ ピッカーの表示名は**ホストが供給する製品名**で、言語別辞書の項目ではありません。DSH は
158
224
  プラグイン提供ティアの `name:` を 2 つの権限サーフェス(一般設定の既定行と入力欄のピッカー)で
159
225
  そのまま描画し、自前のローカライズ済みラベルは 3 つの組み込み値にしか与えないため、
160
- `cordis.patch.yml` は全セッション共通で中国語ラベルを配布します。このティアは**アイコンを
161
- 描きません** —— ピッカーのグリフは組み込み値にのみ紐づきます。
226
+ `cordis.patch.yml` は全セッション共通で中国語ラベルを配布します。
227
+
228
+ **アイコンは別の話です。** 入力欄のグリフ表は閉じており、その表自身のコメントが規則を述べています:
229
+ *host-configured names outside the design set get none*。`permissive` は組み込み値なので
230
+ 「自动审查」には元から盾+目のアイコンがあります。「自动审查(高权限)」が同じアイコンを得るのは、
231
+ `npx dsh-perm-gate-patch-glyph` がその表に 1 項目を追加したからです。このパッチは
232
+ **ホスト**パッケージを書き換えるため、DSH アップグレードのたびに失われます ——
233
+ [DSH アップグレード後:入力欄アイコンのパッチを再適用](./INSTALL.ja.md) を参照してください。
162
234
 
163
235
  `cordis.yml` では:
164
236
 
@@ -221,8 +293,8 @@ never` パススルー — は人手に変更なく委譲されるため、将
221
293
  セッションの権限ピッカーには「auto-approval」ではなく、**独立して選択できる承認
222
294
  ティア**として「自动审查」が並びます。
223
295
 
224
- ゲートが動作するのは **`gatePresets` に列挙したティアの中だけ**です(既定 `['permissive']`、
225
- このプラグインが追加するティア)。それ以外のティア(Read Only / Workspace Write /
296
+ ゲートが動作するのは **`gatePresets` に列挙したティアの中だけ**です(既定
297
+ `['permissive', 'permissive-full']`、このプラグインが追加する 2 つのティア)。それ以外のティア(Read Only / Workspace Write /
226
298
  Full access / `custom`)では、ゲートの判定フローは**一切実行されません**——許可も、ask も、
227
299
  拒否も、P0 ハード拒否も、拒否キーワードの遮断も、監査イベントの記録も行いません。選択された
228
300
  ティア自身の方針が呼び出しを決めます。`danger-full-access` の定義は「承認プロンプトなしの
@@ -258,8 +330,10 @@ Full access / `custom`)では、ゲートの判定フローは**一切実行
258
330
  人手に回した `ask` は回答が返るまで追跡されます。受動的な `approval/request` オブザーバが閉じた結果を記録し(`allowed-once` → **承認**、`rejected` → **拒否**、`cancelled` → **キャンセル**、`unavailable` → 拒否=承認チャネルなし)、オブザーバが関連付けできない場合(`callId` 欠落・approval サービスなし・上流リスナーの短絡)は `tools/result` が同じ ask をフォールバックとして解決します。承認時には承認後の学習進捗(`n`/しきい値)を表示し、通知バーも 3 つの終態にラベルを付けます。
259
331
 
260
332
  さらにプリセットの**拒否キーワード黑名単**(dsh-approval-gate の `DEFAULT_DENY_KEYWORDS` を継承:`rm -rf`、`push --force`、`drop table`、`mkfs`、`git reset --hard`、`docker system prune` など)を備え、テキストがキーワードを含む呼び出し(大小文字を区別しない部分一致)は許可リスト / 許可 / LLM より先に拒否します。設定カードでリストとして編集でき(プリセット項目にはタグ付き、ワンクリック復元対応)、未設定・空ならプリセットを適用します——黑名単が静かに無効化されることはありません。
261
- このティアは権限ピッカーに**アイコンを表示しません** —— ピッカーのグリフは組み込み 3 値にのみ
262
- 紐づき、プラグイン提供ティアはテキストのみです。
333
+ 「自动审查」と「自动审查(高权限)」はどちらもピッカーに盾+目のアイコンを描きます —— 前者は
334
+ DSH の組み込み表から、後者はインストールガイドに書かれたホストパッチからです。そのパッチが
335
+ ない場合、2 つ目のティアはどのサーフェスでもテキストのみになります。ラベルとゲートは影響を
336
+ 受けません。
263
337
 
264
338
 
265
339
  ## CLI(スタンドアロン dry-run)