dsh-perm-gate 5.0.4 → 5.1.0

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 (50) hide show
  1. package/CHANGELOG.ja.md +0 -2
  2. package/CHANGELOG.ko.md +0 -2
  3. package/CHANGELOG.md +4 -2
  4. package/INSTALL.ja.md +0 -15
  5. package/INSTALL.ko.md +0 -15
  6. package/INSTALL.md +0 -15
  7. package/INSTALL.zh.md +0 -15
  8. package/README.ja.md +6 -18
  9. package/README.ko.md +6 -18
  10. package/README.md +40 -19
  11. package/README.zh.md +34 -18
  12. package/assets/appcontainer-launcher.cs +703 -0
  13. package/dsh.plugin.json +2 -2
  14. package/lib/appcontainer/compile.d.ts +17 -0
  15. package/lib/appcontainer/compile.js +74 -0
  16. package/lib/appcontainer/exempt.d.ts +18 -0
  17. package/lib/appcontainer/exempt.js +71 -0
  18. package/lib/appcontainer/index.d.ts +85 -0
  19. package/lib/appcontainer/index.js +286 -0
  20. package/lib/appcontainer/probe.d.ts +44 -0
  21. package/lib/appcontainer/probe.js +66 -0
  22. package/lib/appcontainer/sid.d.ts +23 -0
  23. package/lib/appcontainer/sid.js +33 -0
  24. package/lib/appcontainer/wrap.d.ts +22 -0
  25. package/lib/appcontainer/wrap.js +40 -0
  26. package/lib/cli.js +0 -0
  27. package/lib/client.js +1425 -1108
  28. package/lib/client.js.map +1 -1
  29. package/lib/config.d.ts +41 -1
  30. package/lib/config.js +6 -0
  31. package/lib/index.d.ts +1 -1
  32. package/lib/index.js +53 -0
  33. package/lib/runtime.d.ts +22 -0
  34. package/lib/runtime.js +24 -0
  35. package/locale/de.json +1 -0
  36. package/locale/en.json +1 -0
  37. package/locale/es.json +1 -0
  38. package/locale/fr.json +1 -0
  39. package/locale/it.json +1 -0
  40. package/locale/ja.json +1 -0
  41. package/locale/ko.json +1 -0
  42. package/locale/ru.json +1 -0
  43. package/locale/zh.json +1 -0
  44. package/package.json +28 -28
  45. package/scripts/patch-permission-glyph.mjs +0 -0
  46. package/README.de.md +0 -487
  47. package/README.es.md +0 -489
  48. package/README.fr.md +0 -498
  49. package/README.it.md +0 -490
  50. package/README.ru.md +0 -478
