dsh-perm-gate 5.0.4 → 5.1.2-compat.7
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.ja.md +0 -2
- package/CHANGELOG.ko.md +0 -2
- package/CHANGELOG.md +4 -2
- package/INSTALL.ja.md +0 -15
- package/INSTALL.ko.md +0 -15
- package/INSTALL.md +0 -15
- package/INSTALL.zh.md +0 -15
- package/README.ja.md +6 -18
- package/README.ko.md +6 -18
- package/README.md +40 -19
- package/README.zh.md +34 -18
- package/assets/appcontainer-launcher.cs +703 -0
- package/dsh.plugin.json +2 -2
- package/lib/appcontainer/compile.d.ts +17 -0
- package/lib/appcontainer/compile.js +74 -0
- package/lib/appcontainer/exempt.d.ts +18 -0
- package/lib/appcontainer/exempt.js +71 -0
- package/lib/appcontainer/index.d.ts +85 -0
- package/lib/appcontainer/index.js +286 -0
- package/lib/appcontainer/probe.d.ts +44 -0
- package/lib/appcontainer/probe.js +66 -0
- package/lib/appcontainer/sid.d.ts +23 -0
- package/lib/appcontainer/sid.js +33 -0
- package/lib/appcontainer/wrap.d.ts +22 -0
- package/lib/appcontainer/wrap.js +40 -0
- package/lib/bridge.d.ts +34 -0
- package/lib/bridge.js +125 -0
- package/lib/client.js +1663 -1107
- package/lib/client.js.map +1 -1
- package/lib/config.d.ts +41 -1
- package/lib/config.js +6 -0
- package/lib/debug-generation.d.ts +21 -0
- package/lib/debug-generation.js +44 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +157 -7
- package/lib/runtime.d.ts +22 -0
- package/lib/runtime.js +24 -0
- package/locale/de.json +1 -0
- package/locale/en.json +1 -0
- package/locale/es.json +1 -0
- package/locale/fr.json +1 -0
- package/locale/it.json +1 -0
- package/locale/ja.json +1 -0
- package/locale/ko.json +1 -0
- package/locale/ru.json +1 -0
- package/locale/zh.json +1 -0
- package/package.json +25 -14
- package/README.de.md +0 -487
- package/README.es.md +0 -489
- package/README.fr.md +0 -498
- package/README.it.md +0 -490
- package/README.ru.md +0 -478
package/README.fr.md
DELETED
|
@@ -1,498 +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
|
-
> **Note de compatibilité :** la v2.0.0 est livrée avec des dictionnaires `ja` / `ko`, mais le DSH
|
|
31
|
-
> officiel n'expose que `zh` / `en` via `LocaleRuntime` (`LOCALE_IDS = ["zh", "en"]`). Sur un DSH
|
|
32
|
-
> d'origine, la sélection de `ja` / `ko` échoue avec `locale "<id>" is not registered`. Utilisez un
|
|
33
|
-
> fork de DSH qui met à jour `LOCALE_IDS` (locale-settings.ts) et les libellés `LOCALES`
|
|
34
|
-
> (client/index.ts), puis recompilez.
|
|
35
|
-
|
|
36
|
-
> **▼ Compatibilité des versions de DSH**
|
|
37
|
-
>
|
|
38
|
-
> Deux lignes DSH sont servies depuis deux branches de longue durée, chacune avec sa propre série
|
|
39
|
-
> de versions, son `engines.dsh` et son dist-tag npm ([organisation des releases](./RELEASING.md)) :
|
|
40
|
-
>
|
|
41
|
-
> | Version de DSH | Branche | Version | Tag npm |
|
|
42
|
-
> | --- | --- | --- | --- |
|
|
43
|
-
> | 0.1.0-rc.7 ~ 0.1.1-rc.x | `legacy` | `1.x` | `@legacy` |
|
|
44
|
-
> | 0.1.2-alpha.1+ (incl. 0.1.5-rc.2) | `main` | `2.x` | `@latest` / `@dsh-0.1.2` (`@2.x` est une plage) |
|
|
45
|
-
> | 0.2.0-rc.1 (ligne 0.2.0) | `compat/0.2.0` | `5.x` | `@dsh-0.2.0` |
|
|
46
|
-
>
|
|
47
|
-
> Le numéro de série suit la **ligne DSH** (`1.x` = DSH ≤ 0.1.1, `2.x` = DSH 0.1.2+), et les
|
|
48
|
-
> majeures se cloisonnent mutuellement : une installation `^1.x` ne résout jamais une version
|
|
49
|
-
> `2.x`, et inversement. `engines.dsh` exprime la même séparation, mais DSH ne le lit jamais —
|
|
50
|
-
> ce sont les plages et les dist-tags qui maintiennent un vieux DSH sur `1.x`.
|
|
51
|
-
>
|
|
52
|
-
> `@deepseek-ai/dsh-client-runtime` a été **supprimé** à partir de `0.1.2-alpha.1` — il n'a pas
|
|
53
|
-
> simplement été déplacé. La ligne `legacy` atteint toujours `ctx.slots` à travers lui ; `main`
|
|
54
|
-
> obtient la même déclaration depuis `@deepseek-ai/dsh-client-ui-renderer/client`. Deux coutures
|
|
55
|
-
> sensibles à la version sont gérées par des sondes de capacité plutôt que par des vérifications
|
|
56
|
-
> de version : (1) l'enregistrement des réglages utilise `register`, présent sur les deux lignes
|
|
57
|
-
> (`installSection` est un ajout, pas un remplacement) ; (2) `effectivePolicy` est une méthode
|
|
58
|
-
> **privée** du service d'approbation utilisateur sur les deux lignes ; elle est donc lue derrière
|
|
59
|
-
> une sonde `typeof` et se dégrade en « politique inconnue » lorsqu'elle est absente ou lève une
|
|
60
|
-
> erreur.
|
|
61
|
-
|
|
62
|
-
Version **2.6.0** — voir le [changelog](./CHANGELOG.fr.md).
|
|
63
|
-
|
|
64
|
-
Un gate de permissions unique, autosuffisant, déterministe d'abord et fail-closed, pour DeepSeek
|
|
65
|
-
Harness.
|
|
66
|
-
|
|
67
|
-
`dsh-perm-gate` tranche chaque appel d'outil selon une chaîne de priorité fixe :
|
|
68
|
-
|
|
69
|
-
| Étape | Décision | De quoi il s'agit |
|
|
70
|
-
| ---- | ---- | ---- |
|
|
71
|
-
| **P0** | `deny` | refus dur déterministe : matériau d'identifiants, mutation de chemin protégé, shell dangereux |
|
|
72
|
-
| **P1** | `allow` | un **octroi de session** précis et borné |
|
|
73
|
-
| **P2** | `deny/allow/ask` | chaîne de règles statique : d'abord la blacklist, puis l'allow, puis l'ask |
|
|
74
|
-
| **P3** | `allow/deny/ask` | classificateur sémantique LLM optionnel (**désactivé** par défaut) |
|
|
75
|
-
| **P4** | `ask` | seam d'approbation officiel |
|
|
76
|
-
|
|
77
|
-
Strictement fail-closed : une décision P0 n'est jamais annulée par un octroi, une règle, le
|
|
78
|
-
classificateur ou un humain.
|
|
79
|
-
|
|
80
|
-
## Pourquoi
|
|
81
|
-
|
|
82
|
-
L'écosystème de sécurité DSH éclate cela entre plusieurs plugins (`dsh-permission-rules`,
|
|
83
|
-
`dsh-auto-mode`, `dsh-auto-review`, `dsh-movein-permissions`). `dsh-perm-gate` fusionne le gate +
|
|
84
|
-
l'approbation + le classificateur (facultatif) en un seul paquet, avec une seule piste d'audit et
|
|
85
|
-
aucun couplage de versions entre plugins.
|
|
86
|
-
|
|
87
|
-
## Fonctionnalités
|
|
88
|
-
|
|
89
|
-
- **Liste blanche / liste noire de commandes** — filtrage sur une **décomposition argv** (pas une
|
|
90
|
-
chaîne brute), avec descente récursive dans `sh -c`/`bash -c`, détection de pipelines,
|
|
91
|
-
vérification des cibles de redirection et reconnaissance du récursif/forcé (`rm -rf`).
|
|
92
|
-
- **Priorité au deny** — une règle deny correspondante l'emporte sur toute règle allow.
|
|
93
|
-
- **Octrois de session** — des octrois précis `(outil, empreinte canonique)` avec `TTL` +
|
|
94
|
-
`maxUses` ; relancer avec une cible différente ne réutilise jamais l'autorité. Les sous-agents
|
|
95
|
-
héritent mais ne peuvent pas en frapper.
|
|
96
|
-
- **Moteur de règles à fonctions pures** — compilation glob/regex avec une borne ReDoS, échec
|
|
97
|
-
bruyant sur règle malformée, et cache de compilation par hash de la source.
|
|
98
|
-
- **Audit** — chaque décision est journalisée comme un événement `{ignorable:true}` avec son
|
|
99
|
-
`callId` ; la raison visible du modèle correspond au résultat enregistré.
|
|
100
|
-
- **Palier 自动审查** (`permissive`, plus `permissive-full`) — un **mode d'approbation
|
|
101
|
-
indépendant** (distinct des paliers lecture seule, accès complet et liste blanche) qui n'est ni
|
|
102
|
-
« auto-approbation » ni confiance aveugle. Le front expose un **interrupteur unique**
|
|
103
|
-
(`permissive`) ; les quatre stratégies backend sont **combinables** et pilotées par les réglages
|
|
104
|
-
du plugin — toujours fail-closed face à P0. Le sélecteur de permissions et la carte de réglages
|
|
105
|
-
l'affichent tous deux sous le libellé produit 自动审查, sans icône. La variante `permissive-full`
|
|
106
|
-
conserve un comportement d'approbation identique mais supprime le bac à sable de fichiers
|
|
107
|
-
intégré, qui sinon refuse les tubes nommés dont `git clone` / Cygwin / ConPTY ont besoin.
|
|
108
|
-
- **Réponse automatique aux escalades de sandbox** (`trustEscalation`) — une escalade de sandbox
|
|
109
|
-
est demandée depuis *l'intérieur* du corps de l'outil shell / pwsh / edit, après
|
|
110
|
-
`tools/pre-execute`, si bien que le gate ne l'a jamais vue et qu'un appel qu'il avait
|
|
111
|
-
auto-autorisé vous demandait quand même d'approuver l'élargissement. Avec cette stratégie
|
|
112
|
-
activée, c'est ici qu'est répondue l'exacte requête que le gate avait dégagée (repérée par
|
|
113
|
-
`callId`).
|
|
114
|
-
- **`llmAssist` à risque gradué** — un LLM personnalisé compatible OpenAI grade chaque `ask` en
|
|
115
|
-
`safe` / `risky:<catégorie>` ; les catégories dures (suppression, identifiants, distant,
|
|
116
|
-
système, masse) **demandent toujours**, le neutre alimente l'apprentissage des verdicts, et tout
|
|
117
|
-
échec reste fail-closed.
|
|
118
|
-
- **Apprentissage des verdicts** — les `ask` à risque neutre que l'humain approuve et qui
|
|
119
|
-
s'exécutent réellement s'accumulent ; passé le seuil, la *même opération exacte* (repérée par
|
|
120
|
-
empreinte) s'auto-autorise.
|
|
121
|
-
- **Flux d'événements de décision** — chaque décision est ajoutée à un flux JSONL et remontée par
|
|
122
|
-
la moitié navigateur sous forme de bandeau d'avis au-dessus de la saisie de conversation, plus
|
|
123
|
-
un onglet d'enregistrements d'approbation (du plus récent au plus ancien) dans la vue de
|
|
124
|
-
conversation.
|
|
125
|
-
|
|
126
|
-
Une **blacklist de mots-clés deny préconfigurée** (héritée des `DEFAULT_DENY_KEYWORDS` de
|
|
127
|
-
dsh-approval-gate : `rm -rf`, `push --force`, `drop table`, `mkfs`, `git reset --hard`,
|
|
128
|
-
`docker system prune`, …) oppose son veto à tout appel dont le texte contient un mot-clé —
|
|
129
|
-
sous-chaîne insensible à la casse, appliquée avant la liste blanche, les octrois et le LLM. Elle
|
|
130
|
-
est modifiable sous forme de liste dans la carte de réglages (les entrées préconfigurées sont
|
|
131
|
-
étiquetées, et une restauration en un clic ramène le préréglage) ; non définie ou vide, elle
|
|
132
|
-
applique le préréglage — la liste noire ne se désactive jamais silencieusement.
|
|
133
|
-
|
|
134
|
-
## Installation
|
|
135
|
-
|
|
136
|
-
Nécessite une installation existante de
|
|
137
|
-
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
|
|
138
|
-
|
|
139
|
-
```sh
|
|
140
|
-
dsh plugin --profile web add dsh-perm-gate
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Les étapes complètes d'installation, de mise à niveau, de migration et de dépannage se trouvent
|
|
144
|
-
dans le [Guide d'installation](./INSTALL.fr.md) — également disponible en
|
|
145
|
-
[English](./INSTALL.md) / [中文](./INSTALL.zh.md) / [日本語](./INSTALL.ja.md) /
|
|
146
|
-
[한국어](./INSTALL.ko.md) / [Deutsch](./INSTALL.de.md) / [Italiano](./INSTALL.it.md) /
|
|
147
|
-
[Русский](./INSTALL.ru.md) / [Español](./INSTALL.es.md).
|
|
148
|
-
|
|
149
|
-
## Configuration
|
|
150
|
-
|
|
151
|
-
Ajoutez le plugin au `cordis.yml` :
|
|
152
|
-
|
|
153
|
-
```yaml
|
|
154
|
-
- id: dsh-perm-gate
|
|
155
|
-
name: dsh-perm-gate
|
|
156
|
-
config:
|
|
157
|
-
rulesFile: ./permissions.yaml # optional; defaults to $DSH_HOME/perm-gate/rules.yml
|
|
158
|
-
dshHome: $DSH_HOME # root pinned for protected-target checks
|
|
159
|
-
defaultAction: ask # allow | ask | deny
|
|
160
|
-
gatePresets: [permissive, permissive-full] # tiers where the gate is active (default)
|
|
161
|
-
sessionSweep: true # hourly cleanup of archived/dead sessions' gate data
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Nettoyage des sessions
|
|
165
|
-
|
|
166
|
-
Au démarrage puis chaque heure, le gate lit le magasin d'espaces de travail de DSH
|
|
167
|
-
(`$DSH_HOME/storages/workspace.json`, en lecture seule) et classe chaque session pour laquelle il
|
|
168
|
-
détient des données de chaîne d'autorisation. Les sessions que DSH a archivées
|
|
169
|
-
(`global.archivedSessionIds`) ou ne suit plus du tout voient leurs événements de décision retirés
|
|
170
|
-
de `$DSH_HOME/perm-gate/events.jsonl` et leurs fichiers d'instantanés pré-modification supprimés
|
|
171
|
-
de `$DSH_HOME/perm-gate/snapshots/` — de l'historique que la page de revue ne peut plus atteindre,
|
|
172
|
-
pour des données que le harnais lui-même considère comme disparues. Les sessions vivantes ne sont
|
|
173
|
-
pas touchées, les lignes inattribuables (identifiant de session vide) ne sont jamais supprimées,
|
|
174
|
-
et tout échec est fail-open : la passe est ignorée et retentée une heure plus tard. Définissez
|
|
175
|
-
`sessionSweep: false` pour désactiver ; `workspaceStoreFile` remplace le chemin du magasin.
|
|
176
|
-
Restaurer une session archivée ne restaure pas son historique nettoyé.
|
|
177
|
-
|
|
178
|
-
### Fichier de règles
|
|
179
|
-
|
|
180
|
-
```yaml
|
|
181
|
-
permissions:
|
|
182
|
-
defaultAction: ask
|
|
183
|
-
deny:
|
|
184
|
-
- command: [rm#recursive]
|
|
185
|
-
reason: no recursive rm
|
|
186
|
-
- command: [ssh]
|
|
187
|
-
reason: no direct ssh
|
|
188
|
-
- paths: [.dsh/**]
|
|
189
|
-
reason: protect harness metadata
|
|
190
|
-
allow:
|
|
191
|
-
- command: [pnpm, node]
|
|
192
|
-
reason: dev tools
|
|
193
|
-
- command: [curl, wget]
|
|
194
|
-
args: ["https://*.example.com/*"]
|
|
195
|
-
reason: allowed endpoint
|
|
196
|
-
ask:
|
|
197
|
-
- command: [bash, sh]
|
|
198
|
-
reason: ask shells
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Une entrée de commande `word#flag` correspond au mot de commande (`word`) avec le modificateur
|
|
202
|
-
`recursive` ou `force` — ainsi `rm#recursive` correspond à `rm -rf`, `env rm -rf` et
|
|
203
|
-
`sh -c "rm -rf /"`.
|
|
204
|
-
|
|
205
|
-
### Politique réseau (opt-in)
|
|
206
|
-
|
|
207
|
-
Un proxy HTTP/CONNECT local qui arbitre le trafic sortant des **sous-processus shell** selon le
|
|
208
|
-
même fichier de règles, plus un chemin d'approbation pour les cibles qu'aucune règle ne couvre.
|
|
209
|
-
**Désactivé par défaut** — l'activer lie un port loopback et réécrit l'environnement proxy des
|
|
210
|
-
processus enfants ; il n'est donc jamais activé implicitement.
|
|
211
|
-
|
|
212
|
-
```yaml
|
|
213
|
-
- id: dsh-perm-gate
|
|
214
|
-
config:
|
|
215
|
-
networkEnabled: false # master switch (default false)
|
|
216
|
-
networkMode: whitelist # deny-all | whitelist | allow-all
|
|
217
|
-
networkUnlisted: ask # ask | deny — unlisted target handling
|
|
218
|
-
networkUnattributed: allow # allow | deny — traffic with no shell attribution
|
|
219
|
-
networkInjectEnv: true # rewrite HTTP(S)_PROXY/ALL_PROXY for children
|
|
220
|
-
networkAskTimeoutMs: 120000 # approval wait before failing closed
|
|
221
|
-
networkGrantTtlMs: 1800000 # how long one approval covers its target
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
**Comportement par paliers.** Rien n'atteint le réseau sans règle allow. Une cible non
|
|
225
|
-
répertoriée est escaladée vers le seam d'approbation interactif, soulevée au nom de la commande
|
|
226
|
-
shell qui a ouvert la connexion ; approuver élargit la portée pour cette cible pendant la session.
|
|
227
|
-
Une règle `deny` n'est **jamais** escaladée — l'approbation peut élargir ce qu'une cible non
|
|
228
|
-
répertoriée peut atteindre, mais elle ne peut jamais annuler une règle qui dit non.
|
|
229
|
-
|
|
230
|
-
**La limite — à lire avant de vous y fier.** Le proxy est une couche de politique *coopérative*,
|
|
231
|
-
pas une frontière d'application. Il ne voit que le trafic des clients qui lisent l'environnement
|
|
232
|
-
proxy :
|
|
233
|
-
|
|
234
|
-
| Client | Couvert ? |
|
|
235
|
-
|--------|----------|
|
|
236
|
-
| `curl`, `wget`, `git`, Go `net/http`, Python `requests` | oui |
|
|
237
|
-
| **Node.js `http`/`https`/`fetch`** | **non — connexion directe** |
|
|
238
|
-
| Java (sans options proxy `-D`), .NET `HttpClient` | non |
|
|
239
|
-
| Sockets bruts, TCP personnalisé | non |
|
|
240
|
-
| DNS, QUIC/HTTP3, protocoles non HTTP | non |
|
|
241
|
-
| Connexions vers une IP littérale | non |
|
|
242
|
-
|
|
243
|
-
Ainsi une commande shell comme `node -e "require('http').get('http://host/')"` n'est pas
|
|
244
|
-
interceptée. Considérez cela comme un garde-fou contre les accidents et un endroit pour exprimer
|
|
245
|
-
une intention, pas comme un bac à sable hermétique.
|
|
246
|
-
|
|
247
|
-
Le trafic réseau **propre** de DSH — les outils réseau intégrés et le transport LLM — est
|
|
248
|
-
délibérément laissé de côté. Ces connexions ne portent aucune attribution shell, et
|
|
249
|
-
`networkUnattributed: allow` (le défaut) les laisse passer sans revue : les examiner permettrait à
|
|
250
|
-
l'hôte de se bloquer *lui-même*, ce qui est un échec pire qu'un blocage manqué. Ne définissez
|
|
251
|
-
`networkUnattributed: deny` que si vous savez que les clients de votre hôte ignorent
|
|
252
|
-
l'environnement proxy.
|
|
253
|
-
|
|
254
|
-
Interrogez l'état en direct via `GET /api/dsh-perm-gate/network` (mode, bind, port, vivacité du
|
|
255
|
-
proxy, état d'injection d'env, compteurs de blocage, blocages récents).
|
|
256
|
-
|
|
257
|
-
## Le palier 自动审查 (valeur machine `permissive`)
|
|
258
|
-
|
|
259
|
-
自动审查 est un **palier d'approbation indépendant** du sélecteur de permissions DSH, parallèle à
|
|
260
|
-
Lecture seule / Écriture espace de travail / Accès complet / Liste blanche. Ce n'est **pas** une
|
|
261
|
-
« auto-approbation » générique et il ne frappe jamais d'autorité aveugle : il ne fait que
|
|
262
|
-
rétrécir ou élargir le seam *avant* l'étape humaine/LLM, tandis que le refus dur P0 reste
|
|
263
|
-
monotone et non négociable **dans le périmètre propre du gate**.
|
|
264
|
-
|
|
265
|
-
> **P0 est délimité, pas global.** Le gate n'agit que lorsque le préréglage de permissions de la
|
|
266
|
-
> session est l'un des `gatePresets` (défaut `permissive` / `permissive-full`). Sous tout autre
|
|
267
|
-
> préréglage — Lecture seule, Écriture espace de travail ou Accès complet — **tout** le gate se
|
|
268
|
-
> retire, refus dur P0 compris, parce que la politique propre au palier sélectionné régit cette
|
|
269
|
-
> session. C'est délibéré (voir `gatePresets` dans le tableau de configuration), mais cela
|
|
270
|
-
> signifie que « P0 est non négociable » vaut *à l'intérieur* des paliers du gate plutôt qu'à
|
|
271
|
-
> travers chaque palier. Un retrait n'est pas silencieux : le gate enregistre un événement
|
|
272
|
-
> `stand-down` par transition session/préréglage et le navigateur affiche un bandeau persistant
|
|
273
|
-
> **GATE OFF** au-dessus de la saisie. Définissez `gatePresets: ['*']` pour rendre P0 global à
|
|
274
|
-
> nouveau.
|
|
275
|
-
|
|
276
|
-
**Deux variantes sont livrées**, car le `sandbox` et l'`approval` d'un préréglage sont des
|
|
277
|
-
réglages indépendants et leur couplage imposait un mauvais compromis :
|
|
278
|
-
|
|
279
|
-
| Libellé du sélecteur | Valeur machine | sandbox | approval |
|
|
280
|
-
|--------------|---------------|---------|----------|
|
|
281
|
-
| 自动审查 | `permissive` | `workspace-write` | `ask` |
|
|
282
|
-
| 自动审查(高权限) | `permissive-full` | `danger-full-access` | `ask` |
|
|
283
|
-
|
|
284
|
-
Le palier simple conserve le bac à sable de fichiers intégré. Ce bac à sable refuse aussi les
|
|
285
|
-
tubes nommés dont un processus enfant a besoin pour démarrer, si bien que `git clone`, le
|
|
286
|
-
`sh.exe` MSYS2/Cygwin et ConPTY échouent dessous avec `Win32 error 5` /
|
|
287
|
-
`couldn't create signal pipe`. Le gate n'étant actif **que** dans les paliers listés dans
|
|
288
|
-
`gatePresets`, vouloir le gate impliquait d'accepter cette restriction. 自动审查(高权限)
|
|
289
|
-
supprime le couplage : comportement d'approbation identique, sans restriction de bac à sable de
|
|
290
|
-
fichiers. La description propre du palier énonce le compromis sans détour — le flux de travail
|
|
291
|
-
est plus fluide, les approbations s'appliquent toujours par appel, mais il ne reste **aucun bac à
|
|
292
|
-
sable système en filet de secours**. Les deux sont dans les `gatePresets` par défaut ; l'un comme
|
|
293
|
-
l'autre vous donne la chaîne complète P0–P4 — le gate ne lit que le **nom** du préréglage, jamais
|
|
294
|
-
le mode de sandbox.
|
|
295
|
-
|
|
296
|
-
Le libellé du sélecteur est une **chaîne produit fournie par l'hôte**, pas une entrée de
|
|
297
|
-
dictionnaire par locale : DSH rend verbatim le `name:` du palier d'un plugin sur les deux
|
|
298
|
-
surfaces de permission (la ligne par défaut des réglages généraux et le sélecteur du composer) et
|
|
299
|
-
ne fournit ses propres libellés localisés que pour les trois valeurs intégrées ;
|
|
300
|
-
`cordis.patch.yml` livre donc le libellé chinois pour chaque session.
|
|
301
|
-
|
|
302
|
-
**L'icône** est une autre histoire. La table de glyphes du composer est fermée et son propre
|
|
303
|
-
commentaire énonce la règle : *host-configured names outside the design set get none.*
|
|
304
|
-
`permissive` est une valeur intégrée, donc 自动审查 a déjà un glyphe bouclier+œil ;
|
|
305
|
-
`permissive-full` ne reçoit le même glyphe que parce que `npx dsh-perm-gate-patch-glyph` l'ajoute
|
|
306
|
-
à cette table. Ce patch modifie un paquet **hôte**, il se perd donc à chaque mise à niveau de
|
|
307
|
-
DSH — voir [Après une mise à niveau de DSH](./INSTALL.fr.md).
|
|
308
|
-
|
|
309
|
-
Dans `cordis.yml` :
|
|
310
|
-
|
|
311
|
-
```yaml
|
|
312
|
-
- id: dsh-perm-gate
|
|
313
|
-
name: dsh-perm-gate
|
|
314
|
-
config:
|
|
315
|
-
rulesFile: ./permissions.yaml
|
|
316
|
-
defaultAction: ask
|
|
317
|
-
permissive: true # single front-facing switch (independent tier on)
|
|
318
|
-
permissiveStrategies: # backend, combinable
|
|
319
|
-
trustAutoAllow: true # in-scope safe ops auto-allow; dangerous/unknown ask
|
|
320
|
-
alwaysConfirm: false # every crossing asks; allow-controls add repeat-allow / wl-migrate buttons
|
|
321
|
-
llmAssist: false # LLM classify first, human fallback on ask/failure
|
|
322
|
-
trustEscalation: true # a cleared call's own sandbox escalation needs no prompt
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
`trustAutoAllow` est le palier intermédiaire de base (une règle allow passe automatiquement).
|
|
326
|
-
`alwaysConfirm` affiche le panneau d'approbation à chaque franchissement ; ses « contrôles allow »
|
|
327
|
-
ajoutent deux boutons étendus — **ré-autoriser ce type pour cette session** (un octroi de session
|
|
328
|
-
borné) et **autoriser chaque occurrence** (qui persiste le mot de commande dans la liste blanche
|
|
329
|
-
`allow` du `permissions.yaml` via `approveAllowEverywhere`). `llmAssist` consulte un vrai LLM
|
|
330
|
-
configurable (`classifierEndpoint` / `classifierModel`, toute API compatible OpenAI) pour trancher
|
|
331
|
-
automatiquement un `ask`, et retombe sur le seam humain en cas d'`ask`/erreur — toujours
|
|
332
|
-
fail-closed. `trustEscalation` (actif par défaut quand le palier l'est) répond à une escalade
|
|
333
|
-
`sandbox_permissions` soulevée depuis l'intérieur d'un appel que le gate a déjà autorisé ; voir
|
|
334
|
-
ci-dessous. Quand `permissive` est désactivé, le gate se comporte exactement comme avant.
|
|
335
|
-
|
|
336
|
-
### Escalade de sandbox : pourquoi un verdict `safe` provoquait quand même une invite
|
|
337
|
-
|
|
338
|
-
Un appel d'outil peut soulever **deux approbations indépendantes**. Le gate possède la première —
|
|
339
|
-
son propre `ask`, sur la cascade `tools/pre-execute`. La seconde vient d'`approveEscalation`
|
|
340
|
-
**dans le corps de l'outil**, au moment de `tools/execute`, chaque fois que le modèle a transmis
|
|
341
|
-
`sandbox_permissions` + `justification` ; à ce stade, `tools/pre-execute` est déjà tranché, si
|
|
342
|
-
bien que l'allow du gate ne l'atteint jamais. Un appel que le LLM avait gradé `safe` et que le
|
|
343
|
-
gate avait auto-autorisé affichait donc quand même une invite demandant d'approuver
|
|
344
|
-
l'élargissement de sandbox.
|
|
345
|
-
|
|
346
|
-
`trustEscalation` comble ce fossé. Le gate se souvient de chaque appel qu'il a positivement
|
|
347
|
-
autorisé (indexé par le `callId` de l'hôte, que la requête d'escalade répète) et répond lui-même
|
|
348
|
-
à l'escalade `allowed-once`. Elle ne s'applique que si **tout** est réuni :
|
|
349
|
-
|
|
350
|
-
- le palier 自动审查 est actif et `trustEscalation` est activé ;
|
|
351
|
-
- la requête porte un `callId` que le gate a dégagé, avec un nom d'outil correspondant ;
|
|
352
|
-
- la raison est une escalade reconnue nommant `workspace-write` ou `danger-full-access`.
|
|
353
|
-
|
|
354
|
-
Tout le reste — une raison non reconnue, un autre appel, un appel que le gate a demandé ou
|
|
355
|
-
refusé, le passe-plat `approval: never` — est délégué à l'humain à l'identique, si bien qu'un
|
|
356
|
-
futur changement de formulation de DSH échoue fermé plutôt qu'ouvert. La réponse automatique est
|
|
357
|
-
enregistrée sur le flux d'événements (`verdict: "escalation-auto"`, `mode: <cible>`). Désactivez
|
|
358
|
-
l'interrupteur pour garder l'élargissement de sandbox sous contrôle humain pendant que les autres
|
|
359
|
-
allows restent automatiques.
|
|
360
|
-
|
|
361
|
-
### llmAssist à risque gradué, apprentissage des verdicts et flux d'événements
|
|
362
|
-
|
|
363
|
-
Quand `llmAssist` est actif, le LLM configuré grade un `ask` à la fois selon un protocole
|
|
364
|
-
structuré. La notation se fait **dans la cascade `tools/pre-execute` du gate, avant que la
|
|
365
|
-
décision ne soit renvoyée à l'hôte** : un verdict `safe` délègue l'appel directement, si bien que
|
|
366
|
-
le panneau d'approbation n'apparaît jamais ; seul un verdict réellement incertain vous parvient.
|
|
367
|
-
Deux sources de récepteur sont sélectionnables dans la carte de réglages : une **API
|
|
368
|
-
personnalisée** (tout endpoint compatible OpenAI — `classifierEndpoint` / `classifierModel` /
|
|
369
|
-
`classifierApiKey`, avec des préréglages dont Xiaomi MiMo `https://api.xiaomimimo.com/v1`), ou le
|
|
370
|
-
**groupe de modèles hôte DSH** (le service `llm` configuré de la session, via
|
|
371
|
-
`agentDefaultModel.currentSelection`, avec possibilité de le remplacer par `classifierProvider` /
|
|
372
|
-
`classifierModel`). Un bouton de **test de santé** (`POST /api/dsh-perm-gate/health`) exécute une
|
|
373
|
-
complétion minimale et rapporte la latence.
|
|
374
|
-
|
|
375
|
-
- `safe` → l'appel est auto-autorisé (audité comme source `classifier`) ; aucun panneau n'est
|
|
376
|
-
affiché.
|
|
377
|
-
- `risky` + une **catégorie dure** (`deletion`, `credential`, `remote`, `system`, `bulk`) →
|
|
378
|
-
l'appel **conserve l'ask humain**. Le classificateur **ne refuse jamais** : le chemin de deny
|
|
379
|
-
appartient aux seules couches déterministes (refus dur P0, blacklist de mots-clés deny, règles
|
|
380
|
-
`deny:` explicites), si bien qu'une catégorie mal gradée reste toujours négociable au lieu d'un
|
|
381
|
-
blocage sans recours. Les catégories dures restent distinctes de `neutral` d'une manière qui
|
|
382
|
-
compte : elles ne sont **jamais apprises**, si bien que des approbations humaines répétées ne
|
|
383
|
-
peuvent jamais les sédimenter en auto-allow.
|
|
384
|
-
(Refuser sur la parole du modèle a été mesuré en conditions réelles : un bénin
|
|
385
|
-
`git commit -F …` gradé `remote` a produit un auto-deny sans panneau et sans octroi pour
|
|
386
|
-
retenter.)
|
|
387
|
-
- `risky:neutral` → avec `riskLearning` activé (carte Réglages, désactivé par défaut), chaque
|
|
388
|
-
approbation humaine réellement exécutée (solde via l'événement `tools/result` de l'hôte) compte
|
|
389
|
-
pour une clé `outil|catégorie` ; dès que le compte atteint `riskThreshold` (défaut 3) **et** que
|
|
390
|
-
l'empreinte d'opération (mot de commande + basename de la cible) du nouvel appel correspond à un
|
|
391
|
-
échantillon confirmé, la même opération exacte s'auto-autorise. Une cible différente ne réutilise
|
|
392
|
-
jamais cette autorité. Avec la sédimentation (`riskSediment`, actif par défaut), les échantillons
|
|
393
|
-
confirmés d'une clé au seuil deviennent des **règles allow déterministes** : une correspondance
|
|
394
|
-
exacte autorise net, sans nouvel appel LLM — même avec `llmAssist` désactivé — et les règles
|
|
395
|
-
sédimentées sont visibles et gérables (terminer / supprimer) dans la carte de réglages.
|
|
396
|
-
- Les dépassements de délai (`riskTimeoutMs`, défaut 20 s, une seule nouvelle tentative), les
|
|
397
|
-
échecs de transport et les sorties hors protocole laissent l'ask intact — le gate ne devine
|
|
398
|
-
jamais.
|
|
399
|
-
|
|
400
|
-
L'état d'apprentissage persiste dans un JSON possédé par le plugin
|
|
401
|
-
(`$DSH_HOME/perm-gate/learning.json`, ou `learningFile`), jamais dans votre fichier de règles
|
|
402
|
-
YAML. Chaque décision est ajoutée à `$DSH_HOME/perm-gate/events.jsonl` (ou `eventsFile`) et servie
|
|
403
|
-
à `GET /api/dsh-perm-gate/events?sessionId=&since=` ; la moitié navigateur l'interroge et affiche
|
|
404
|
-
la dernière décision en bandeau d'avis au-dessus de la saisie de conversation (les asks restent
|
|
405
|
-
visibles jusqu'à l'événement suivant) et liste toutes les décisions de la session, de la plus
|
|
406
|
-
récente à la plus ancienne, dans l'onglet **Approbations** de la vue de conversation.
|
|
407
|
-
|
|
408
|
-
Les fichiers affectés de chaque décision sont copiés en instantanés avant que la modification
|
|
409
|
-
n'atterrisse (≤ 5 fichiers, ≤ 256 Ko chacun) sous `$DSH_HOME/perm-gate/snapshots/` ; dans l'onglet
|
|
410
|
-
**Approbations**, chaque pastille de fichier ouvre un diff de lignes
|
|
411
|
-
(`GET /api/dsh-perm-gate/diff`) avec une action de **revert** qui délivre une instruction de
|
|
412
|
-
restauration dans la conversation (`POST /api/dsh-perm-gate/revert`). Une barre d'inventaire des
|
|
413
|
-
instantanés les efface par session ou entièrement (`GET /api/dsh-perm-gate/snapshots-stats` /
|
|
414
|
-
`POST /api/dsh-perm-gate/snapshots-clear`).
|
|
415
|
-
|
|
416
|
-
Un `ask` que le gate route vers un humain est suivi jusqu'à la réponse de l'humain : un
|
|
417
|
-
observateur passif `approval/request` enregistre l'issue close (`allowed-once` → **approuvé**,
|
|
418
|
-
`rejected` → **rejeté**, `cancelled` → **annulé**, `unavailable` → un refus, puisqu'aucun canal
|
|
419
|
-
d'approbation n'existait), avec `tools/result` qui solde le même ask en secours lorsque
|
|
420
|
-
l'observateur ne peut pas le corréler. Les approbations rapportent la progression d'apprentissage
|
|
421
|
-
post-approbation (`n`/seuil), et le bandeau d'avis étiquette les trois états terminaux.
|
|
422
|
-
|
|
423
|
-
自动审查 et 自动审查(高权限) dessinent toutes deux le glyphe bouclier+œil dans le sélecteur —
|
|
424
|
-
la première depuis la table intégrée de DSH, la seconde depuis le patch hôte que décrit le guide
|
|
425
|
-
d'installation. Sans ce patch, le second palier est en texte seul sur toutes les surfaces ; son
|
|
426
|
-
libellé et son filtrage ne sont pas affectés.
|
|
427
|
-
|
|
428
|
-
### Un palier de session sélectionnable
|
|
429
|
-
|
|
430
|
-
`cordis.patch.yml` ajoute un préréglage `permissive` (`sandbox: workspace-write`,
|
|
431
|
-
`approval: ask`, nom **自动审查**) entre Écriture espace de travail et Accès complet. Le patch de
|
|
432
|
-
bundle DSH remplace toute la table `permission.config.presets` au lieu de fusionner par clé, si
|
|
433
|
-
bien que le fichier réénonce aussi les trois intégrés (`read-only` / `workspace-write` /
|
|
434
|
-
`danger-full-access`, issus de `@deepseek-ai/dsh-base/cordis.patch.yml`) ;
|
|
435
|
-
`test/patch-presets.spec.ts` épingle ce jeu de clés. Ainsi, le sélecteur de permissions de session
|
|
436
|
-
propose 自动审查 comme palier d'approbation indépendant sélectionnable, et non un mode générique
|
|
437
|
-
d'« auto-approbation ».
|
|
438
|
-
|
|
439
|
-
Le gate n'est actif **que dans les paliers listés dans `gatePresets`** (défaut
|
|
440
|
-
`['permissive', 'permissive-full']`, les deux paliers qu'ajoute ce plugin). Dans tout autre
|
|
441
|
-
palier — Lecture seule, Écriture espace de travail, Accès complet, `custom` — le flux de décision
|
|
442
|
-
du gate ne s'exécute pas du tout : aucun allow, aucun ask, aucun deny, aucun refus dur P0, aucun
|
|
443
|
-
veto de mot-clé deny, aucun événement d'audit. La politique propre au palier sélectionné régit
|
|
444
|
-
l'appel, et c'est le propos : le `danger-full-access` intégré est défini comme « accès complet
|
|
445
|
-
sans invites d'approbation », donc le contourner par un ask (auquel on ne peut répondre là-bas —
|
|
446
|
-
le seam d'approbation refuse avant que le moindre répondant ne s'exécute, produisant
|
|
447
|
-
`the user rejected tool "..."` sans panneau) ou par un refus dur contredirait silencieusement le
|
|
448
|
-
palier choisi par l'utilisateur. `gatePresets: ['*']` rend le gate global à nouveau (refus dur
|
|
449
|
-
compris) ; à l'intérieur d'un palier actif, un `ask` est toujours dégradé en passe-plat lorsque
|
|
450
|
-
la politique d'approbation effective de la session est `never` — voilà pourquoi les deux paliers
|
|
451
|
-
自动审查 déclarent `approval: ask`.
|
|
452
|
-
|
|
453
|
-
### Configurable dans l'interface
|
|
454
|
-
|
|
455
|
-
Le palier est également ajustable à l'exécution depuis **Réglages → Plugins → 自动审查** (une
|
|
456
|
-
page `settings.plugins.tab` rendue par le client navigateur du plugin) : un interrupteur bascule
|
|
457
|
-
`permissive`, et quatre bascules éditent les `permissiveStrategies` backend. L'hôte lit l'espace
|
|
458
|
-
de noms en direct, si bien qu'un changement s'applique à l'appel d'outil suivant sans redémarrage.
|
|
459
|
-
C'est une classe d'approbation indépendante, PAS un mode générique d'« auto-approbation ».
|
|
460
|
-
|
|
461
|
-
### Test de règle (dry-run)
|
|
462
|
-
|
|
463
|
-
La même page embarque un panneau de **test de règle** : saisissez un nom d'outil et une commande,
|
|
464
|
-
appuyez sur Tester, et le gate juge cet appel au regard du jeu de règles qu'il a actuellement
|
|
465
|
-
chargé — sans rien exécuter et sans écrire aucune règle. Vous obtenez le verdict, la règle
|
|
466
|
-
correspondante (index et action), les dimensions que cette règle contraint, et la raison.
|
|
467
|
-
|
|
468
|
-
Il rapporte à la fois le verdict effectif (toute la chaîne P0 → P1 → P2 → P3 → P4) *et* la
|
|
469
|
-
réponse propre de la couche de règles, qui ne sont pas la même chose : un refus dur P0 ou un
|
|
470
|
-
mot-clé deny de préréglage se déclenche avant la chaîne de règles et ne laisse aucun index de
|
|
471
|
-
règle derrière lui, si bien que le panneau dit « aucune règle correspondante » plutôt que de
|
|
472
|
-
nommer une règle sans rapport. Une note « 0 règles chargées » signifie que le chemin `rulesFile`
|
|
473
|
-
n'a résolu aucun fichier.
|
|
474
|
-
|
|
475
|
-
Le panneau communique avec `POST /api/dsh-perm-gate/dry-run`, qui est **en lecture seule par
|
|
476
|
-
construction** — il n'a aucune forme d'écriture, si bien que tester une règle ne peut jamais la
|
|
477
|
-
modifier.
|
|
478
|
-
|
|
479
|
-
## CLI
|
|
480
|
-
|
|
481
|
-
Dry-run d'un appel contre un fichier de règles (sans harnais) :
|
|
482
|
-
|
|
483
|
-
```sh
|
|
484
|
-
dsh-perm-gate --rules permissions.yaml --tool bash --args '{"command":"pnpm install"}'
|
|
485
|
-
dsh-perm-gate --rules permissions.yaml --list
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
## Développement
|
|
489
|
-
|
|
490
|
-
```sh
|
|
491
|
-
npm run typecheck
|
|
492
|
-
npm test
|
|
493
|
-
npm run build
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
## Licence
|
|
497
|
-
|
|
498
|
-
[MIT](./LICENSE)
|