dsh-permission-rules 0.6.21 → 0.7.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.
- package/CHANGELOG.md +20 -0
- package/README.es.md +6 -1
- package/README.hi.md +6 -1
- package/README.md +6 -1
- package/README.pt.md +6 -1
- package/README.zh.md +6 -1
- package/docs/rules-format.en.md +2 -1
- package/docs/rules-format.md +2 -1
- package/lib/client.js +6924 -37
- package/lib/client.js.map +1 -1
- package/lib/index.js +671 -28
- package/lib/typert.host.js +74 -3
- package/lib/types/allow-host.d.ts +151 -0
- package/lib/types/allow-host.d.ts.map +1 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/remote.d.ts +54 -1
- package/lib/types/client/remote.d.ts.map +1 -1
- package/lib/types/client/section.d.ts +3 -1
- package/lib/types/client/section.d.ts.map +1 -1
- package/lib/types/config.d.ts +22 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/prose.d.ts +1 -0
- package/lib/types/prose.d.ts.map +1 -1
- package/lib/types/proxy.d.ts +73 -0
- package/lib/types/proxy.d.ts.map +1 -1
- package/lib/types/remote-service.d.ts +9 -1
- package/lib/types/remote-service.d.ts.map +1 -1
- package/lib/types/rules.d.ts +11 -0
- package/lib/types/rules.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +76 -11
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/typert.host.d.ts +50 -0
- package/lib/types/typert.host.d.ts.map +1 -1
- package/lib/types/wire.d.ts +191 -0
- package/lib/types/wire.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/allow-host.ts +307 -0
- package/src/client/index.ts +7 -1
- package/src/client/locales.ts +36 -0
- package/src/client/remote.ts +4 -1
- package/src/client/section.ts +83 -10
- package/src/config.ts +56 -0
- package/src/prose.ts +6 -0
- package/src/proxy.ts +202 -16
- package/src/remote-service.ts +33 -1
- package/src/rules.ts +11 -2
- package/src/runtime.ts +243 -9
- package/src/wire.ts +119 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## v0.7.0 - 2026-09-10
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- `network.upstreamProxy` — chain the connections this plugin **allows** through an upstream proxy: `off` (default — allowed connections dial directly), `inherit` (reuse the proxy names from the launch environment), or an explicit `http(s)://` proxy URL. CONNECT requests are chained by asking the upstream for a tunnel (`CONNECT host:port`); plain-HTTP requests are forwarded to the upstream in absolute form. A SOCKS (`socks:`/`socks5:`), non-http(s), blank or unparseable value fails the mount with a `TypeError` naming `network.upstreamProxy` (Node has no SOCKS client, so SOCKS is refused rather than attempted).
|
|
6
|
+
- `/rules network` renders one `Upstream: …` line and the settings page one `Upstream` row, both fed by the new `upstream` block of `networkSnapshot()`: `mode` (`off` | `inherit` | `url`), the redacted `http`/`https` candidates, `active`, and the `chained` counter.
|
|
7
|
+
- Settings-page **"allow this host"** action (issue #19 item 3): every recent interception now offers an **Allow** button that writes ONE minimal `match: { network: { domains: [<host>] } }` / `action: allow` rule at index 0 of the nearest rule file that actually judges that connection — the project file of the workspace the block was attributed to, or, for a session-less host-level block, the file the host chain resolves — preserving the file's comments and untouched rules. The rule goes at the head because rules are first-match-wins; the write re-reads the workspace chains AND invalidates the session-less host chain, so it takes effect immediately with no restart. The decision is recomputed after the write, so the page reports the REAL outcome (an already-allowed target writes nothing; a write that leaves the target blocked reads as a failure, never a success). It refuses, leaving the file untouched, an unknown workspace, a target outside `knownRuleSources()`, the read-only built-in baseline, and a file it cannot read or parse. The new `permissionRules/allowHost` RPC accepts a workspace `cwd`, never a path, so it has exactly the same write boundary as `rulesSave` and grants no new capability.
|
|
8
|
+
- `network.allowHostAction` (default `true`) — switches the settings-page allow action off: the Allow button is hidden and the `permissionRules/allowHost` RPC refuses. The page's rule editor is unaffected (it is deliberately wider: it writes arbitrary rule text).
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- Two cases never chain even when an upstream is configured: a **loopback** target (a proxy outside this host cannot route its loopback) and any decision produced by an **`ips`-scoped rule** (chaining hands the hostname to the upstream, so the issue #21 invariant — the connection lands on an address the rules saw — would stop holding exactly where the rules cared about the address; those decisions keep dialing the adjudicated address directly). A target whose scheme has no usable upstream is not chained either. A blocked target never reaches the upstream: it still gets this plugin's structured 403.
|
|
13
|
+
- An upstream URL carrying credentials is never emitted raw: warnings, `/rules network` and the settings snapshot print it through `redactProxyUrl` with the password masked (`http://user:***@host:port`).
|
|
14
|
+
- A configured upstream that points at this proxy's own bind address and port is treated as a self-loop: chaining is disabled for it and warned about once, instead of recursing into this proxy.
|
|
15
|
+
- An unreachable upstream, a timeout (10 s), or a non-2xx answer from the upstream yields **502** — deliberately no silent fallback to a direct dial, so a misconfiguration stays visible.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- An ambient proxy used to be discarded silently when `network.upstreamProxy` was `off` (the default): the plugin now logs one warning saying the ambient proxy is discarded for traffic through this proxy (it is still exported to subprocesses). `inherit` warns too when `injectEnv` is false or when the launch environment named nothing usable.
|
|
20
|
+
|
|
1
21
|
## v0.6.21 - 2026-09-10
|
|
2
22
|
|
|
3
23
|
### Fixed
|
package/README.es.md
CHANGED
|
@@ -77,8 +77,10 @@ Una **política de red a nivel de proceso** estilo Codex: el tráfico de subproc
|
|
|
77
77
|
- **`auto`** (por defecto) — sigue el preset del sandbox; en hosts sin el servicio de política de sandbox se resuelve a `autoFallback` (`allow-all`).
|
|
78
78
|
|
|
79
79
|
- **Emparejamiento** — `match.network` con `domains` / `ips` / `ports` / `schemes` (globs, comodines, CIDR, rangos de puertos; se aceptan puertos YAML numéricos). La extracción de candidatos URL en la ruta caliente `tools/pre-execute` se dispara sobre argumentos de herramientas web y URLs embebidas en texto de comandos bash/pwsh; los destinos de loopback pueden cortocircuitar reglas según la política `loopback`. Los literales IPv6 mapeados a IPv4 se normalizan a su forma IPv4 antes de comparar, y el proxy se conecta a las direcciones con las que se tomó la decisión, sin una segunda resolución DNS.
|
|
80
|
+
- **Encadenado a un proxy ascendente** — `network.upstreamProxy` (por defecto `off`) envía las conexiones que este proxy **permite** a través de un proxy ascendente: CONNECT le pide un túnel (`CONNECT host:port`) y las peticiones HTTP simples se le reenvían en forma absoluta. Un destino bloqueado nunca llega al proxy ascendente: sigue recibiendo el 403 estructurado de este plugin. Dos casos no se encadenan nunca aunque haya un proxy ascendente configurado: un destino de **loopback** (un proxy fuera de este host no puede enrutar su loopback) y cualquier decisión producida por una **regla con ámbito `ips`** (encadenar entrega el nombre de host al proxy ascendente, así que «la conexión aterriza en una dirección que las reglas vieron» — issue #21 — dejaría de cumplirse justo donde a las reglas les importaba la dirección; esas decisiones siguen conectando directamente a la dirección adjudicada), más un destino cuyo esquema no tenga un proxy ascendente utilizable. El nombre de host del propio proxy ascendente es configuración del operador, no entrada del agente, y no lo adjudican estas reglas. Una URL con credenciales nunca se emite en claro: los avisos, `/rules network` y la instantánea de settings enmascaran la contraseña (`http://user:***@host:port`). Un proxy ascendente inalcanzable, un timeout (10 s) o una respuesta no 2xx producen **502**; no hay a propósito una caída silenciosa a conexión directa, para que una mala configuración siga siendo visible.
|
|
80
81
|
- **Auditoría** — las conexiones denegadas anexan `permissionRules/network` a la sesión propietaria (la misma puerta adaptativa `ignorable`), con contadores de bloqueo e intercepciones recientes en `/rules network` y la página de settings.
|
|
81
82
|
- **Diagnóstico** — las conexiones bloqueadas llevan un mensaje `[network: …]` que nombra el destino bloqueado, el modo o la regla que decidió y la solución.
|
|
83
|
+
- **Permitir desde la página de settings** — cada intercepción reciente ofrece una acción **Permitir**. Escribe UNA regla mínima `match: { network: { domains: [<host>] } }` / `action: allow` en la **parte superior** (índice 0) del archivo de reglas más cercano que realmente adjudica esa conexión: el archivo de proyecto del workspace al que se atribuyó el bloqueo (el `rulesFile` absoluto cuando hay uno configurado) o, para un bloqueo a nivel de host sin sesión, el archivo que resuelve la cadena del host (`rulesFile` absoluto → `<processCwd>/<rulesFile>` existente → `fallbackPath` configurado → `<processCwd>/<rulesFile>`, creado). Con varios workspaces cargados, la página ofrece primero un selector, porque cualquier cadena de workspace cargada tiene prioridad sobre la cadena del host. En el índice 0 porque las reglas son de primera coincidencia: añadida después de un `deny` existente, la regla sería texto muerto. Solo `domains`, porque esa dimensión incluye subdominios y es independiente de puerto/esquema, así que el host no vuelve a bloquearse en su siguiente puerto; los comentarios y las reglas no tocadas se conservan, y el texto pasa por la misma puerta de validación que una edición manual. El cambio surte efecto de inmediato —se releen las cadenas de workspace en caché Y la cadena de host sin sesión, sin reinicio y sin `/rules reload`— y la decisión se recalcula, así que el aviso informa del resultado REAL y nunca afirma éxito mientras la conexión sigue bloqueada. Rechaza, sin tocar el archivo, un workspace desconocido, un destino fuera de las fuentes de reglas conocidas, la línea base integrada de solo lectura y un archivo que no puede leer o analizar; una conexión ya permitida no escribe nada. `network.allowHostAction: false` oculta el botón y hace que el RPC rechace.
|
|
82
84
|
|
|
83
85
|
## Quick start
|
|
84
86
|
|
|
@@ -131,6 +133,8 @@ Todos los parámetros son campos Schemastery `Config` (modificables desde cordis
|
|
|
131
133
|
| `network.loopback` | `allow` | Destinos de loopback: `allow` (paridad Codex) o `policy` |
|
|
132
134
|
| `network.injectEnv` | `true` | Si se inyectan variables de entorno del proxy para subprocesos |
|
|
133
135
|
| `network.noProxy` | `clear` | Manejo de NO_PROXY en subprocesos: `clear` aplica la política o `preserve` |
|
|
136
|
+
| `network.upstreamProxy` | `off` | Proxy ascendente para las conexiones que este plugin PERMITE: `off` conecta directamente, `inherit` reutiliza los nombres de proxy del entorno de lanzamiento, o una URL de proxy `http(s)://` explícita |
|
|
137
|
+
| `network.allowHostAction` | `true` | Acción «permitir este host» por bloque de la página de settings: `false` oculta el botón Permitir y hace que el RPC `permissionRules/allowHost` rechace (el editor de reglas no se ve afectado) |
|
|
134
138
|
| `builtin.enabled` | `true` | Línea base de alto riesgo integrada: `false` deshabilita por completo el conjunto deny/ask embarcado |
|
|
135
139
|
| `builtin.path` | *(embarcado)* | Archivo de línea base de reemplazo (absoluto, o relativo a `process.cwd()`); validado al montar |
|
|
136
140
|
|
|
@@ -143,7 +147,7 @@ Todos los parámetros son campos Schemastery `Config` (modificables desde cordis
|
|
|
143
147
|
| `permissionRules/decision` | event | Auditoría solo de registro para cada acierto y paso directo |
|
|
144
148
|
| `permissionRules/network` | event | Auditoría de capa de proxy para conexiones bloqueadas |
|
|
145
149
|
| HTTP/CONNECT proxy | service | Proxy local integrado que gobierna el tráfico de subprocesos de shell |
|
|
146
|
-
| settings page | client | Editor de modo de red, editor de reglas, contadores de bloqueo, intercepciones recientes |
|
|
150
|
+
| settings page | client | Editor de modo de red, editor de reglas, contadores de bloqueo, intercepciones recientes con una acción de permiso por bloque |
|
|
147
151
|
|
|
148
152
|
```
|
|
149
153
|
/rules list the active rules, their source files, and any last-reload error
|
|
@@ -176,6 +180,7 @@ Todos los parámetros son campos Schemastery `Config` (modificables desde cordis
|
|
|
176
180
|
- **Los globs son un subconjunto conservador.** Sin expansión de llaves — escribe dos patrones, o usa modo regex.
|
|
177
181
|
- **La guardia de backtracking de regex es estructural, no exhaustiva.** Prefiere el modo glob para archivos no confiables.
|
|
178
182
|
- **Las peticiones salientes del propio host no pasan por este proxy.** El entorno de proxy inyectado cubre los **subprocesos de shell derivados** —que es para lo que sirve la inyección— y cualquier consumidor que lea los nombres de proxy en el momento de la petición. **No** cubre el tráfico basado en `fetch` del propio proceso del host: el launcher instala el dispatcher global de undici desde el **entorno de lanzamiento** antes de que se monte el primer plugin, ese dispatcher enruta por su política y no por el entorno, y Node muestrea el entorno de proxy al arrancar, así que un plugin que se monte después no puede redirigirlo. Consecuencias: los endpoints del proveedor **no** necesitan una regla allow, y `network.injectEnv: false` no saca al proceso del host de nada. Medido el 2026-09-10 en Node 22; reproducible con `scripts/host-egress-probe.mjs`.
|
|
183
|
+
- **El encadenado solo cubre lo que llega a este proxy.** En `dsh-v0.1.3-alpha.1` y posteriores, la propia política de proxy de lanzamiento del harness y su capa de entorno para subprocesos pueden tener precedencia sobre los nombres de proxy que inyecta este plugin, así que `network.upstreamProxy` describe qué ocurre con las conexiones que este proxy maneja — no es una afirmación sobre todas las rutas salientes del proceso del host.
|
|
179
184
|
|
|
180
185
|
## Collaborating with dsh-auto-review
|
|
181
186
|
|
package/README.hi.md
CHANGED
|
@@ -77,8 +77,10 @@ Codex-शैली की **प्रक्रिया-स्तरीय न
|
|
|
77
77
|
- **`auto`** (डिफ़ॉल्ट) — sandbox preset का अनुसरण करता है; बिना sandbox-नीति सेवा वाले hosts पर `autoFallback` (`allow-all`) में हल होता है।
|
|
78
78
|
|
|
79
79
|
- **मिलान** — `match.network` `domains` / `ips` / `ports` / `schemes` के साथ (globs, वाइल्डकार्ड, CIDR, पोर्ट श्रेणियाँ; संख्यात्मक YAML पोर्ट स्वीकृत)। `tools/pre-execute` हॉट पथ पर URL-उम्मीदवार निष्कर्षण वेब-टूल तर्कों और bash/pwsh कमांड टेक्स्ट में एम्बेडेड URLs पर चलता है; लूपबैक लक्ष्य `loopback` नीति के अनुसार नियमों को शॉर्ट-सर्किट कर सकते हैं। IPv4-मैप्ड IPv6 लिटरल तुलना से पहले IPv4 रूप में सामान्यीकृत होते हैं, और प्रॉक्सी उन्हीं पतों पर कनेक्ट करता है जिन पर निर्णय लिया गया — दूसरा DNS रिज़ॉल्यूशन नहीं।
|
|
80
|
+
- **अपस्ट्रीम श्रृंखलन** — `network.upstreamProxy` (डिफ़ॉल्ट `off`) इस प्रॉक्सी द्वारा **अनुमत** कनेक्शनों को अपस्ट्रीम प्रॉक्सी से भेजता है: CONNECT उससे सुरंग माँगता है (`CONNECT host:port`), और सादे HTTP अनुरोध पूर्ण रूप में उसे अग्रेषित होते हैं। अवरुद्ध लक्ष्य कभी अपस्ट्रीम तक नहीं पहुँचता — उसे इस प्लगइन का संरचित 403 ही मिलता है। अपस्ट्रीम कॉन्फ़िगर होने पर भी दो स्थितियाँ कभी श्रृंखलित नहीं होतीं: **लूपबैक** लक्ष्य (इस होस्ट के बाहर का प्रॉक्सी उसके लूपबैक को रूट नहीं कर सकता), और **`ips`-स्कोप वाले नियम** से उत्पन्न कोई भी निर्णय (श्रृंखलन होस्टनाम अपस्ट्रीम को सौंप देता है, इसलिए "कनेक्शन उसी पते पर पहुँचता है जो नियमों ने देखा" — issue #21 — ठीक वहीं टूट जाता जहाँ नियमों को पते की परवाह थी; ऐसे निर्णय निर्णीत पते पर ही सीधे कनेक्ट करते रहते हैं), साथ ही वह लक्ष्य जिसके scheme के लिए कोई उपयोगी अपस्ट्रीम न हो। अपस्ट्रीम का अपना होस्टनाम ऑपरेटर कॉन्फ़िगरेशन है, एजेंट इनपुट नहीं, और इन नियमों द्वारा निर्णीत नहीं होता। क्रेडेंशियल वाला URL कभी कच्चा नहीं छपता: चेतावनियाँ, `/rules network` और settings स्नैपशॉट पासवर्ड मास्क करते हैं (`http://user:***@host:port`)। अपस्ट्रीम अगम्य हो, टाइमआउट (10 सेकंड) हो, या गैर-2xx उत्तर दे — तो **502** मिलता है; सीधे कनेक्शन पर मौन वापसी जानबूझकर नहीं है, ताकि गलत कॉन्फ़िगरेशन दिखता रहे।
|
|
80
81
|
- **ऑडिट** — अस्वीकृत कनेक्शन स्वामी सत्र में `permissionRules/network` जोड़ते हैं (वही अनुकूली `ignorable` द्वार), `/rules network` और settings पृष्ठ में ब्लॉक काउंटर व हाल की अवरोधन के साथ।
|
|
81
82
|
- **निदान** — अवरुद्ध कनेक्शन `[network: …]` संदेश लेकर आते हैं जो अवरुद्ध लक्ष्य, निर्णय लेने वाला मोड या नियम, और समाधान बताता है।
|
|
83
|
+
- **settings पृष्ठ से अनुमति** — हाल की हर अवरोधन पर **अनुमति दें** क्रिया उपलब्ध है। यह उस कनेक्शन का वास्तव में निर्णय करने वाली निकटतम नियम फ़ाइल के **सिरे** (इंडेक्स 0) पर एक न्यूनतम `match: { network: { domains: [<host>] } }` / `action: allow` नियम लिखती है: जिस कार्यक्षेत्र को ब्लॉक सौंपा गया उसका प्रोजेक्ट फ़ाइल (कोई निरपेक्ष `rulesFile` कॉन्फ़िगर हो तो वही), और सत्र-रहित होस्ट-स्तरीय ब्लॉक के लिए वह फ़ाइल जिसे host शृंखला हल करती है (निरपेक्ष `rulesFile` → मौजूद `<processCwd>/<rulesFile>` → कॉन्फ़िगर किया `fallbackPath` → `<processCwd>/<rulesFile>`, न हो तो बनाई जाती है)। कई कार्यक्षेत्र लोड हों तो पृष्ठ पहले चयनकर्ता देता है, क्योंकि कोई भी लोडेड कार्यक्षेत्र शृंखला host शृंखला से ऊपर होती है। इंडेक्स 0 पर, क्योंकि नियम पहले-मिलान से चलते हैं: मौजूदा `deny` के बाद जोड़ा नियम मृत पाठ ही रहेगा। केवल `domains`, क्योंकि वह आयाम उपडोमेन सहित है और पोर्ट/scheme से स्वतंत्र है, इसलिए वही होस्ट अगले पोर्ट पर फिर अवरुद्ध नहीं होगा; मौजूदा टिप्पणियाँ और बिना छुए नियम बचे रहते हैं, और पाठ हस्त-संपादन जैसी ही मान्यता-द्वार से गुज़रता है। परिवर्तन तुरंत प्रभावी होता है — कैश किए गए कार्यक्षेत्र-चरण **और** सत्र-रहित host शृंखला दोनों दोबारा पढ़े जाते हैं, इसलिए न पुनःआरंभ और न `/rules reload` — और निर्णय फिर से गणना होता है, इसलिए सूचना **वास्तविक** परिणाम बताती है और कनेक्शन के अवरुद्ध रहते हुए कभी सफलता नहीं कहती। यह बिना फ़ाइल छुए अस्वीकार करती है: अज्ञात कार्यक्षेत्र, ज्ञात नियम-स्रोतों से बाहर का लक्ष्य, केवल-पठन अंतर्निहित आधाररेखा, और वह फ़ाइल जिसे वह पढ़ या पार्स नहीं कर सकती; पहले से अनुमत कनेक्शन पर कुछ नहीं लिखा जाता। `network.allowHostAction: false` बटन छिपाता है और RPC को अस्वीकार कराता है।
|
|
82
84
|
|
|
83
85
|
## Quick start
|
|
84
86
|
|
|
@@ -131,6 +133,8 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules'
|
|
|
131
133
|
| `network.loopback` | `allow` | लूपबैक लक्ष्य: `allow` (Codex समता) या `policy` |
|
|
132
134
|
| `network.injectEnv` | `true` | क्या उप-प्रक्रियाओं के लिए प्रॉक्सी पर्यावरण चर इंजेक्ट हों |
|
|
133
135
|
| `network.noProxy` | `clear` | उप-प्रक्रिया NO_PROXY व्यवहार: `clear` नीति लागू करता है या `preserve` |
|
|
136
|
+
| `network.upstreamProxy` | `off` | इस प्लगइन द्वारा अनुमत कनेक्शनों के लिए अपस्ट्रीम प्रॉक्सी: `off` सीधे कनेक्ट करता है, `inherit` लॉन्च वातावरण के प्रॉक्सी नाम दोबारा उपयोग करता है, या स्पष्ट `http(s)://` प्रॉक्सी URL |
|
|
137
|
+
| `network.allowHostAction` | `true` | settings पृष्ठ की प्रति-ब्लॉक "इस होस्ट को अनुमति दें" क्रिया: `false` अनुमति बटन छिपाता है और `permissionRules/allowHost` RPC को अस्वीकार कराता है (नियम संपादक अप्रभावित रहता है) |
|
|
134
138
|
| `builtin.enabled` | `true` | अंतर्निहित उच्च-जोखिम आधाररेखा: `false` साथ भेजे गए deny/ask नियम-समूह को पूरी तरह अक्षम करता है |
|
|
135
139
|
| `builtin.path` | *(साथ भेजा)* | प्रतिस्थापन आधाररेखा फ़ाइल (निरपेक्ष, या `process.cwd()` के सापेक्ष); माउंट पर मान्य |
|
|
136
140
|
|
|
@@ -143,7 +147,7 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules'
|
|
|
143
147
|
| `permissionRules/decision` | event | हर हिट और पास-थ्रू की केवल-लॉग ऑडिट |
|
|
144
148
|
| `permissionRules/network` | event | अवरुद्ध कनेक्शनों की प्रॉक्सी-परत ऑडिट |
|
|
145
149
|
| HTTP/CONNECT proxy | service | shell उप-प्रक्रिया ट्रैफ़िक को नियंत्रित करने वाला अंतर्निहित स्थानीय प्रॉक्सी |
|
|
146
|
-
| settings page | client | नेटवर्क-मोड संपादक, नियम संपादक, ब्लॉक काउंटर, हाल की अवरोधन |
|
|
150
|
+
| settings page | client | नेटवर्क-मोड संपादक, नियम संपादक, ब्लॉक काउंटर, हाल की अवरोधन — प्रति-ब्लॉक अनुमति क्रिया के साथ |
|
|
147
151
|
|
|
148
152
|
```
|
|
149
153
|
/rules list the active rules, their source files, and any last-reload error
|
|
@@ -176,6 +180,7 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules'
|
|
|
176
180
|
- **globs एक रूढ़िवादी उपसमुच्चय हैं।** कोई ब्रेस विस्तार नहीं — दो पैटर्न लिखें, या regex मोड उपयोग करें।
|
|
177
181
|
- **regex बैकट्रैकिंग गार्ड संरचनात्मक है, संपूर्ण नहीं।** अविश्वसनीय फ़ाइलों के लिए glob मोड पसंद करें।
|
|
178
182
|
- **होस्ट के अपने आउटबाउंड अनुरोध इस प्रॉक्सी से नहीं जाते।** इंजेक्ट किया गया प्रॉक्सी वातावरण **उत्पन्न shell उप-प्रक्रियाओं** को कवर करता है — इंजेक्शन इसी के लिए है — और उन उपभोक्ताओं को जो अनुरोध के समय प्रॉक्सी नाम पढ़ते हैं। यह होस्ट प्रोसेस के अपने `fetch`-आधारित ट्रैफ़िक को **नहीं** कवर करता: launcher पहले प्लगइन के माउंट होने से पहले **लॉन्च वातावरण** से undici का ग्लोबल dispatcher स्थापित कर देता है, वह dispatcher वातावरण से नहीं बल्कि अपनी नीति से रूट करता है, और Node प्रॉक्सी वातावरण को प्रारंभ पर सैंपल करता है — इसलिए बाद में माउंट होने वाला प्लगइन उसे नहीं मोड़ सकता। परिणाम: प्रदाता endpoints के लिए allow नियम की **आवश्यकता नहीं** है, और `network.injectEnv: false` होस्ट प्रोसेस को किसी चीज़ से बाहर नहीं करता। 2026-09-10 को Node 22 पर मापा गया; `scripts/host-egress-probe.mjs` से दोहराएँ।
|
|
183
|
+
- **श्रृंखलन केवल इस प्रॉक्सी तक पहुँचने वाले ट्रैफ़िक के बारे में है।** `dsh-v0.1.3-alpha.1` और उसके बाद के hosts पर, harness की स्वयं की लॉन्च-कालीन प्रॉक्सी नीति और उसकी उप-प्रक्रिया वातावरण परत इस प्लगइन द्वारा इंजेक्ट किए गए प्रॉक्सी नामों पर वरीयता ले सकती हैं, इसलिए `network.upstreamProxy` बताता है कि इस प्रॉक्सी द्वारा संभाले गए कनेक्शनों का क्या होता है — यह होस्ट प्रोसेस के हर आउटबाउंड पथ के बारे में दावा नहीं है।
|
|
179
184
|
|
|
180
185
|
## Collaborating with dsh-auto-review
|
|
181
186
|
|
package/README.md
CHANGED
|
@@ -78,8 +78,10 @@ A Codex-style **process-level network policy**: shell subprocess traffic flows t
|
|
|
78
78
|
- **`auto`** (default) — follows the sandbox preset; on hosts without the sandbox-policy service it resolves to `autoFallback` (`allow-all`).
|
|
79
79
|
|
|
80
80
|
- **Matching** — `match.network` with `domains` / `ips` / `ports` / `schemes` (globs, wildcards, CIDRs, port ranges; numeric YAML ports are accepted). URL-candidate extraction on the `tools/pre-execute` hot path fires on web-tool arguments and URLs embedded in bash/pwsh command text; loopback targets can short-circuit rules per `loopback` policy. IPv4-mapped IPv6 literals are normalized to their IPv4 form before matching, and the proxy connects on the addresses the decision was made on — never a second DNS resolution.
|
|
81
|
+
- **Upstream chaining** — `network.upstreamProxy` (default `off`) sends the connections this proxy **allows** through an upstream proxy: CONNECT asks the upstream for a tunnel (`CONNECT host:port`) and plain-HTTP requests are forwarded to it in absolute form. A blocked target never reaches the upstream — it still gets this plugin's structured 403. Two cases never chain even when an upstream is configured: a **loopback** target (a proxy outside this host cannot route its loopback) and any decision produced by an **`ips`-scoped rule** (chaining hands the hostname to the upstream, so "the connection lands on an address the rules saw" — issue #21 — would stop holding exactly where the rules cared about the address; those decisions keep dialing the adjudicated address directly), plus a target whose scheme has no usable upstream. The upstream's own hostname is operator configuration, not agent input, and is not adjudicated by these rules. A URL carrying credentials is never emitted raw: warnings, `/rules network` and the settings snapshot mask the password (`http://user:***@host:port`). An unreachable upstream, a timeout (10 s), or a non-2xx answer yields **502** — there is deliberately no silent fallback to a direct dial, so a misconfiguration stays visible.
|
|
81
82
|
- **Audit** — denied connections append `permissionRules/network` to the owning session (same adaptive `ignorable` gate), with block counters and recent interceptions in `/rules network` and the settings page.
|
|
82
83
|
- **Diagnosis** — blocked connections carry a `[network: …]` message naming the blocked target, the mode or rule that decided, and the remediation.
|
|
84
|
+
- **Allow from the settings page** — every recent interception offers an **Allow** action. It writes ONE minimal `match: { network: { domains: [<host>] } }` / `action: allow` rule at the **top** (index 0) of the nearest rule file that actually judges that connection: the project file of the workspace the block was attributed to (the absolute `rulesFile` when one is configured), or, for a session-less host-level block, the file the host chain resolves (absolute `rulesFile` → existing `<processCwd>/<rulesFile>` → configured `fallbackPath` → `<processCwd>/<rulesFile>`, created) — and when several workspaces are loaded, the page first offers a picker, because any loaded workspace chain outranks the host chain. Index 0 because rules are first-match-wins: appended after an existing `deny` the rule would be dead text. `domains` only, because that dimension is subdomain-inclusive and port/scheme-agnostic, so the host is not re-blocked on its next port; existing comments and untouched rules survive, and the text goes through the same validation gate as a hand edit. The change takes effect immediately — the cached workspace chains AND the session-less host chain are re-read, so no restart and no `/rules reload` — and the decision is recomputed, so the notice reports the REAL outcome and never claims success while the connection is still blocked. It refuses, leaving the file untouched, an unknown workspace, a target outside the known rule sources, the read-only built-in baseline, and a file it cannot read or parse; an already-allowed connection writes nothing. `network.allowHostAction: false` hides the button and makes the RPC refuse.
|
|
83
85
|
|
|
84
86
|
## Quick start
|
|
85
87
|
|
|
@@ -132,6 +134,8 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id
|
|
|
132
134
|
| `network.loopback` | `allow` | Loopback targets: `allow` (Codex parity) or `policy` |
|
|
133
135
|
| `network.injectEnv` | `true` | Whether proxy environment variables are injected for subprocesses |
|
|
134
136
|
| `network.noProxy` | `clear` | Subprocess NO_PROXY handling: `clear` enforces the policy or `preserve` |
|
|
137
|
+
| `network.upstreamProxy` | `off` | Upstream proxy for connections this plugin ALLOWS: `off` dials directly, `inherit` reuses the launch environment's proxy names, or an explicit `http(s)://` proxy URL |
|
|
138
|
+
| `network.allowHostAction` | `true` | Settings-page per-block "allow this host" action: `false` hides the Allow button and makes the `permissionRules/allowHost` RPC refuse (the rule editor is not affected) |
|
|
135
139
|
| `builtin.enabled` | `true` | Built-in high-risk baseline: `false` disables the shipped deny/ask ruleset entirely |
|
|
136
140
|
| `builtin.path` | *(shipped)* | Replacement baseline file (absolute, or relative to `process.cwd()`); validated at mount |
|
|
137
141
|
|
|
@@ -144,7 +148,7 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id
|
|
|
144
148
|
| `permissionRules/decision` | event | Log-only audit for every hit and passthrough |
|
|
145
149
|
| `permissionRules/network` | event | Proxy-layer audit for blocked connections |
|
|
146
150
|
| HTTP/CONNECT proxy | service | Built-in local proxy governing shell subprocess traffic |
|
|
147
|
-
| settings page | client | Network-mode editor, rule editor, block counters, recent interceptions |
|
|
151
|
+
| settings page | client | Network-mode editor, rule editor, block counters, recent interceptions with a per-block allow action |
|
|
148
152
|
|
|
149
153
|
```
|
|
150
154
|
/rules list the active rules, their source files, and any last-reload error
|
|
@@ -177,6 +181,7 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id
|
|
|
177
181
|
- **Globs are a conservative subset.** No brace expansion — write two patterns, or use regex mode.
|
|
178
182
|
- **The regex backtracking guard is structural, not exhaustive.** Prefer glob mode for untrusted files.
|
|
179
183
|
- **The host's own outbound requests do not go through this proxy.** The injected proxy environment covers **spawned shell children** — which is what the injection is for — and any consumer that reads the proxy names at request time. It does **not** cover the host process's own `fetch`-based traffic: the launcher installs undici's global dispatcher from the **launch environment** before the first plugin mounts, that dispatcher routes by its policy rather than by the environment, and Node samples the proxy environment at start — so a plugin mounting later cannot redirect it. Consequences: provider endpoints do **not** need an allow rule, and `network.injectEnv: false` does not take the host process out of anything. Measured 2026-09-10 on Node 22; reproduce with `scripts/host-egress-probe.mjs`.
|
|
184
|
+
- **Upstream chaining covers only what reaches this proxy.** On `dsh-v0.1.3-alpha.1` and later, the harness's own launch-time proxy policy and its subprocess environment overlay can take precedence over the proxy names this plugin injects, so `network.upstreamProxy` describes what happens to the connections this proxy handles — it is not a claim about every outbound path in the host process.
|
|
180
185
|
|
|
181
186
|
## Collaborating with dsh-auto-review
|
|
182
187
|
|
package/README.pt.md
CHANGED
|
@@ -77,8 +77,10 @@ Uma **política de rede em nível de processo** estilo Codex: o tráfego de subp
|
|
|
77
77
|
- **`auto`** (padrão) — segue o preset do sandbox; em hosts sem o serviço de política de sandbox resolve para `autoFallback` (`allow-all`).
|
|
78
78
|
|
|
79
79
|
- **Correspondência** — `match.network` com `domains` / `ips` / `ports` / `schemes` (globs, curingas, CIDR, faixas de porta; portas YAML numéricas são aceitas). A extração de candidatos URL na rota quente `tools/pre-execute` dispara sobre argumentos de ferramentas web e URLs embutidas em texto de comando bash/pwsh; destinos de loopback podem curto-circuitar regras conforme a política `loopback`. Literais IPv6 mapeados em IPv4 são normalizados para a forma IPv4 antes da comparação, e o proxy conecta nos endereços usados na decisão, sem uma segunda resolução DNS.
|
|
80
|
+
- **Encadeamento por proxy ascendente** — `network.upstreamProxy` (padrão `off`) envia as conexões que este proxy **permite** por um proxy ascendente: o CONNECT pede um túnel a ele (`CONNECT host:port`) e as requisições HTTP simples são encaminhadas em forma absoluta. Um destino bloqueado nunca chega ao proxy ascendente — ele continua recebendo o 403 estruturado deste plugin. Dois casos nunca encadeiam, mesmo com um proxy ascendente configurado: um destino de **loopback** (um proxy fora deste host não consegue rotear o loopback dele) e qualquer decisão produzida por uma **regra com escopo `ips`** (encadear entrega o nome do host ao proxy ascendente, então «a conexão chega a um endereço que as regras viram» — issue #21 — deixaria de valer exatamente onde as regras se importavam com o endereço; essas decisões continuam conectando direto no endereço adjudicado), além de um destino cujo esquema não tenha um proxy ascendente utilizável. O nome do host do próprio proxy ascendente é configuração do operador, não entrada do agente, e não é adjudicado por estas regras. Uma URL com credenciais nunca é emitida em claro: avisos, `/rules network` e o snapshot de settings mascaram a senha (`http://user:***@host:port`). Um proxy ascendente inalcançável, um timeout (10 s) ou uma resposta não 2xx resultam em **502** — não há, de propósito, queda silenciosa para conexão direta, para que uma má configuração continue visível.
|
|
80
81
|
- **Auditoria** — conexões negadas anexam `permissionRules/network` à sessão proprietária (a mesma porta adaptativa `ignorable`), com contadores de bloqueio e intercepções recentes em `/rules network` e na página de settings.
|
|
81
82
|
- **Diagnóstico** — conexões bloqueadas trazem uma mensagem `[network: …]` nomeando o destino bloqueado, o modo ou a regra que decidiu e a solução.
|
|
83
|
+
- **Permitir pela página de settings** — cada intercepção recente oferece uma ação **Permitir**. Ela grava UMA regra mínima `match: { network: { domains: [<host>] } }` / `action: allow` no **topo** (índice 0) do arquivo de regras mais próximo que realmente julga aquela conexão: o arquivo de projeto do workspace ao qual o bloqueio foi atribuído (o `rulesFile` absoluto quando há um configurado) ou, para um bloqueio de host sem sessão, o arquivo que a cadeia do host resolve (`rulesFile` absoluto → `<processCwd>/<rulesFile>` existente → `fallbackPath` configurado → `<processCwd>/<rulesFile>`, criado). Com vários workspaces carregados, a página oferece primeiro um seletor, porque qualquer cadeia de workspace carregada tem precedência sobre a cadeia do host. No índice 0 porque as regras são de primeira correspondência: acrescentada depois de um `deny` existente, a regra seria texto morto. Somente `domains`, porque essa dimensão inclui subdomínios e independe de porta/esquema, então o host não volta a ser bloqueado na próxima porta; comentários e regras não tocadas sobrevivem, e o texto passa pela mesma porta de validação de uma edição manual. A mudança entra em vigor imediatamente — as cadeias de workspace em cache E a cadeia de host sem sessão são relidas, sem reinício e sem `/rules reload` — e a decisão é recalculada, então o aviso informa o resultado REAL e nunca afirma sucesso enquanto a conexão continua bloqueada. Recusa, sem tocar no arquivo, um workspace desconhecido, um destino fora das fontes de regras conhecidas, a linha de base integrada somente leitura e um arquivo que não consegue ler ou analisar; uma conexão já permitida não grava nada. `network.allowHostAction: false` oculta o botão e faz o RPC recusar.
|
|
82
84
|
|
|
83
85
|
## Quick start
|
|
84
86
|
|
|
@@ -131,6 +133,8 @@ Todos os parâmetros são campos Schemastery `Config` (alteráveis pelo cordis.y
|
|
|
131
133
|
| `network.loopback` | `allow` | Destinos de loopback: `allow` (paridade Codex) ou `policy` |
|
|
132
134
|
| `network.injectEnv` | `true` | Se variáveis de ambiente do proxy são injetadas para subprocessos |
|
|
133
135
|
| `network.noProxy` | `clear` | Manejo de NO_PROXY em subprocessos: `clear` aplica a política ou `preserve` |
|
|
136
|
+
| `network.upstreamProxy` | `off` | Proxy ascendente para as conexões que este plugin PERMITE: `off` conecta direto, `inherit` reutiliza os nomes de proxy do ambiente de lançamento, ou uma URL de proxy `http(s)://` explícita |
|
|
137
|
+
| `network.allowHostAction` | `true` | Ação «permitir este host» por bloqueio da página de settings: `false` oculta o botão Permitir e faz o RPC `permissionRules/allowHost` recusar (o editor de regras não é afetado) |
|
|
134
138
|
| `builtin.enabled` | `true` | Linha de base de alto risco integrada: `false` desabilita por completo o conjunto deny/ask embarcado |
|
|
135
139
|
| `builtin.path` | *(embarcado)* | Arquivo de linha de base de substituição (absoluto, ou relativo a `process.cwd()`); validado ao montar |
|
|
136
140
|
|
|
@@ -143,7 +147,7 @@ Todos os parâmetros são campos Schemastery `Config` (alteráveis pelo cordis.y
|
|
|
143
147
|
| `permissionRules/decision` | event | Auditoria somente de log para cada acerto e passagem |
|
|
144
148
|
| `permissionRules/network` | event | Auditoria da camada de proxy para conexões bloqueadas |
|
|
145
149
|
| HTTP/CONNECT proxy | service | Proxy local integrado que governa o tráfego de subprocessos de shell |
|
|
146
|
-
| settings page | client | Editor de modo de rede, editor de regras, contadores de bloqueio, intercepções recentes |
|
|
150
|
+
| settings page | client | Editor de modo de rede, editor de regras, contadores de bloqueio, intercepções recentes com uma ação de permissão por bloqueio |
|
|
147
151
|
|
|
148
152
|
```
|
|
149
153
|
/rules list the active rules, their source files, and any last-reload error
|
|
@@ -176,6 +180,7 @@ Todos os parâmetros são campos Schemastery `Config` (alteráveis pelo cordis.y
|
|
|
176
180
|
- **Globs são um subconjunto conservador.** Sem expansão de chaves — escreva dois padrões, ou use o modo regex.
|
|
177
181
|
- **A guarda de backtracking de regex é estrutural, não exaustiva.** Prefira o modo glob para arquivos não confiáveis.
|
|
178
182
|
- **As requisições de saída do próprio host não passam por este proxy.** O ambiente de proxy injetado cobre os **subprocessos de shell derivados** — que é para isso que a injeção serve — e qualquer consumidor que leia os nomes de proxy no momento da requisição. **Não** cobre o tráfego baseado em `fetch` do próprio processo do host: o launcher instala o dispatcher global do undici a partir do **ambiente de lançamento** antes de o primeiro plugin ser montado, esse dispatcher roteia pela sua política e não pelo ambiente, e o Node amostra o ambiente de proxy na inicialização, então um plugin montado depois não consegue redirecioná-lo. Consequências: os endpoints do provedor **não** precisam de uma regra allow, e `network.injectEnv: false` não tira o processo do host de nada. Medido em 2026-09-10 no Node 22; reproduza com `scripts/host-egress-probe.mjs`.
|
|
183
|
+
- **O encadeamento cobre apenas o que chega a este proxy.** Em `dsh-v0.1.3-alpha.1` e posteriores, a própria política de proxy de lançamento do harness e sua camada de ambiente para subprocessos podem ter precedência sobre os nomes de proxy que este plugin injeta, então `network.upstreamProxy` descreve o que acontece com as conexões que este proxy trata — não é uma afirmação sobre todos os caminhos de saída do processo do host.
|
|
179
184
|
|
|
180
185
|
## Collaborating with dsh-auto-review
|
|
181
186
|
|
package/README.zh.md
CHANGED
|
@@ -77,8 +77,10 @@ Codex 风格的**进程级网络策略**:shell 子进程流量经内置本地
|
|
|
77
77
|
- **`auto`**(默认)—— 跟随沙箱预设;无沙箱策略服务的宿主上解析为 `autoFallback`(`allow-all`)。
|
|
78
78
|
|
|
79
79
|
- **匹配** —— `match.network` 用 `domains` / `ips` / `ports` / `schemes`(glob、通配符、CIDR、端口范围;数值型 YAML 端口可接受)。`tools/pre-execute` 热路径上的 URL 候选抽取作用于 web 工具参数与嵌入 bash/pwsh 命令文本的 URL;回环目标可按 `loopback` 策略短路规则。IPv4 映射的 IPv6 字面量在匹配前归一化为 IPv4 形式;代理按裁决到的地址建连,不做二次 DNS 解析。
|
|
80
|
+
- **上游链式** —— `network.upstreamProxy`(默认 `off`)把本代理**放行**的连接送上上游代理:CONNECT 向上游要一条隧道(`CONNECT host:port`),纯 HTTP 请求以绝对形式转发给它。被拦截的目标永远到不了上游——它照旧收到本插件的结构化 403。即使配置了上游,两类情况也绝不链式:**回环**目标(本机之外的代理无法路由它的回环),以及由 **`ips` 作用域规则**产生的任何裁决(链式会把主机名交给上游,"连接落在规则看过的地址上"(issue #21)恰恰会在规则真正在意地址的地方不再成立;这些裁决仍按裁决到的地址直连),再加上目标 scheme 没有可用上游的情况。上游自身的主机名是运维配置而非 agent 输入,不参与这些规则的裁决。带凭据的 URL 绝不原样输出:警告、`/rules network` 与设置页快照都会把口令打码(`http://user:***@host:port`)。上游不可达、超时(10 秒)或返回非 2xx 一律 **502**——刻意没有静默回退直连,配置错误因此始终可见。
|
|
80
81
|
- **审计** —— 被拒连接向所属会话追加 `permissionRules/network`(同样的自适应 `ignorable` 门),块计数器与近期拦截在 `/rules network` 与设置页展示。
|
|
81
82
|
- **诊断** —— 被拦截的连接带 `[network: …]` 消息,点名被拦目标、做出裁决的模式或规则,以及处置办法。
|
|
83
|
+
- **设置页放行** —— 每条近期拦截都提供**放行**动作:它在真正裁决该连接的最近一个规则文件的**最前面**(索引 0)写入一条最小的 `match: { network: { domains: [<主机>] } }` / `action: allow` 规则——被拦截连接所归属工作区的项目文件(配置了绝对 `rulesFile` 时即该文件);对无会话归属的宿主级拦截,则写宿主链解析出的文件(绝对 `rulesFile` → 已存在的 `<processCwd>/<rulesFile>` → 配置的 `fallbackPath` → `<processCwd>/<rulesFile>`,不存在则创建)。加载了多个工作区时,页面先提供工作区选择器——任何已加载的工作区链都优先于宿主链。之所以放在索引 0:规则首个匹配生效,追加在既有 `deny` 之后只会成为死文本。只写 `domains`:该维度包含子域且与端口/scheme 无关,因此同一主机在下一个端口上不会被重新拦截;既有注释与未改动规则原样保留,文本与手工编辑走同一道校验门。写入即刻生效——缓存的工作区链**与**无会话宿主链都会重读,无需重启、无需 `/rules reload`——随后重新计算裁决,因此提示报告的是**真实**结果,绝不在连接仍被拦截时声称成功。以下情况一律拒绝且磁盘文件分毫不动:未知工作区、不在已知规则源内的目标、只读的内置基线、无法读取或解析的文件;连接已被放行时不写入任何内容。`network.allowHostAction: false` 隐藏按钮并让 RPC 拒绝。
|
|
82
84
|
|
|
83
85
|
## Quick start
|
|
84
86
|
|
|
@@ -131,6 +133,8 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules'
|
|
|
131
133
|
| `network.loopback` | `allow` | 回环目标:`allow`(Codex 对齐)或 `policy` |
|
|
132
134
|
| `network.injectEnv` | `true` | 是否为子进程注入代理环境变量 |
|
|
133
135
|
| `network.noProxy` | `clear` | 子进程 NO_PROXY 处理:`clear` 强制策略或 `preserve` |
|
|
136
|
+
| `network.upstreamProxy` | `off` | 本插件**放行**连接的上游代理:`off` 直连、`inherit` 沿用启动环境的代理名,或显式 `http(s)://` 代理 URL |
|
|
137
|
+
| `network.allowHostAction` | `true` | 设置页逐条「放行」动作:`false` 隐藏「放行」按钮并让 `permissionRules/allowHost` RPC 拒绝(规则编辑器不受影响) |
|
|
134
138
|
| `builtin.enabled` | `true` | 内置高危基线:`false` 完全禁用随附的 deny/ask 规则集 |
|
|
135
139
|
| `builtin.path` | *(随附)* | 替换基线文件(绝对路径,或相对 `process.cwd()`);挂载时校验 |
|
|
136
140
|
|
|
@@ -143,7 +147,7 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules'
|
|
|
143
147
|
| `permissionRules/decision` | event | 每次命中与透传的仅日志审计 |
|
|
144
148
|
| `permissionRules/network` | event | 被拒连接的代理层审计 |
|
|
145
149
|
| HTTP/CONNECT proxy | service | 治理 shell 子进程流量的内置本地代理 |
|
|
146
|
-
| settings page | client |
|
|
150
|
+
| settings page | client | 网络模式编辑器、规则编辑器、块计数器、近期拦截(每条可一键放行) |
|
|
147
151
|
|
|
148
152
|
```
|
|
149
153
|
/rules list the active rules, their source files, and any last-reload error
|
|
@@ -176,6 +180,7 @@ dsh --profile web --dump-config | grep -A4 'id: permission-rules'
|
|
|
176
180
|
- **glob 是保守子集。** 无花括号展开——写两个模式,或用正则模式。
|
|
177
181
|
- **正则回溯守卫是结构性的、非穷尽的。** 对不可信文件优先用 glob 模式。
|
|
178
182
|
- **宿主自身的出站请求不经过本代理。** 注入的代理环境覆盖**派生的 shell 子进程**(这正是注入的用途),以及在请求时读取代理变量名的消费者;它**不覆盖**宿主进程自身基于 `fetch` 的流量——launcher 在第一个插件挂载之前就用**启动环境**装好了 undici 的全局 dispatcher,该 dispatcher 按策略而非按环境路由,且 Node 在启动时采样代理环境,因此后挂载的插件无法改变它。结论:供应商端点**不需要**允许规则,`network.injectEnv: false` 也不会把宿主进程移出任何东西。2026-09-10 实测于 Node 22;用 `scripts/host-egress-probe.mjs` 可复现。
|
|
183
|
+
- **链式只覆盖到达本代理的流量。** 在 `dsh-v0.1.3-alpha.1` 及之后的宿主上,harness 自身的启动期代理策略与其子进程环境覆盖可能优先于本插件注入的代理名,因此 `network.upstreamProxy` 描述的是本代理所处理连接的去向——它不是关于宿主进程每一条出站路径的断言。
|
|
179
184
|
|
|
180
185
|
## Collaborating with dsh-auto-review
|
|
181
186
|
|
package/docs/rules-format.en.md
CHANGED
|
@@ -41,7 +41,7 @@ A rule whose `match` carries a `network` block is a **network rule**. It matches
|
|
|
41
41
|
| Field | Semantics |
|
|
42
42
|
|---|---|
|
|
43
43
|
| `domains` | Domain patterns, lowercased with a trailing dot stripped. A pattern without wildcards is subdomain-inclusive — `example.com` also matches `api.example.com`; `*.example.com` matches subdomains only; `*`/`**` compile as ordinary globs. ANY pattern matching the target host satisfies the dimension. |
|
|
44
|
-
| `ips` | Exact IPv4/IPv6 literal, a glob (`10.0.*.*`), or an IPv4 CIDR (`10.0.0.0/8`). A literal IP in the URL is always a candidate; the proxy additionally resolves hostnames and tests their addresses, then **connects on the adjudicated address** (no second resolution, so DNS rebinding cannot move the connection to an address the rules never approved; a multi-address host falls back in adjudicated order), while `tools/pre-execute` matches literal IPs only (no DNS in the hot path). An IPv4-mapped IPv6 literal (`::ffff:169.254.169.254`, `::ffff:a9fe:a9fe`, `0:0:0:0:0:ffff:a9fe:a9fe`) normalizes to its dotted IPv4 form for matching, and a rule written in the mapped form also covers the plain IPv4 spelling. ANY match satisfies the dimension. |
|
|
44
|
+
| `ips` | Exact IPv4/IPv6 literal, a glob (`10.0.*.*`), or an IPv4 CIDR (`10.0.0.0/8`). A literal IP in the URL is always a candidate; the proxy additionally resolves hostnames and tests their addresses, then **connects on the adjudicated address** (no second resolution, so DNS rebinding cannot move the connection to an address the rules never approved; a multi-address host falls back in adjudicated order), while `tools/pre-execute` matches literal IPs only (no DNS in the hot path). An IPv4-mapped IPv6 literal (`::ffff:169.254.169.254`, `::ffff:a9fe:a9fe`, `0:0:0:0:0:ffff:a9fe:a9fe`) normalizes to its dotted IPv4 form for matching, and a rule written in the mapped form also covers the plain IPv4 spelling. With `network.upstreamProxy` configured, a chained request has its DNS resolved by the upstream, so an `ips`-scoped decision deliberately does not chain and keeps dialing the adjudicated address. ANY match satisfies the dimension. |
|
|
45
45
|
| `ports` | `*`, one port (`443`), or an inclusive range (`8000-9000`); numeric YAML ports are accepted. Evaluated against the effective port (URL port, else 80/443 by scheme). ANY match satisfies the dimension. |
|
|
46
46
|
| `schemes` | `http` and/or `https`; the target scheme must be one of them. |
|
|
47
47
|
|
|
@@ -172,3 +172,4 @@ This plugin only produces `ask` decisions; the `ask` goes to the official `ctx.a
|
|
|
172
172
|
- `/rules` lists the active rules (with `list` as an explicit alias); in multi-file chains every rule line is attributed to its own source file.
|
|
173
173
|
- `/rules test` accepts leading flags: `--cwd <dir>` evaluates against another workspace (rule discovery AND path normalization), `--env KEY=VALUE` (repeatable) overrides host env for `when.env`, `--agent <selector>` (repeatable) supplies identity candidates for the `agents` dimension, and `--platform <name>` overrides the host platform for `when.platform`.
|
|
174
174
|
- `enforce: false` puts the plugin in dry-run mode: deny/ask hits are audit-logged with a `dryRun` marker (keeping the would-be action and the real downstream outcome) and every call passes through — use it to trial a new policy in production before enforcing it. `/rules` prints a dry-run notice while the mode is active.
|
|
175
|
+
- The settings page's **Allow** action inserts a `match.network.domains` allow rule at the HEAD of the nearest rule file that actually judges the connection (rules are first-match-wins, so one appended behind an existing matching `deny` would be dead text), and the write re-reads the rule chains immediately — including the session-less host chain — so no restart is needed.
|
package/docs/rules-format.md
CHANGED
|
@@ -41,7 +41,7 @@ rules: # 规则列表,按书写顺序求值;可以省略或为空
|
|
|
41
41
|
| 字段 | 语义 |
|
|
42
42
|
|---|---|
|
|
43
43
|
| `domains` | 域名模式,小写化并去掉末尾点。无通配符的模式包含子域——`example.com` 同时命中 `api.example.com`;`*.example.com` 只匹配子域;`*`/`**` 按普通 glob 编译。任一模式命中目标主机即满足该维度。 |
|
|
44
|
-
| `ips` | 精确 IPv4/IPv6 字面量、glob(`10.0.*.*`)或 IPv4 CIDR(`10.0.0.0/8`)。URL 中的字面量 IP 始终是候选;代理额外解析主机名并测试其地址,随后**按裁决到的地址建连**(不做二次解析,DNS rebinding 无法把连接改到未获批的地址;多地址主机按裁决顺序回退),而 `tools/pre-execute` 只匹配字面量 IP(热路径不做 DNS)。IPv4 映射的 IPv6 字面量(`::ffff:169.254.169.254`、`::ffff:a9fe:a9fe`、`0:0:0:0:0:ffff:a9fe:a9fe`)归一化为其 IPv4 点分形式参与匹配,规则写成映射形式时同样覆盖普通 IPv4
|
|
44
|
+
| `ips` | 精确 IPv4/IPv6 字面量、glob(`10.0.*.*`)或 IPv4 CIDR(`10.0.0.0/8`)。URL 中的字面量 IP 始终是候选;代理额外解析主机名并测试其地址,随后**按裁决到的地址建连**(不做二次解析,DNS rebinding 无法把连接改到未获批的地址;多地址主机按裁决顺序回退),而 `tools/pre-execute` 只匹配字面量 IP(热路径不做 DNS)。IPv4 映射的 IPv6 字面量(`::ffff:169.254.169.254`、`::ffff:a9fe:a9fe`、`0:0:0:0:0:ffff:a9fe:a9fe`)归一化为其 IPv4 点分形式参与匹配,规则写成映射形式时同样覆盖普通 IPv4 写法。配置 `network.upstreamProxy` 后,链式请求的 DNS 由上游解析,因此 `ips` 作用域的裁决有意不链式,仍按裁决到的地址建连。任一命中即满足。 |
|
|
45
45
|
| `ports` | `*`、单端口(`443`)或闭区间(`8000-9000`);接受数值型 YAML 端口。按有效端口求值(URL 端口,否则按 scheme 取 80/443)。任一命中即满足。 |
|
|
46
46
|
| `schemes` | `http` 和/或 `https`;目标 scheme 必须是其中之一。 |
|
|
47
47
|
|
|
@@ -170,3 +170,4 @@ rules:
|
|
|
170
170
|
- `/rules` 列出当前规则(`list` 为显式别名);多文件链中每条规则行标注自己的来源文件。
|
|
171
171
|
- `/rules test` 支持前置标志:`--cwd <目录>` 换一个工作区求值(规则发现与路径归一化都切换);`--env 键=值`(可重复)覆盖宿主环境变量以测试 `when.env`;`--agent <选择器>`(可重复)为 `agents` 维度提供身份候选;`--platform <平台名>` 覆盖宿主平台以测试 `when.platform`。
|
|
172
172
|
- `enforce: false` 让插件进入干跑模式:deny/ask 命中只写审计(带 `dryRun` 标记,记录「本会做什么」与下游真实结果),所有调用照常透传——用于在生产环境先试跑新策略再强制执行。激活期间 `/rules` 会打印干跑提示。
|
|
173
|
+
- 设置页的**放行**动作会在真正裁决该连接的最近一个规则文件的**最前面**插入一条 `match.network.domains` 放行规则(规则首个匹配生效,追加在既有匹配 `deny` 之后只会成为死文本),写入后立即重读规则链——含无会话宿主链——无需重启。
|