package/README.it.md DELETED
@@ -1,490 +0,0 @@
1
- # dsh-perm-gate
2
-
3
- - [English README](./README.md)
4
- - [中文 README](./README.zh.md)
5
- - [日本語 README](./README.ja.md)
6
- - [한국어 README](./README.ko.md)
7
- - [Français README](./README.fr.md)
8
- - [Deutsch README](./README.de.md)
9
- - [Italiano README](./README.it.md)
10
- - [Русский README](./README.ru.md)
11
- - [Español README](./README.es.md)
12
- - [Installation guide](./INSTALL.md)
13
- - [中文安装指南](./INSTALL.zh.md)
14
- - [日本語インストールガイド](./INSTALL.ja.md)
15
- - [한국어 설치 안내](./INSTALL.ko.md)
16
- - [Guide d'installation](./INSTALL.fr.md)
17
- - [Installationsanleitung](./INSTALL.de.md)
18
- - [Guida all'installazione](./INSTALL.it.md)
19
- - [Руководство по установке](./INSTALL.ru.md)
20
- - [Guía de instalación](./INSTALL.es.md)
21
- - [Changelog](./CHANGELOG.md)
22
- - [日本語 changelog](./CHANGELOG.ja.md)
23
- - [한국어 changelog](./CHANGELOG.ko.md)
24
- - [Français changelog](./CHANGELOG.fr.md)
25
- - [Deutsch changelog](./CHANGELOG.de.md)
26
- - [Italiano changelog](./CHANGELOG.it.md)
27
- - [Русский changelog](./CHANGELOG.ru.md)
28
- - [Español changelog](./CHANGELOG.es.md)
29
-
30
- > **Nota di compatibilità:** la v2.0.0 include i dizionari `ja` / `ko`, ma il DSH ufficiale espone
31
- > solo `zh` / `en` tramite `LocaleRuntime` (`LOCALE_IDS = ["zh", "en"]`). Su un DSH originale, la
32
- > selezione di `ja` / `ko` fallisce con `locale "<id>" is not registered`. Usare un fork di DSH
33
- > che aggiorni `LOCALE_IDS` (locale-settings.ts) e le etichette `LOCALES` (client/index.ts), poi
34
- > ricompilare.
35
-
36
- > **▼ Compatibilità delle versioni DSH**
37
- >
38
- > Due linee DSH sono servite da due branch di lunga durata, ciascuno con la propria serie di
39
- > versioni, il proprio `engines.dsh` e il proprio dist-tag npm
40
- > ([disposizione delle release](./RELEASING.md)):
41
- >
42
- > | Versione DSH | Branch | Versione | Tag npm |
43
- > | --- | --- | --- | --- |
44
- > | 0.1.0-rc.7 ~ 0.1.1-rc.x | `legacy` | `1.x` | `@legacy` |
45
- > | 0.1.2-alpha.1+ (incl. 0.1.5-rc.2) | `main` | `2.x` | `@latest` / `@dsh-0.1.2` (`@2.x` è un intervallo) |
46
- > | 0.2.0-rc.1 (linea 0.2.0) | `compat/0.2.0` | `5.x` | `@dsh-0.2.0` |
47
- >
48
- > Il numero di serie segue la **linea DSH** (`1.x` = DSH ≤ 0.1.1, `2.x` = DSH 0.1.2+), e i major
49
- > si recintano a vicenda: un'installazione `^1.x` non risolve mai una versione `2.x` e viceversa.
50
- > `engines.dsh` dichiara la stessa separazione, ma DSH non lo legge mai — sono gli intervalli e i
51
- > dist-tag a tenere un vecchio DSH su `1.x`.
52
- >
53
- > `@deepseek-ai/dsh-client-runtime` è stato **rimosso** a partire da `0.1.2-alpha.1` — non si è
54
- > semplicemente spostato. La linea `legacy` raggiunge ancora `ctx.slots` attraverso di esso;
55
- > `main` ottiene la stessa dichiarazione da `@deepseek-ai/dsh-client-ui-renderer/client`. Due
56
- > giunzioni sensibili alla versione sono gestite con sonde di capacità anziché con controlli di
57
- > versione: (1) la registrazione delle impostazioni usa `register`, presente su entrambe le linee
58
- > (`installSection` è un'aggiunta, non una sostituzione); (2) `effectivePolicy` è un metodo
59
- > **privato** del servizio di approvazione utente su entrambe, quindi viene letto dietro una
60
- > sonda `typeof` e degrada a "policy sconosciuta" quando è assente o lancia un errore.
61
-
62
- Versione **2.6.0** — vedere il [changelog](./CHANGELOG.it.md).
63
-
64
- Un unico gate di permessi autosufficiente, deterministico in primis e fail-closed per DeepSeek
65
- Harness.
66
-
67
- `dsh-perm-gate` decide ogni chiamata a un tool attraverso una catena di priorità fissa:
68
-
69
- | Stadio | Decisione | Che cosa è |
70
- | ---- | ---- | ---- |
71
- | **P0** | `deny` | hard-deny deterministico: materiale credenziali, mutazione di percorsi protetti, shell pericolosa |
72
- | **P1** | `allow` | un **grant di sessione** preciso e delimitato |
73
- | **P2** | `deny/allow/ask` | catena di regole statica: prima la blacklist, poi l'allow, poi l'ask |
74
- | **P3** | `allow/deny/ask` | classificatore semantico LLM opzionale (**off** per impostazione predefinita) |
75
- | **P4** | `ask` | seam di approvazione ufficiale |
76
-
77
- Strettamente fail-closed: una decisione P0 non viene mai scavalcata da un grant, una regola, il
78
- classificatore o un essere umano.
79
-
80
- ## Perché
81
-
82
- L'ecosistema di sicurezza di DSH distribuisce questo su più plugin (`dsh-permission-rules`,
83
- `dsh-auto-mode`, `dsh-auto-review`, `dsh-movein-permissions`). `dsh-perm-gate` fonde gate +
84
- approvazione + classificatore (opzionale) in un solo pacchetto, con un'unica traccia di audit e
85
- nessun accoppiamento di versione tra plugin.
86
-
87
- ## Funzionalità
88
-
89
- - **Whitelist / blacklist dei comandi** — confronto su una **scomposizione argv** (non una stringa
90
- grezza), con discesa ricorsiva in `sh -c`/`bash -c`, rilevamento delle pipeline, controllo delle
91
- destinazioni delle redirezioni e riconoscimento di ricorsivo/forzato (`rm -rf`).
92
- - **Priorità al deny** — una regola deny corrispondente batte qualsiasi regola allow.
93
- - **Grant di sessione** — grant precisi `(tool, fingerprint canonico)` con `TTL` + `maxUses`;
94
- rieseguire con un target diverso non riutilizza mai l'autorità. I sub-agent ereditano ma non
95
- possono coniarne.
96
- - **Motore di regole a funzioni pure** — compilazione glob/regex con un limite ReDoS, fallimento
97
- rumoroso su regole malformate e cache di compilazione basata sull'hash della sorgente.
98
- - **Audit** — ogni decisione viene registrata come evento `{ignorable:true}` con il suo `callId`;
99
- il motivo visibile al modello corrisponde all'esito registrato.
100
- - **Livello 自动审查** (`permissive`, più `permissive-full`) — una **modalità di approvazione
101
- indipendente** (separata dai livelli sola lettura, accesso completo e whitelist) che non è né
102
- "approvazione automatica" né fiducia indiscriminata. Il front-end espone un **unico
103
- interruttore** (`permissive`); le quattro strategie di back-end sono **combinabili** e guidate
104
- dalle impostazioni del plugin — sempre fail-closed contro P0. Il selettore dei permessi e la
105
- riga delle impostazioni la mostrano entrambi con l'etichetta di prodotto 自动审查, senza icona.
106
- La variante `permissive-full` mantiene lo stesso comportamento di approvazione ma elimina la
107
- sandbox dei file integrata, che altrimenti nega le named pipe di cui `git clone` / Cygwin /
108
- ConPTY hanno bisogno.
109
- - **Risposta automatica alle escalation della sandbox** (`trustEscalation`) — un'escalation della
110
- sandbox viene richiesta dall'*interno* del corpo del tool shell / pwsh / edit, dopo
111
- `tools/pre-execute`, così il gate non l'ha mai vista e una chiamata che aveva auto-consentito
112
- richiedeva comunque l'approvazione dell'ampliamento. Con questa strategia attiva, è qui che
113
- viene risposta l'esatta richiesta che il gate aveva autorizzato (riconosciuta per `callId`).
114
- - **`llmAssist` con rischio graduato** — un LLM personalizzato compatibile OpenAI valuta ogni
115
- `ask` come `safe` / `risky:<categoria>`; le categorie hard (eliminazione, credenziali, remoto,
116
- sistema, massa) **chiedono sempre**, il neutro alimenta l'apprendimento dei verdetti e ogni
117
- guasto resta fail-closed.
118
- - **Apprendimento dei verdetti** — gli `ask` a rischio neutro approvati da una persona ed
119
- effettivamente eseguiti si accumulano; superata la soglia, *la stessa identica operazione*
120
- (riconosciuta per fingerprint) si auto-consente.
121
- - **Feed di eventi di decisione** — ogni decisione viene aggiunta a un feed JSONL e mostrata dalla
122
- parte browser come una striscia di avviso sopra l'input della conversazione, più una scheda dei
123
- registri di approvazione (dal più recente) nella vista conversazione.
124
-
125
- Una **blacklist di parole chiave deny preimpostata** (ereditata dai `DEFAULT_DENY_KEYWORDS` di
126
- dsh-approval-gate: `rm -rf`, `push --force`, `drop table`, `mkfs`, `git reset --hard`,
127
- `docker system prune`, …) mette il veto a qualsiasi chiamata il cui testo contenga una parola
128
- chiave — sottostringa case-insensitive, applicata prima della whitelist, dei grant e dell'LLM. È
129
- modificabile come elenco nella scheda impostazioni (le voci preimpostate sono etichettate e un
130
- ripristino con un clic rimette il preset); non impostata o vuota si applica il preset — la
131
- blacklist non si spegne mai silenziosamente.
132
-
133
- ## Installazione
134
-
135
- Richiede un'installazione esistente di
136
- [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
137
-
138
- ```sh
139
- dsh plugin --profile web add dsh-perm-gate
140
- ```
141
-
142
- I passaggi completi di installazione, aggiornamento, migrazione e risoluzione dei problemi si
143
- trovano nella [Guida all'installazione](./INSTALL.it.md) — disponibile anche in
144
- [English](./INSTALL.md) / [中文](./INSTALL.zh.md) / [日本語](./INSTALL.ja.md) /
145
- [한국어](./INSTALL.ko.md) / [Français](./INSTALL.fr.md) / [Deutsch](./INSTALL.de.md) /
146
- [Русский](./INSTALL.ru.md) / [Español](./INSTALL.es.md).
147
-
148
- ## Configurazione
149
-
150
- Aggiungere il plugin al `cordis.yml`:
151
-
152
- ```yaml
153
- - id: dsh-perm-gate
154
- name: dsh-perm-gate
155
- config:
156
- rulesFile: ./permissions.yaml # optional; defaults to $DSH_HOME/perm-gate/rules.yml
157
- dshHome: $DSH_HOME # root pinned for protected-target checks
158
- defaultAction: ask # allow | ask | deny
159
- gatePresets: [permissive, permissive-full] # tiers where the gate is active (default)
160
- sessionSweep: true # hourly cleanup of archived/dead sessions' gate data
161
- ```
162
-
163
- ### Pulizia delle sessioni
164
-
165
- All'avvio e ogni ora il gate legge l'archivio dei workspace di DSH
166
- (`$DSH_HOME/storages/workspace.json`, in sola lettura) e classifica ogni sessione per cui
167
- conserva dati della catena di autorizzazione. Le sessioni che DSH ha archiviato
168
- (`global.archivedSessionIds`) o non traccia più affatto vedono i propri eventi di decisione
169
- rimossi da `$DSH_HOME/perm-gate/events.jsonl` e i propri file di snapshot pre-modifica cancellati
170
- da `$DSH_HOME/perm-gate/snapshots/` — storia che la pagina di revisione non può più raggiungere,
171
- per dati che l'harness stesso considera andati. Le sessioni vive non vengono toccate, le righe non
172
- attribuibili (session id vuoto) non vengono mai cancellate, e ogni guasto è fail-open: il giro
173
- viene saltato e ritentato un'ora dopo. Impostare `sessionSweep: false` per disattivare;
174
- `workspaceStoreFile` sostituisce il percorso dell'archivio. Ripristinare una sessione archiviata
175
- non ripristina la sua storia ripulita.
176
-
177
- ### File delle regole
178
-
179
- ```yaml
180
- permissions:
181
- defaultAction: ask
182
- deny:
183
- - command: [rm#recursive]
184
- reason: no recursive rm
185
- - command: [ssh]
186
- reason: no direct ssh
187
- - paths: [.dsh/**]
188
- reason: protect harness metadata
189
- allow:
190
- - command: [pnpm, node]
191
- reason: dev tools
192
- - command: [curl, wget]
193
- args: ["https://*.example.com/*"]
194
- reason: allowed endpoint
195
- ask:
196
- - command: [bash, sh]
197
- reason: ask shells
198
- ```
199
-
200
- Una voce di comando `word#flag` corrisponde alla parola comando (`word`) con il modificatore
201
- `recursive` o `force` — così `rm#recursive` corrisponde a `rm -rf`, `env rm -rf` e
202
- `sh -c "rm -rf /"`.
203
-
204
- ### Politica di rete (opt-in)
205
-
206
- Un proxy HTTP/CONNECT locale che giudica il traffico in uscita dei **sottoprocessi shell** in
207
- base allo stesso file di regole, più un percorso di approvazione per i target che nessuna regola
208
- copre. **Disattivato per impostazione predefinita** — attivarlo lega una porta di loopback e
209
- riscrive l'ambiente proxy per i processi figli, quindi non viene mai attivato implicitamente.
210
-
211
- ```yaml
212
- - id: dsh-perm-gate
213
- config:
214
- networkEnabled: false # master switch (default false)
215
- networkMode: whitelist # deny-all | whitelist | allow-all
216
- networkUnlisted: ask # ask | deny — unlisted target handling
217
- networkUnattributed: allow # allow | deny — traffic with no shell attribution
218
- networkInjectEnv: true # rewrite HTTP(S)_PROXY/ALL_PROXY for children
219
- networkAskTimeoutMs: 120000 # approval wait before failing closed
220
- networkGrantTtlMs: 1800000 # how long one approval covers its target
221
- ```
222
-
223
- **Comportamento a livelli.** Nulla raggiunge la rete senza una regola allow. Un target non
224
- elencato viene escalato al seam di approvazione interattivo, sollevato per conto del comando
225
- shell che ha aperto la connessione; approvare amplia la copertura per quel target per la
226
- sessione. Una regola `deny` non viene **mai** escalata — l'approvazione può ampliare ciò che un
227
- target non elencato può raggiungere, ma non può mai scavalcare una regola che dice no.
228
-
229
- **Il confine — da leggere prima di contarci.** Il proxy è uno strato di policy *cooperativo*,
230
- non un confine di applicazione. Vede solo il traffico dei client che leggono l'ambiente proxy:
231
-
232
- | Client | Coperto? |
233
- |--------|----------|
234
- | `curl`, `wget`, `git`, Go `net/http`, Python `requests` | sì |
235
- | **Node.js `http`/`https`/`fetch`** | **no — si collega direttamente** |
236
- | Java (senza flag proxy `-D`), .NET `HttpClient` | no |
237
- | Socket raw, TCP personalizzato | no |
238
- | DNS, QUIC/HTTP3, protocolli non HTTP | no |
239
- | Connessioni a un IP letterale | no |
240
-
241
- Quindi un comando shell come `node -e "require('http').get('http://host/')"` non viene
242
- intercettato. Trattatelo come un guard-rail contro gli incidenti e un luogo dove dichiarare
243
- un'intenzione, non come una sandbox ermetica.
244
-
245
- Il traffico di rete **proprio** di DSH — i tool di rete integrati e il trasporto LLM — è
246
- deliberatamente lasciato in pace. Quelle connessioni non portano alcuna attribuzione shell, e
247
- `networkUnattributed: allow` (il valore predefinito) le lascia passare senza revisione:
248
- revisionarle permetterebbe all'host di bloccare *se stesso*, che è un fallimento peggiore di un
249
- blocco mancato. Impostare `networkUnattributed: deny` solo se si sa che i client del proprio host
250
- ignorano l'ambiente proxy.
251
-
252
- Lo stato live si interroga su `GET /api/dsh-perm-gate/network` (modalità, bind, porta, vivacità
253
- del proxy, stato dell'iniezione d'ambiente, contatori dei blocchi, blocchi recenti).
254
-
255
- ## Il livello 自动审查 (valore macchina `permissive`)
256
-
257
- 自动审查 è un **livello di approvazione indipendente** nel selettore dei permessi di DSH, in
258
- parallelo a Sola lettura / Scrittura workspace / Accesso completo / Whitelist. Non è
259
- un'"approvazione automatica" generica e non conia mai autorità indiscriminata: restringe o
260
- amplia soltanto il seam *prima* del passaggio umano/LLM, mentre l'hard-deny P0 resta monotono e
261
- non negoziabile **dentro l'ambito proprio del gate**.
262
-
263
- > **P0 è delimitato, non globale.** Il gate agisce solo quando il preset dei permessi della
264
- > sessione è uno dei `gatePresets` (predefinito `permissive` / `permissive-full`). Con qualsiasi
265
- > altro preset — Sola lettura, Scrittura workspace o Accesso completo — **l'intero** gate si
266
- > ritira, hard-deny P0 compreso, perché la politica propria del livello scelto governa quella
267
- > sessione. È voluto (si veda `gatePresets` nella tabella di configurazione), ma significa che "P0
268
- > non è negoziabile" vale *dentro* i livelli del gate e non attraverso ogni livello. Un ritiro
269
- > non è silenzioso: il gate registra un evento `stand-down` per ogni transizione sessione/preset
270
- > e il browser mostra una striscia persistente **GATE OFF** sopra l'input. Impostare
271
- > `gatePresets: ['*']` per rendere di nuovo globale P0.
272
-
273
- **Ne vengono shipiate due varianti**, perché `sandbox` e `approval` di un preset sono manopole
274
- indipendenti e accoppiarle imponeva un cattivo compromesso:
275
-
276
- | Etichetta nel selettore | Valore macchina | sandbox | approval |
277
- |--------------|---------------|---------|----------|
278
- | 自动审查 | `permissive` | `workspace-write` | `ask` |
279
- | 自动审查(高权限) | `permissive-full` | `danger-full-access` | `ask` |
280
-
281
- Il livello semplice conserva la sandbox dei file integrata. Quella sandbox nega anche le named
282
- pipe di cui un processo figlio ha bisogno per avviarsi, così `git clone`, `sh.exe` di MSYS2/Cygwin
283
- e ConPTY falliscono con `Win32 error 5` / `couldn't create signal pipe`. Poiché il gate è attivo
284
- **solo** nei livelli elencati in `gatePresets`, volere il gate significava accettare quella
285
- restrizione. 自动审查(高权限) rimuove l'accoppiamento: identico comportamento di approvazione,
286
- nessuna restrizione di sandbox dei file. La descrizione del livello enuncia il compromesso senza
287
- giri di parole — il flusso di lavoro è più fluido, le approvazioni valgono comunque per chiamata,
288
- ma non resta **nessuna sandbox di sistema come rete di sicurezza**. Entrambi sono nei
289
- `gatePresets` predefiniti, quindi l'uno o l'altro danno l'intera catena P0–P4 — il gate legge solo
290
- il **nome** del preset, mai la modalità di sandbox.
291
-
292
- L'etichetta nel selettore è una **stringa di prodotto fornita dall'host**, non una voce di
293
- dizionario per locale: DSH rende verbatim il `name:` del livello di un plugin su entrambe le
294
- superfici dei permessi (la riga predefinita nelle impostazioni generali e il selettore del
295
- composer) e fornisce le proprie etichette localizzate solo per i tre valori integrati, così
296
- `cordis.patch.yml` spedisce l'etichetta cinese per ogni sessione.
297
-
298
- **L'icona** è un'altra storia. La mappa dei glifi del composer è chiusa e il suo stesso commento
299
- enuncia la regola: *host-configured names outside the design set get none.* `permissive` è un
300
- valore integrato, quindi 自动审查 ha già un glifo scudo+occhio; `permissive-full` riceve lo stesso
301
- glifo solo perché `npx dsh-perm-gate-patch-glyph` lo aggiunge a quella mappa. Quella patch modifica
302
- un pacchetto **host**, quindi si perde a ogni aggiornamento di DSH — si veda
303
- [Dopo un aggiornamento di DSH](./INSTALL.it.md).
304
-
305
- Nel `cordis.yml`:
306
-
307
- ```yaml
308
- - id: dsh-perm-gate
309
- name: dsh-perm-gate
310
- config:
311
- rulesFile: ./permissions.yaml
312
- defaultAction: ask
313
- permissive: true # single front-facing switch (independent tier on)
314
- permissiveStrategies: # backend, combinable
315
- trustAutoAllow: true # in-scope safe ops auto-allow; dangerous/unknown ask
316
- alwaysConfirm: false # every crossing asks; allow-controls add repeat-allow / wl-migrate buttons
317
- llmAssist: false # LLM classify first, human fallback on ask/failure
318
- trustEscalation: true # a cleared call's own sandbox escalation needs no prompt
319
- ```
320
-
321
- `trustAutoAllow` è il livello intermedio di base (un allow da regola passa da sé).
322
- `alwaysConfirm` mostra il pannello di approvazione a ogni attraversamento; i suoi "controlli
323
- allow" aggiungono due pulsanti estesi — **ri-consentire questo tipo per questa sessione** (un
324
- grant di sessione delimitato) e **consentire ogni occorrenza** (che persiste la parola comando
325
- nella whitelist `allow` del `permissions.yaml` tramite `approveAllowEverywhere`). `llmAssist`
326
- consulta un vero LLM configurabile (`classifierEndpoint` / `classifierModel`, qualsiasi API
327
- compatibile OpenAI) per decidere automaticamente un `ask`, e ricade sul seam umano su
328
- `ask`/errore — sempre fail-closed. `trustEscalation` (attivo per impostazione predefinita quando
329
- il livello è attivo) risponde a un'escalation `sandbox_permissions` sollevata dall'interno di una
330
- chiamata che il gate aveva già consentito; si veda sotto. Con `permissive` disattivo, il gate si
331
- comporta esattamente come prima.
332
-
333
- ### Escalation della sandbox: perché un verdetto `safe` mostrava comunque la richiesta
334
-
335
- Una chiamata a un tool può sollevare **due approvazioni indipendenti**. Il gate possiede la prima
336
- — il suo `ask`, sulla cascata `tools/pre-execute`. La seconda arriva da `approveEscalation`
337
- **dentro il corpo del tool**, al momento di `tools/execute`, ogni volta che il modello ha passato
338
- `sandbox_permissions` + `justification`; a quel punto `tools/pre-execute` è già deciso, così
339
- l'allow del gate non la raggiunge mai. Una chiamata valutata `safe` dall'LLM e auto-consentita dal
340
- gate mostrava quindi comunque una richiesta di approvare l'ampliamento della sandbox.
341
-
342
- `trustEscalation` chiude questo varco. Il gate ricorda ogni chiamata che ha effettivamente
343
- consentito (indicizzata per la `callId` dell'host, che la richiesta di escalation ripete) e
344
- risponde da sé all'escalation con `allowed-once`. Si applica solo quando valgono **tutti** i
345
- punti:
346
-
347
- - il livello 自动审查 è attivo e `trustEscalation` è attivo;
348
- - la richiesta porta una `callId` che il gate ha autorizzato, con nome del tool corrispondente;
349
- - il motivo è un'escalation riconosciuta che nomina `workspace-write` o `danger-full-access`.
350
-
351
- Tutto il resto — un motivo non riconosciuto, una chiamata diversa, una chiamata che il gate aveva
352
- richiesto o negato, il passaggio diretto `approval: never` — delega immutato all'umano, così un
353
- futuro cambio di formulazione di DSH fallisce chiuso anziché aperto. La risposta automatica viene
354
- registrata nel feed di eventi (`verdict: "escalation-auto"`, `mode: <target>`). Spegnere
355
- l'interruttore per tenere l'ampliamento della sandbox sotto controllo umano mentre gli altri
356
- allow restano automatici.
357
-
358
- ### llmAssist con rischio graduato, apprendimento dei verdetti e feed di eventi
359
-
360
- Con `llmAssist` attivo, l'LLM configurato valuta un `ask` alla volta con un protocollo strutturato.
361
- La valutazione avviene **dentro la cascata `tools/pre-execute` del gate, prima che la decisione
362
- torni all'host**: un verdetto `safe` delega la chiamata direttamente, così il pannello di
363
- approvazione non appare mai; solo un verdetto davvero incerto raggiunge l'utente. Due sorgenti di
364
- ricevitore sono selezionabili nella scheda impostazioni: un'**API personalizzata** (qualsiasi
365
- endpoint compatibile OpenAI — `classifierEndpoint` / `classifierModel` / `classifierApiKey`, con
366
- preset tra cui Xiaomi MiMo `https://api.xiaomimimo.com/v1`), oppure il **gruppo di modelli host
367
- DSH** (il servizio `llm` configurato della sessione, via `agentDefaultModel.currentSelection`,
368
- facoltativamente sostituito con `classifierProvider` / `classifierModel`). Un pulsante di **test
369
- di salute** (`POST /api/dsh-perm-gate/health`) esegue una completion minima e riporta la latenza.
370
-
371
- - `safe` → la chiamata è auto-consentita (audited come sorgente `classifier`); nessun pannello.
372
- - `risky` + una **categoria hard** (`deletion`, `credential`, `remote`, `system`, `bulk`) → la
373
- chiamata **mantiene l'ask umano**. Il classificatore **non nega mai**: il percorso di deny
374
- appartiene ai soli strati deterministici (hard-deny P0, la blacklist di parole chiave deny, le
375
- regole `deny:` esplicite), così una categoria mal valutata resta sempre negoziabile invece di
376
- essere un blocco impugnabile.
377
- (Negare sulla parola del modello è stato misurato dal vivo: un benigno `git commit -F …`
378
- valutato `remote` ha prodotto un auto-deny senza pannello e senza grant per riprovare.)
379
- - `risky:neutral` → con `riskLearning` attivo (scheda Impostazioni, spento per impostazione
380
- predefinita), ogni approvazione umana effettivamente eseguita (saldato tramite l'evento
381
- `tools/result` dell'host) conta su una chiave `tool|category`; quando il conteggio raggiunge
382
- `riskThreshold` (predefinito 3) **e** il fingerprint dell'operazione della nuova chiamata
383
- (parola comando + basename del target) corrisponde a un campione confermato, la stessa identica
384
- operazione si auto-consente. Un target diverso non riutilizza mai quell'autorità. Con la
385
- sedimentazione (`riskSediment`, attiva per impostazione predefinita) i campioni confermati di
386
- una chiave arrivata a soglia diventano **regole allow deterministiche**: una corrispondenza
387
- esatta consente outright senza un'altra chiamata LLM — anche con `llmAssist` spento — e le
388
- regole sedimentate sono visibili e gestibili (terminare / rimuovere) nella scheda impostazioni.
389
- - Timeout (`riskTimeoutMs`, predefinito 20 s, un tentativo), guasti di trasporto e uscite fuori
390
- protocollo lasciano l'ask intatto — il gate non indovina mai.
391
-
392
- Lo stato di apprendimento persiste in un JSON di proprietà del plugin
393
- (`$DSH_HOME/perm-gate/learning.json`, o `learningFile`), mai nel file di regole YAML
394
- dell'utente. Ogni decisione viene aggiunta a `$DSH_HOME/perm-gate/events.jsonl` (o `eventsFile`) e
395
- servita su `GET /api/dsh-perm-gate/events?sessionId=&since=`; la parte browser la interroga e
396
- mostra l'ultima decisione come striscia di avviso sopra l'input della conversazione (gli ask
397
- restano visibili fino all'evento successivo) ed elenca tutte le decisioni della sessione dalla più
398
- recente nella scheda **Approvals** della vista conversazione.
399
-
400
- I file toccati da ogni decisione vengono istantaneizzati prima che la modifica atterri (≤ 5 file,
401
- ≤ 256 KB ciascuno) sotto `$DSH_HOME/perm-gate/snapshots/`; nella scheda **Approvals** ogni chip di
402
- file apre un diff di righe (`GET /api/dsh-perm-gate/diff`) con un'azione di **revert** che invia
403
- un'istruzione di ripristino nella conversazione (`POST /api/dsh-perm-gate/revert`). Una barra di
404
- inventario degli snapshot li cancella per sessione o del tutto
405
- (`GET /api/dsh-perm-gate/snapshots-stats` / `POST /api/dsh-perm-gate/snapshots-clear`).
406
-
407
- Un `ask` instradato dal gate a una persona viene tracciato finché la persona risponde: un
408
- osservatore passivo `approval/request` registra l'esito chiuso (`allowed-once` → **approvato**,
409
- `rejected` → **rifiutato**, `cancelled` → **annullato**, `unavailable` → un diniego, poiché non
410
- esisteva alcun canale di approvazione), con `tools/result` che salda lo stesso ask come fallback
411
- quando l'osservatore non riesce a correlarlo. Le approvazioni riportano il progresso
412
- dell'apprendimento post-approvazione (`n`/soglia), e la striscia di avviso etichetta tutti e tre
413
- gli stati terminali.
414
-
415
- 自动审查 e 自动审查(高权限) disegnano entrambi il glifo scudo+occhio nel selettore — la prima
416
- dalla mappa integrata di DSH, la seconda dalla patch host descritta nella guida
417
- all'installazione. Senza quella patch il secondo livello è solo testo su ogni superficie;
418
- l'etichetta e il gating restano invariati.
419
-
420
- ### Un livello di sessione selezionabile
421
-
422
- `cordis.patch.yml` aggiunge un preset `permissive` (`sandbox: workspace-write`, `approval: ask`,
423
- nome **自动审查**) tra Scrittura workspace e Accesso completo. La patch del bundle DSH sostituisce
424
- l'intera mappa `permission.config.presets` invece di fare merge per chiave, quindi il file
425
- ridichiara anche i tre integrati (`read-only` / `workspace-write` / `danger-full-access`, da
426
- `@deepseek-ai/dsh-base/cordis.patch.yml`); `test/patch-presets.spec.ts` fissa quel set di chiavi.
427
- Così il selettore dei permessi di sessione offre 自动审查 come livello di approvazione indipendente
428
- selezionabile, non una generica modalità di "approvazione automatica".
429
-
430
- Il gate è attivo **solo nei livelli elencati in `gatePresets`** (predefinito
431
- `['permissive', 'permissive-full']`, i due livelli che questo plugin aggiunge). In ogni altro
432
- livello — Sola lettura, Scrittura workspace, Accesso completo, `custom` — il flusso di decisione
433
- del gate non gira affatto: nessun allow, nessun ask, nessun deny, nessun hard-deny P0, nessun veto
434
- di parola chiave deny, nessun evento di audit. La politica propria del livello scelto governa la
435
- chiamata, ed è questo il punto: il `danger-full-access` integrato è definito come "accesso
436
- completo senza richieste di approvazione", quindi scavalcato con un ask (lì irrivendicabile — il
437
- seam di approvazione rifiuta prima che qualsiasi risponditore parta, producendo
438
- `the user rejected tool "..."` senza pannello) o con un hard-deny contraddirebbe silenziosamente
439
- il livello scelto dall'utente. `gatePresets: ['*']` rende di nuovo globale il gate (hard-deny
440
- compreso); dentro un livello attivo un `ask` viene comunque degradato a passthrough quando la
441
- politica di approvazione effettiva della sessione è `never` — ecco perché entrambi i livelli
442
- 自动审查 dichiarano `approval: ask`.
443
-
444
- ### Configurabile nell'interfaccia
445
-
446
- Il livello è regolabile anche a runtime da **Impostazioni → Plugin → 自动审查** (una pagina
447
- `settings.plugins.tab` resa dal client browser del plugin): un interruttore commuta `permissive`,
448
- e quattro interruttori modificano le `permissiveStrategies` di back-end. L'host legge il namespace
449
- al volo, quindi una modifica si applica alla successiva chiamata a tool senza riavvio. Questa è
450
- una classe di approvazione indipendente, NON una generica modalità di "approvazione automatica".
451
-
452
- ### Test delle regole (dry-run)
453
-
454
- La stessa pagina ospita un pannello di **test delle regole**: digitare un nome di tool e un
455
- comando, premere Test, e il gate giudica quella chiamata contro l'insieme di regole che ha
456
- attualmente caricato — senza eseguire nulla e senza scrivere alcuna regola. Si ottengono il
457
- verdetto, la regola corrispondente (indice e azione), le dimensioni che quella regola vincola e il
458
- motivo.
459
-
460
- Riporta sia il verdetto effettivo (l'intera catena P0 → P1 → P2 → P3 → P4) *sia* la risposta
461
- propria dello strato di regole, che non sono la stessa cosa: un hard-deny P0 o una parola chiave
462
- deny da preset scattano prima della catena di regole e non lasciano dietro alcun indice di
463
- regola, così il pannello dice "nessuna regola corrispondente" anziché nominare una regola non
464
- pertinente. Una nota "0 regole caricate" significa che il percorso `rulesFile` non ha risolto
465
- nulla.
466
-
467
- Il pannello parla con `POST /api/dsh-perm-gate/dry-run`, che è **in sola lettura per
468
- costruzione** — non ha alcuna forma di scrittura, quindi testare una regola non potrà mai
469
- modificarla.
470
-
471
- ## CLI
472
-
473
- Dry-run di una chiamata contro un file di regole (senza harness):
474
-
475
- ```sh
476
- dsh-perm-gate --rules permissions.yaml --tool bash --args '{"command":"pnpm install"}'
477
- dsh-perm-gate --rules permissions.yaml --list
478
- ```
479
-
480
- ## Sviluppo
481
-
482
- ```sh
483
- npm run typecheck
484
- npm test
485
- npm run build
486
- ```
487
-
488
- ## Licenza
489
-
490
- [MIT](./LICENSE)