dsh-permission-rules 0.6.15 → 0.6.17
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 +21 -0
- package/README.es.md +1 -1
- package/README.hi.md +1 -1
- package/README.md +1 -1
- package/README.pt.md +1 -1
- package/README.zh.md +1 -1
- package/docs/rules-format.en.md +1 -1
- package/docs/rules-format.md +1 -1
- package/docs/rules-format.schema.json +1 -1
- package/lib/index.js +149 -50
- package/lib/types/proxy.d.ts +7 -2
- package/lib/types/proxy.d.ts.map +1 -1
- package/lib/types/rules.d.ts.map +1 -1
- package/package.json +20 -20
- package/scripts/repair-session-logs.mjs +5 -2
- package/src/proxy.ts +104 -41
- package/src/rules.ts +47 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## v0.6.17 - 2026-09-10
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
|
|
5
|
+
- Pin the `@deepseek-ai/dsh-*` dev/test dependencies to the published `0.1.5-rc.1` line and record `0.1.5-rc.1` in `dshWorkshop.compatibility.dshVersions`; the monthly Compat workflow now runs against `0.1.5-rc.1`. The peer range `>=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0` is unchanged, so no supported host line is dropped.
|
|
6
|
+
- Vendor integration tarball updated to `dsh-auto-review` 0.12.2 (`vendor/dsh-auto-review-0.12.2.tgz`, packed from the sibling repo at tag `v0.12.2`; the packed `files` are byte-identical to that tag's checkout), with the devDependency `file:` specifier and `pnpm-lock.yaml` refreshed. The sibling's runtime `@deepseek-ai/dsh-*` pins now follow the `0.1.5-rc.1` host line, matching this repo's dev peers. The six historical vendor tarballs (`0.9.0`–`0.12.1`) stay in the tree.
|
|
7
|
+
|
|
8
|
+
### Docs
|
|
9
|
+
|
|
10
|
+
- Refresh the five-language README compatibility baseline to `dsh-v0.1.5-rc.1` (verified 2026-09-10).
|
|
11
|
+
|
|
12
|
+
## v0.6.16 - 2026-09-09
|
|
13
|
+
|
|
14
|
+
### Security
|
|
15
|
+
|
|
16
|
+
- **Fixed: an IPv4-mapped IPv6 literal bypassed `ips` rules.** `isIpLiteral()` classifies any host containing `:` as an IPv6 literal, while `ipMatches()` compared literal patterns by exact string equality and CIDR patterns with an IPv4 dotted-quad regex. A target spelled `::ffff:169.254.169.254` (or the hex form `::ffff:a9fe:a9fe`, or the full form `0:0:0:0:0:ffff:a9fe:a9fe`) therefore matched neither an IPv4 literal nor an IPv4 CIDR rule, even though Node routes the connection to that IPv4 destination — a sandboxed caller could reach an address the rules intended to block (for example a cloud metadata endpoint). Both sides now normalize through `unmapIpv4`: candidates and patterns are mapped back to their dotted IPv4 form before matching, a mapped pattern also covers the plain IPv4 spelling, and mapped globs keep their IPv4 remainder (`::ffff:10.0.*.*` → `10.0.*.*`). Reported by the maintainer during a network-rule audit; the shipped `docs/rules-format.md` / `.en.md` `ips` row documents the normalization.
|
|
17
|
+
|
|
18
|
+
### Tests
|
|
19
|
+
|
|
20
|
+
- `test/network.spec.ts` gains a regression case: mapped dotted / mapped hex / full-form mapped literals against both literal and CIDR `ips` rules, the normalized `parseUrlTarget` candidate, the mapped-pattern direction, and an unrelated-IPv6 negative.
|
|
21
|
+
|
|
1
22
|
## v0.6.15 - 2026-09-09
|
|
2
23
|
|
|
3
24
|
### Changed
|
package/README.es.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
| Surface | Status |
|
|
28
28
|
|---|---|
|
|
29
|
-
| Harness | DeepSeek Harness `dsh-v0.1.5-
|
|
29
|
+
| Harness | DeepSeek Harness `dsh-v0.1.5-rc.1` (adaptado el 2026-09-09, cadena completa de gates + smoke de instalación de profile): su `Session.append` todavía no puede estampar el marcador `ignorable` —verificado en el paquete publicado `0.1.5-rc.1`, donde el tercer argumento se descarta silenciosamente y el campo del sobre solo sobrevive para la lectura de logs almacenados—, así que toda la línea `0.1.5-alpha` se preverifica como no marcada y la auditoría del registro de sesión queda desactivada por defecto. La línea `0.1.3-alpha` mantiene la misma firma de append surface-only. Las migraciones de log de ambas líneas rechazan eventos de plugin sin clasificar incluso marcados: aplica `strip` a las filas de auditoría v1 antes de que un host `0.1.3-alpha` abra el log y a las v2 antes de que un host `0.1.5-alpha` lo migre (los logs v3 nativos solo necesitan `repair`). |
|
|
30
30
|
| Node | `^22.19.0 || >=24.0.0` |
|
|
31
31
|
| Platforms | Todas (host + cliente web de settings) |
|
|
32
32
|
| Model | Cualquiera (las razones deny/ask se muestran a través de los resultados de herramienta) |
|
package/README.hi.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
| Surface | Status |
|
|
28
28
|
|---|---|
|
|
29
|
-
| Harness | DeepSeek Harness `dsh-v0.1.5-
|
|
29
|
+
| Harness | DeepSeek Harness `dsh-v0.1.5-rc.1` (2026-09-09 को अनुकूलित, पूर्ण gates शृंखला + profile इंस्टॉल smoke): इसका `Session.append` अभी भी `ignorable` मार्कर स्टैम्प नहीं कर सकता — प्रकाशित `0.1.5-rc.1` पैकेज पर सत्यापित, जहाँ तीसरा आर्ग्युमेंट चुपचाप छोड़ दिया जाता है और लिफ़ाफ़ा फ़ील्ड केवल संग्रहीत-लॉग पठन के लिए बचता है — इसलिए पूरी `0.1.5-alpha` पंक्ति पहले append से पहले ही अनमार्क्ड के रूप में पूर्व-जाँची जाती है और सत्र-लॉग ऑडिट डिफ़ॉल्ट रूप से बंद रहता है। `0.1.3-alpha` पंक्ति वही surface-only append हस्ताक्षर रखती है। दोनों पंक्तियों का लॉग प्रवासन चिह्नित अन-क्लासिफ़ाइड प्लगइन घटनाओं को भी अस्वीकारता है: `0.1.3-alpha` host के लॉग खोलने से पहले v1 ऑडिट पंक्तियाँ और `0.1.5-alpha` host के प्रवासन से पहले v2 पंक्तियाँ `strip` करें (v3 मूल लॉग के लिए केवल `repair` चाहिए)। |
|
|
30
30
|
| Node | `^22.19.0 || >=24.0.0` |
|
|
31
31
|
| Platforms | सभी (host + वेब settings क्लाइंट) |
|
|
32
32
|
| Model | कोई भी (deny/ask कारण टूल परिणामों के माध्यम से दिखते हैं) |
|
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
| Surface | Status |
|
|
29
29
|
|---|---|
|
|
30
|
-
| Harness | DeepSeek Harness `dsh-v0.1.5-
|
|
30
|
+
| Harness | DeepSeek Harness `dsh-v0.1.5-rc.1` (adapted 2026-09-09, full gate chain + profile install smoke): its `Session.append` still cannot stamp the `ignorable` marker — verified on the published `0.1.5-rc.1` package, where the third argument is silently dropped and the envelope field survives for stored-log reads only — so the whole `0.1.5-alpha` line is pre-checked as unmarked and session-log audit stays disabled by default. The `0.1.3-alpha` line keeps the same surface-only append signature. Both lines' log migrations refuse unclassified plugin events even when marked, so `strip` v1 audit rows before a `0.1.3-alpha` host opens the log and v2 audit rows before a `0.1.5-alpha` host migrates it (native v3 logs only need `repair`). |
|
|
31
31
|
| Node | `^22.19.0 || >=24.0.0` |
|
|
32
32
|
| Platforms | All (host + web settings client) |
|
|
33
33
|
| Model | Any (deny/ask reasons surface through tool results) |
|
package/README.pt.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
| Surface | Status |
|
|
28
28
|
|---|---|
|
|
29
|
-
| Harness | DeepSeek Harness `dsh-v0.1.5-
|
|
29
|
+
| Harness | DeepSeek Harness `dsh-v0.1.5-rc.1` (adaptado em 2026-09-09, cadeia completa de gates + smoke de instalação de profile): o `Session.append` ainda não consegue estampar o marcador `ignorable` — verificado no pacote publicado `0.1.5-rc.1`, onde o terceiro argumento é descartado silenciosamente e o campo do envelope só sobrevive para a leitura de logs armazenados —, então toda a linha `0.1.5-alpha` é pré-verificada como não marcada e a auditoria do log de sessão fica desativada por padrão. A linha `0.1.3-alpha` mantém a mesma assinatura de append surface-only. As migrações de log de ambas as linhas recusam eventos de plugin não classificados mesmo marcados: aplique `strip` nas linhas de auditoria v1 antes de um host `0.1.3-alpha` abrir o log e nas v2 antes de um host `0.1.5-alpha` migrá-lo (logs v3 nativos só precisam de `repair`). |
|
|
30
30
|
| Node | `^22.19.0 || >=24.0.0` |
|
|
31
31
|
| Platforms | Todas (host + cliente web de settings) |
|
|
32
32
|
| Model | Qualquer (razões deny/ask aparecem pelos resultados de ferramenta) |
|
package/README.zh.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
| Surface | Status |
|
|
28
28
|
|---|---|
|
|
29
|
-
| Harness | DeepSeek Harness `dsh-v0.1.5-
|
|
29
|
+
| Harness | DeepSeek Harness `dsh-v0.1.5-rc.1`(2026-09-09 已适配,完整门禁链 + profile 安装冒烟):其 `Session.append` 仍无法盖章 `ignorable` 标记——已在已发布的 `0.1.5-rc.1` 包上实测,第三参数被静默丢弃,信封字段仅用于存量日志读取——故整条 `0.1.5-alpha` 线在首次追加前即被预检为未盖章,会话日志审计默认停用。`0.1.3-alpha` 线同样沿用 surface-only 追加签名。两条线的日志迁移连已标记的未分类插件事件也拒绝:在 `0.1.3-alpha` 宿主打开日志前 `strip` 掉 v1 审计行,在 `0.1.5-alpha` 宿主迁移前 `strip` 掉 v2 审计行(v3 原生日志只需 `repair`)。 |
|
|
30
30
|
| Node | `^22.19.0 || >=24.0.0` |
|
|
31
31
|
| Platforms | 全部(host + Web 设置客户端) |
|
|
32
32
|
| Model | 任意(deny/ask 原因经工具结果呈现) |
|
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, while `tools/pre-execute` matches literal IPs only (no DNS in the hot path). 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. 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
|
|
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
|
|
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 写法。任一命中即满足。 |
|
|
45
45
|
| `ports` | `*`、单端口(`443`)或闭区间(`8000-9000`);接受数值型 YAML 端口。按有效端口求值(URL 端口,否则按 scheme 取 80/443)。任一命中即满足。 |
|
|
46
46
|
| `schemes` | `http` 和/或 `https`;目标 scheme 必须是其中之一。 |
|
|
47
47
|
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"items": { "type": "string", "minLength": 1 }
|
|
150
150
|
},
|
|
151
151
|
"ips": {
|
|
152
|
-
"description": "IP literals, globs, or IPv4 CIDRs; ANY matching the target address satisfies the dimension.",
|
|
152
|
+
"description": "IP literals, globs, or IPv4 CIDRs; an IPv4-mapped IPv6 literal (::ffff:169.254.169.254, ::ffff:a9fe:a9fe) is unmapped to its dotted IPv4 form before matching, and a mapped pattern also covers the plain IPv4 spelling; ANY matching the target address satisfies the dimension.",
|
|
153
153
|
"type": "array",
|
|
154
154
|
"items": { "type": "string", "minLength": 1 }
|
|
155
155
|
},
|
package/lib/index.js
CHANGED
|
@@ -1210,9 +1210,30 @@ function normalizeHost(host) {
|
|
|
1210
1210
|
while (out.endsWith(".")) out = out.slice(0, -1);
|
|
1211
1211
|
return out;
|
|
1212
1212
|
}
|
|
1213
|
-
/**
|
|
1213
|
+
/**
|
|
1214
|
+
* Map an IPv4-mapped IPv6 literal (dotted or hex form) back to its dotted IPv4
|
|
1215
|
+
* quad. Node routes `::ffff:169.254.169.254` and `::ffff:a9fe:a9fe` to the same
|
|
1216
|
+
* IPv4 destination as `169.254.169.254`, so both spellings must compare as that
|
|
1217
|
+
* address: without this normalization an `ips` rule written in IPv4 terms never
|
|
1218
|
+
* matches the mapped spelling and the deny is bypassed.
|
|
1219
|
+
* @param host - lowercased, unbracketed host or pattern.
|
|
1220
|
+
* @returns the IPv4 dotted quad when the input is IPv4-mapped, else the input.
|
|
1221
|
+
*/
|
|
1222
|
+
function unmapIpv4(host) {
|
|
1223
|
+
const lower = host.toLowerCase();
|
|
1224
|
+
const dotted = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(lower);
|
|
1225
|
+
if (dotted !== null && dotted[1] !== void 0) return dotted[1];
|
|
1226
|
+
const hex = /^(?:(?:0{0,4}:){5}|::)ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(lower);
|
|
1227
|
+
if (hex !== null && hex[1] !== void 0 && hex[2] !== void 0) {
|
|
1228
|
+
const high = Number.parseInt(hex[1], 16);
|
|
1229
|
+
const low = Number.parseInt(hex[2], 16);
|
|
1230
|
+
return `${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`;
|
|
1231
|
+
}
|
|
1232
|
+
return lower;
|
|
1233
|
+
}
|
|
1234
|
+
/** The literal IP of a host, or `[]` when the host is a name. Mapped literals normalize to IPv4. */
|
|
1214
1235
|
function literalIpOf(host) {
|
|
1215
|
-
return isIpLiteral(host) ? [host.toLowerCase()] : [];
|
|
1236
|
+
return isIpLiteral(host) ? [unmapIpv4(host.toLowerCase())] : [];
|
|
1216
1237
|
}
|
|
1217
1238
|
/** Whether a host string is an IP literal (IPv4 or IPv6). */
|
|
1218
1239
|
function isIpLiteral(host) {
|
|
@@ -1260,9 +1281,25 @@ function compilePortPattern(pattern) {
|
|
|
1260
1281
|
max: Number(range?.[2] ?? 0)
|
|
1261
1282
|
};
|
|
1262
1283
|
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Normalize an IP pattern's spelling: a mapped literal becomes its IPv4 quad,
|
|
1286
|
+
* and a mapped glob keeps its IPv4 remainder (`::ffff:10.0.*.*` → `10.0.*.*`),
|
|
1287
|
+
* so IPv4 patterns cover mapped spellings too.
|
|
1288
|
+
* @param pattern - the raw pattern.
|
|
1289
|
+
* @returns the normalized, lowercased pattern.
|
|
1290
|
+
*/
|
|
1291
|
+
function unmapIpv4Pattern(pattern) {
|
|
1292
|
+
const lower = pattern.toLowerCase();
|
|
1293
|
+
const mapped = unmapIpv4(lower);
|
|
1294
|
+
if (mapped !== lower) return mapped;
|
|
1295
|
+
const dottedPrefix = /^::ffff:(.+)$/.exec(lower);
|
|
1296
|
+
if (dottedPrefix !== null && dottedPrefix[1] !== void 0) return dottedPrefix[1];
|
|
1297
|
+
return lower;
|
|
1298
|
+
}
|
|
1263
1299
|
/** Compile one IP pattern: CIDR first, then glob; exact literals glob-compile to themselves. */
|
|
1264
1300
|
function compileIpPattern(pattern, maxGlobStars) {
|
|
1265
|
-
const
|
|
1301
|
+
const normalized = unmapIpv4Pattern(pattern);
|
|
1302
|
+
const cidr = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/.exec(normalized);
|
|
1266
1303
|
if (cidr !== null) {
|
|
1267
1304
|
const octets = cidr.slice(1, 5).map(Number);
|
|
1268
1305
|
const prefix = Number(cidr[5]);
|
|
@@ -1271,8 +1308,8 @@ function compileIpPattern(pattern, maxGlobStars) {
|
|
|
1271
1308
|
prefix
|
|
1272
1309
|
} };
|
|
1273
1310
|
}
|
|
1274
|
-
if (!/[[*?]/.test(
|
|
1275
|
-
return { regex: compileGlob(
|
|
1311
|
+
if (!/[[*?]/.test(normalized) && !normalized.includes(":")) return { literal: normalized };
|
|
1312
|
+
return { regex: compileGlob(normalized, {
|
|
1276
1313
|
segments: false,
|
|
1277
1314
|
maxStars: maxGlobStars,
|
|
1278
1315
|
caseInsensitive: true
|
|
@@ -1345,9 +1382,9 @@ function targetMatchesNetwork(target, network) {
|
|
|
1345
1382
|
}
|
|
1346
1383
|
return true;
|
|
1347
1384
|
}
|
|
1348
|
-
/** Whether one compiled IP pattern matches one candidate address (lowercased, unbracketed). */
|
|
1385
|
+
/** Whether one compiled IP pattern matches one candidate address (lowercased, unbracketed, mapped IPv4 unmapped). */
|
|
1349
1386
|
function ipMatches(pattern, candidate) {
|
|
1350
|
-
const normalized = normalizeHost(candidate);
|
|
1387
|
+
const normalized = unmapIpv4(normalizeHost(candidate));
|
|
1351
1388
|
if (pattern.cidr !== void 0) {
|
|
1352
1389
|
const parts = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(normalized);
|
|
1353
1390
|
if (parts === null) return false;
|
|
@@ -2118,23 +2155,59 @@ var NetworkProxy = class {
|
|
|
2118
2155
|
res.end("permission-rules proxy: only absolute-form proxy requests are served here\n");
|
|
2119
2156
|
return;
|
|
2120
2157
|
}
|
|
2121
|
-
await this.
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
proxyRes.pipe(res);
|
|
2129
|
-
});
|
|
2130
|
-
proxyReq.on("error", (error) => {
|
|
2131
|
-
if (!res.headersSent) {
|
|
2132
|
-
res.writeHead(502, { "content-type": "text/plain" });
|
|
2133
|
-
res.end(`[network: upstream error] ${String(error)}\n`);
|
|
2134
|
-
} else res.destroy();
|
|
2158
|
+
const { decision, target: adjudicated } = await this.decideWithResolution(target);
|
|
2159
|
+
if (decision.action !== "allow") {
|
|
2160
|
+
this.recordBlock(decision, target);
|
|
2161
|
+
const body = blockMessage(decision);
|
|
2162
|
+
res.writeHead(403, {
|
|
2163
|
+
"content-type": "text/plain; charset=utf-8",
|
|
2164
|
+
"content-length": Buffer.byteLength(body)
|
|
2135
2165
|
});
|
|
2136
|
-
|
|
2166
|
+
res.end(body);
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
const upstream = new URL(req.url);
|
|
2170
|
+
const send = upstream.protocol === "https:" ? request$1 : request;
|
|
2171
|
+
const pinned = pinnedAddresses(adjudicated);
|
|
2172
|
+
const proxyReq = send(upstream, {
|
|
2173
|
+
method: req.method,
|
|
2174
|
+
headers: req.headers,
|
|
2175
|
+
...pinned.length === 0 ? {} : { lookup: pinnedLookup(pinned) }
|
|
2176
|
+
}, (proxyRes) => {
|
|
2177
|
+
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
|
|
2178
|
+
proxyRes.pipe(res);
|
|
2137
2179
|
});
|
|
2180
|
+
proxyReq.on("error", (error) => {
|
|
2181
|
+
if (!res.headersSent) {
|
|
2182
|
+
res.writeHead(502, { "content-type": "text/plain" });
|
|
2183
|
+
res.end(`[network: upstream error] ${String(error)}\n`);
|
|
2184
|
+
} else res.destroy();
|
|
2185
|
+
});
|
|
2186
|
+
req.pipe(proxyReq);
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Connect to the first adjudicated address that accepts, so the tunnel lands
|
|
2190
|
+
* on an address the decision approved; a hostname target with no resolution
|
|
2191
|
+
* keeps the historical hostname connect. Each candidate is tried in order so
|
|
2192
|
+
* a multi-address host does not lose its fallback.
|
|
2193
|
+
*/
|
|
2194
|
+
async connectUpstream(target, port) {
|
|
2195
|
+
const pinned = pinnedAddresses(target);
|
|
2196
|
+
if (pinned.length === 0) return connect(port, target.host);
|
|
2197
|
+
let lastError;
|
|
2198
|
+
for (const address of pinned) try {
|
|
2199
|
+
return await new Promise((resolve, reject) => {
|
|
2200
|
+
const socket = connect(port, address);
|
|
2201
|
+
socket.once("connect", () => resolve(socket));
|
|
2202
|
+
socket.once("error", (error) => {
|
|
2203
|
+
socket.destroy();
|
|
2204
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
2205
|
+
});
|
|
2206
|
+
});
|
|
2207
|
+
} catch (error) {
|
|
2208
|
+
lastError = error;
|
|
2209
|
+
}
|
|
2210
|
+
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("no adjudicated address accepted the connection");
|
|
2138
2211
|
}
|
|
2139
2212
|
/** CONNECT tunneling (HTTPS and friends): adjudicate, then either 403 or an established TCP tunnel. */
|
|
2140
2213
|
async handleConnect(req, socket, head) {
|
|
@@ -2149,14 +2222,21 @@ var NetworkProxy = class {
|
|
|
2149
2222
|
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
2150
2223
|
return;
|
|
2151
2224
|
}
|
|
2152
|
-
const decision = await this.decideWithResolution(target);
|
|
2225
|
+
const { decision, target: adjudicated } = await this.decideWithResolution(target);
|
|
2153
2226
|
if (decision.action !== "allow") {
|
|
2154
2227
|
this.recordBlock(decision, target);
|
|
2155
2228
|
const body = blockMessage(decision);
|
|
2156
2229
|
socket.end(`HTTP/1.1 403 Forbidden\r\ncontent-type: text/plain\r\ncontent-length: ${Buffer.byteLength(body)}\r\n\r\n${body}`);
|
|
2157
2230
|
return;
|
|
2158
2231
|
}
|
|
2159
|
-
|
|
2232
|
+
let tunnel;
|
|
2233
|
+
try {
|
|
2234
|
+
tunnel = await this.connectUpstream(adjudicated, target.port ?? 443);
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
this.options.logger.warn(`permission-rules: upstream connect failed for ${target.host}: ${String(error)}`);
|
|
2237
|
+
socket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2160
2240
|
tunnelHolder.tunnel = tunnel;
|
|
2161
2241
|
this.sockets.add(socket);
|
|
2162
2242
|
this.sockets.add(tunnel);
|
|
@@ -2167,27 +2247,10 @@ var NetworkProxy = class {
|
|
|
2167
2247
|
socket.on("close", cleanup);
|
|
2168
2248
|
tunnel.on("close", cleanup);
|
|
2169
2249
|
tunnel.on("error", () => socket.destroy());
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
socket.pipe(tunnel);
|
|
2175
|
-
});
|
|
2176
|
-
}
|
|
2177
|
-
/** Adjudicate one plain-HTTP request; deny/ask blocks with a structured 403 before any forwarding. */
|
|
2178
|
-
async forwardOrBlock(res, target, forward) {
|
|
2179
|
-
const decision = await this.decideWithResolution(target);
|
|
2180
|
-
if (decision.action !== "allow") {
|
|
2181
|
-
this.recordBlock(decision, target);
|
|
2182
|
-
const body = blockMessage(decision);
|
|
2183
|
-
res.writeHead(403, {
|
|
2184
|
-
"content-type": "text/plain; charset=utf-8",
|
|
2185
|
-
"content-length": Buffer.byteLength(body)
|
|
2186
|
-
});
|
|
2187
|
-
res.end(body);
|
|
2188
|
-
return;
|
|
2189
|
-
}
|
|
2190
|
-
forward();
|
|
2250
|
+
socket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
2251
|
+
if (head.length > 0) tunnel.write(head);
|
|
2252
|
+
tunnel.pipe(socket);
|
|
2253
|
+
socket.pipe(tunnel);
|
|
2191
2254
|
}
|
|
2192
2255
|
/** Resolve a hostname once so `ips`-scoped rules see the real addresses, then decide. */
|
|
2193
2256
|
async decideWithResolution(target) {
|
|
@@ -2196,12 +2259,21 @@ var NetworkProxy = class {
|
|
|
2196
2259
|
all: true,
|
|
2197
2260
|
verbatim: true
|
|
2198
2261
|
})).map((entry) => entry.address);
|
|
2199
|
-
if (resolved.length > 0)
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2262
|
+
if (resolved.length > 0) {
|
|
2263
|
+
const effective = {
|
|
2264
|
+
...target,
|
|
2265
|
+
ips: [...target.ips, ...resolved]
|
|
2266
|
+
};
|
|
2267
|
+
return {
|
|
2268
|
+
decision: await this.options.decide(effective),
|
|
2269
|
+
target: effective
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2203
2272
|
} catch {}
|
|
2204
|
-
return
|
|
2273
|
+
return {
|
|
2274
|
+
decision: await this.options.decide(target),
|
|
2275
|
+
target
|
|
2276
|
+
};
|
|
2205
2277
|
}
|
|
2206
2278
|
/** Record a block: counters, recent ring, and the runtime hook (logger + session audit). */
|
|
2207
2279
|
recordBlock(decision, target) {
|
|
@@ -2248,6 +2320,33 @@ function connectTarget(authority) {
|
|
|
2248
2320
|
};
|
|
2249
2321
|
}
|
|
2250
2322
|
/**
|
|
2323
|
+
* The literal addresses a connection may use — everything the decision was
|
|
2324
|
+
* made on. Returning them keeps the connection on an adjudicated address
|
|
2325
|
+
* instead of letting a second DNS lookup pick a different one.
|
|
2326
|
+
* @param target - the adjudicated target.
|
|
2327
|
+
* @returns the adjudicated literal addresses, in decision order.
|
|
2328
|
+
*/
|
|
2329
|
+
function pinnedAddresses(target) {
|
|
2330
|
+
return target.ips.filter((candidate) => isIpLiteral(candidate));
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* A `lookup` that answers from the adjudicated address list without a second
|
|
2334
|
+
* DNS query. Node's happy-eyeballs path asks with `all: true` and tries the
|
|
2335
|
+
* returned addresses in order, so a multi-address host keeps its fallbacks.
|
|
2336
|
+
* @param addresses - the adjudicated literal addresses.
|
|
2337
|
+
* @returns the lookup function handed to `http(s).request`.
|
|
2338
|
+
*/
|
|
2339
|
+
function pinnedLookup(addresses) {
|
|
2340
|
+
const resolved = addresses.map((address) => ({
|
|
2341
|
+
address,
|
|
2342
|
+
family: address.includes(":") ? 6 : 4
|
|
2343
|
+
}));
|
|
2344
|
+
return (_hostname, options, callback) => {
|
|
2345
|
+
if (options.all === true) callback(null, resolved);
|
|
2346
|
+
else callback(null, resolved[0].address, resolved[0].family);
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2251
2350
|
* Inject the proxy environment variables for subprocesses and return a
|
|
2252
2351
|
* disposer restoring every previous value exactly (so updates and
|
|
2253
2352
|
* uninstalls leave the host environment untouched). `noProxy: 'clear'`
|
package/lib/types/proxy.d.ts
CHANGED
|
@@ -99,10 +99,15 @@ export declare class NetworkProxy {
|
|
|
99
99
|
close(): Promise<void>;
|
|
100
100
|
/** Plain-HTTP proxying: absolute-form requests are adjudicated and forwarded (or blocked with a structured 403). */
|
|
101
101
|
private handleRequest;
|
|
102
|
+
/**
|
|
103
|
+
* Connect to the first adjudicated address that accepts, so the tunnel lands
|
|
104
|
+
* on an address the decision approved; a hostname target with no resolution
|
|
105
|
+
* keeps the historical hostname connect. Each candidate is tried in order so
|
|
106
|
+
* a multi-address host does not lose its fallback.
|
|
107
|
+
*/
|
|
108
|
+
private connectUpstream;
|
|
102
109
|
/** CONNECT tunneling (HTTPS and friends): adjudicate, then either 403 or an established TCP tunnel. */
|
|
103
110
|
private handleConnect;
|
|
104
|
-
/** Adjudicate one plain-HTTP request; deny/ask blocks with a structured 403 before any forwarding. */
|
|
105
|
-
private forwardOrBlock;
|
|
106
111
|
/** Resolve a hostname once so `ips`-scoped rules see the real addresses, then decide. */
|
|
107
112
|
private decideWithResolution;
|
|
108
113
|
/** Record a block: counters, recent ring, and the runtime hook (logger + session audit). */
|
package/lib/types/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/proxy.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/lib/types/rules.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/rules.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/rules.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-permission-rules",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.17",
|
|
4
4
|
"description": "Declarative Claude Code-style permission rules plus a Codex-style process-level network policy for DeepSeek Harness: ordered allow/deny/ask rules with tool-name, argument (glob/regex), workspace-path, and network-target (domain/ip/port/scheme) matching on the tools/pre-execute waterfall, a built-in local HTTP/CONNECT proxy governing shell subprocess traffic, three network modes mapped onto the official sandbox presets, full session-log audit, HMR rule reload, and a settings page with a rule editor, block counters, and recent interception lists.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"network:outbound"
|
|
77
77
|
],
|
|
78
78
|
"compatibility": {
|
|
79
|
-
"dshVersions": ["0.1.2-rc.1","0.1.5-
|
|
79
|
+
"dshVersions": ["0.1.2-rc.1","0.1.5-rc.1"]
|
|
80
80
|
},
|
|
81
81
|
"capability": {
|
|
82
82
|
"id": "rules",
|
|
@@ -154,31 +154,31 @@
|
|
|
154
154
|
"zod": "^4.4.3"
|
|
155
155
|
},
|
|
156
156
|
"devDependencies": {
|
|
157
|
-
"@deepseek-ai/dsh-attachment": "0.1.5-
|
|
157
|
+
"@deepseek-ai/dsh-attachment": "0.1.5-rc.1",
|
|
158
158
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
159
159
|
"@deepseek-ai/cordis-plugin-include": "^1.0.7",
|
|
160
160
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.3",
|
|
161
|
-
"@deepseek-ai/dsh-agent": "0.1.5-
|
|
162
|
-
"@deepseek-ai/dsh-brand": "0.1.5-
|
|
163
|
-
"@deepseek-ai/dsh-client-connection": "0.1.5-
|
|
164
|
-
"@deepseek-ai/dsh-client-locale": "0.1.5-
|
|
165
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-
|
|
166
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-
|
|
167
|
-
"@deepseek-ai/dsh-client-web": "0.1.5-
|
|
168
|
-
"@deepseek-ai/dsh-commands": "0.1.5-
|
|
169
|
-
"@deepseek-ai/dsh-invariants": "0.1.5-
|
|
170
|
-
"@deepseek-ai/dsh-llm": "0.1.5-
|
|
171
|
-
"@deepseek-ai/dsh-session": "0.1.5-
|
|
172
|
-
"@deepseek-ai/dsh-session-projection": "0.1.5-
|
|
173
|
-
"@deepseek-ai/dsh-subagent": "0.1.5-
|
|
174
|
-
"@deepseek-ai/dsh-tools": "0.1.5-
|
|
175
|
-
"@deepseek-ai/dsh-typert-protocol": "0.1.5-
|
|
176
|
-
"@deepseek-ai/dsh-user-approval": "0.1.5-
|
|
161
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.1",
|
|
162
|
+
"@deepseek-ai/dsh-brand": "0.1.5-rc.1",
|
|
163
|
+
"@deepseek-ai/dsh-client-connection": "0.1.5-rc.1",
|
|
164
|
+
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.1",
|
|
165
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.1",
|
|
166
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.1",
|
|
167
|
+
"@deepseek-ai/dsh-client-web": "0.1.5-rc.1",
|
|
168
|
+
"@deepseek-ai/dsh-commands": "0.1.5-rc.1",
|
|
169
|
+
"@deepseek-ai/dsh-invariants": "0.1.5-rc.1",
|
|
170
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.1",
|
|
171
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.1",
|
|
172
|
+
"@deepseek-ai/dsh-session-projection": "0.1.5-rc.1",
|
|
173
|
+
"@deepseek-ai/dsh-subagent": "0.1.5-rc.1",
|
|
174
|
+
"@deepseek-ai/dsh-tools": "0.1.5-rc.1",
|
|
175
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.5-rc.1",
|
|
176
|
+
"@deepseek-ai/dsh-user-approval": "0.1.5-rc.1",
|
|
177
177
|
"@deepseek-ai/schemastery": "^3.18.2",
|
|
178
178
|
"@eslint/js": "^10.0.1",
|
|
179
179
|
"@types/node": "^22.19.0",
|
|
180
180
|
"@vitest/coverage-v8": "^3.2.0",
|
|
181
|
-
"dsh-auto-review": "file:vendor/dsh-auto-review-0.12.
|
|
181
|
+
"dsh-auto-review": "file:vendor/dsh-auto-review-0.12.2.tgz",
|
|
182
182
|
"eslint": "^10.8.1",
|
|
183
183
|
"globals": "^17.11.0",
|
|
184
184
|
"react": "18.3.1",
|
|
@@ -82,13 +82,14 @@ const STORAGE_ROW_TYPES = new Set([
|
|
|
82
82
|
/**
|
|
83
83
|
* The harness build's generated event vocabulary
|
|
84
84
|
* (`KNOWN_SESSION_EVENT_TYPES`, `packages/core/session/src/known-event-types.ts`,
|
|
85
|
-
* synced with the `0.1.5-
|
|
85
|
+
* synced with the `0.1.5-rc.1` catalog), plus the v1/v2-only spellings the
|
|
86
86
|
* older generations wrote (`assistant/chunk`, `tool/code-dispatch`,
|
|
87
87
|
* `tool/code-dispatch-start`; V3 renamed the latter two to `tool/ptc-dispatch`
|
|
88
88
|
* and `tool/ptc-dispatch-start`), so every generation scans clean. Rows whose
|
|
89
89
|
* type is outside this set are what the read path refuses when the envelope
|
|
90
90
|
* lacks `ignorable: true`; the scan reports them so nothing is missed.
|
|
91
|
-
* Regenerate this list from the harness checkout when it changes.
|
|
91
|
+
* Regenerate this list from the harness checkout when it changes (0.1.5-rc.1
|
|
92
|
+
* added `deliverables/presented` and `subagent/catalog`).
|
|
92
93
|
*/
|
|
93
94
|
const KNOWN_SESSION_EVENT_TYPES = new Set([
|
|
94
95
|
'agent-preset/selected',
|
|
@@ -107,6 +108,7 @@ const KNOWN_SESSION_EVENT_TYPES = new Set([
|
|
|
107
108
|
'compaction/prune',
|
|
108
109
|
'compaction/start',
|
|
109
110
|
'compaction/summary',
|
|
111
|
+
'deliverables/presented',
|
|
110
112
|
'feedback/message-delete',
|
|
111
113
|
'feedback/message-put',
|
|
112
114
|
'feedback/record',
|
|
@@ -128,6 +130,7 @@ const KNOWN_SESSION_EVENT_TYPES = new Set([
|
|
|
128
130
|
'session/title-llm-request',
|
|
129
131
|
'step/end',
|
|
130
132
|
'step/start',
|
|
133
|
+
'subagent/catalog',
|
|
131
134
|
'subagent/descriptor',
|
|
132
135
|
'subagent/model-selection-policy',
|
|
133
136
|
'system/message',
|
package/src/proxy.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { connect } from 'node:net'
|
|
|
21
21
|
import { lookup } from 'node:dns/promises'
|
|
22
22
|
import type { AddressInfo } from 'node:net'
|
|
23
23
|
import type { Duplex } from 'node:stream'
|
|
24
|
-
import type { Server, IncomingMessage, ServerResponse } from 'node:http'
|
|
24
|
+
import type { Server, IncomingMessage, RequestOptions, ServerResponse } from 'node:http'
|
|
25
25
|
import type { CallId } from './call-id.ts'
|
|
26
26
|
import type { NetworkDecision, NetworkMode } from './network.ts'
|
|
27
27
|
import { blockMessage } from './network.ts'
|
|
@@ -163,23 +163,65 @@ export class NetworkProxy {
|
|
|
163
163
|
res.end('permission-rules proxy: only absolute-form proxy requests are served here\n')
|
|
164
164
|
return
|
|
165
165
|
}
|
|
166
|
-
await this.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
req.
|
|
166
|
+
const { decision, target: adjudicated } = await this.decideWithResolution(target)
|
|
167
|
+
if (decision.action !== 'allow') {
|
|
168
|
+
this.recordBlock(decision, target)
|
|
169
|
+
const body = blockMessage(decision)
|
|
170
|
+
res.writeHead(403, { 'content-type': 'text/plain; charset=utf-8', 'content-length': Buffer.byteLength(body) })
|
|
171
|
+
res.end(body)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
const upstream = new URL(req.url as string)
|
|
175
|
+
const send = upstream.protocol === 'https:' ? httpsRequest : httpRequest
|
|
176
|
+
// The forwarded connection is pinned to the addresses the decision was made
|
|
177
|
+
// on, so a hostname that re-resolves between adjudication and connect
|
|
178
|
+
// (DNS rebinding) cannot reach an address the rules never approved.
|
|
179
|
+
const pinned = pinnedAddresses(adjudicated)
|
|
180
|
+
const options: RequestOptions = {
|
|
181
|
+
method: req.method,
|
|
182
|
+
headers: req.headers,
|
|
183
|
+
...(pinned.length === 0 ? {} : { lookup: pinnedLookup(pinned) }),
|
|
184
|
+
}
|
|
185
|
+
const proxyReq = send(upstream, options, proxyRes => {
|
|
186
|
+
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers)
|
|
187
|
+
proxyRes.pipe(res)
|
|
188
|
+
})
|
|
189
|
+
proxyReq.on('error', (error: unknown) => {
|
|
190
|
+
if (!res.headersSent) {
|
|
191
|
+
res.writeHead(502, { 'content-type': 'text/plain' })
|
|
192
|
+
res.end(`[network: upstream error] ${String(error)}\n`)
|
|
193
|
+
} else {
|
|
194
|
+
res.destroy()
|
|
195
|
+
}
|
|
182
196
|
})
|
|
197
|
+
req.pipe(proxyReq)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Connect to the first adjudicated address that accepts, so the tunnel lands
|
|
202
|
+
* on an address the decision approved; a hostname target with no resolution
|
|
203
|
+
* keeps the historical hostname connect. Each candidate is tried in order so
|
|
204
|
+
* a multi-address host does not lose its fallback.
|
|
205
|
+
*/
|
|
206
|
+
private async connectUpstream(target: NetworkTarget, port: number): Promise<Duplex> {
|
|
207
|
+
const pinned = pinnedAddresses(target)
|
|
208
|
+
if (pinned.length === 0) return connect(port, target.host)
|
|
209
|
+
let lastError: unknown
|
|
210
|
+
for (const address of pinned) {
|
|
211
|
+
try {
|
|
212
|
+
return await new Promise<Duplex>((resolve, reject) => {
|
|
213
|
+
const socket = connect(port, address)
|
|
214
|
+
socket.once('connect', () => resolve(socket))
|
|
215
|
+
socket.once('error', (error: unknown) => {
|
|
216
|
+
socket.destroy()
|
|
217
|
+
reject(error instanceof Error ? error : new Error(String(error)))
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
} catch (error) {
|
|
221
|
+
lastError = error
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
throw lastError instanceof Error ? lastError : new Error('no adjudicated address accepted the connection')
|
|
183
225
|
}
|
|
184
226
|
|
|
185
227
|
/** CONNECT tunneling (HTTPS and friends): adjudicate, then either 403 or an established TCP tunnel. */
|
|
@@ -201,14 +243,21 @@ export class NetworkProxy {
|
|
|
201
243
|
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
|
|
202
244
|
return
|
|
203
245
|
}
|
|
204
|
-
const decision = await this.decideWithResolution(target)
|
|
246
|
+
const { decision, target: adjudicated } = await this.decideWithResolution(target)
|
|
205
247
|
if (decision.action !== 'allow') {
|
|
206
248
|
this.recordBlock(decision, target)
|
|
207
249
|
const body = blockMessage(decision)
|
|
208
250
|
socket.end(`HTTP/1.1 403 Forbidden\r\ncontent-type: text/plain\r\ncontent-length: ${Buffer.byteLength(body)}\r\n\r\n${body}`)
|
|
209
251
|
return
|
|
210
252
|
}
|
|
211
|
-
|
|
253
|
+
let tunnel: Duplex
|
|
254
|
+
try {
|
|
255
|
+
tunnel = await this.connectUpstream(adjudicated, target.port ?? 443)
|
|
256
|
+
} catch (error) {
|
|
257
|
+
this.options.logger.warn(`permission-rules: upstream connect failed for ${target.host}: ${String(error)}`)
|
|
258
|
+
socket.end('HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
|
259
|
+
return
|
|
260
|
+
}
|
|
212
261
|
tunnelHolder.tunnel = tunnel
|
|
213
262
|
this.sockets.add(socket)
|
|
214
263
|
this.sockets.add(tunnel)
|
|
@@ -219,39 +268,27 @@ export class NetworkProxy {
|
|
|
219
268
|
socket.on('close', cleanup)
|
|
220
269
|
tunnel.on('close', cleanup)
|
|
221
270
|
tunnel.on('error', () => socket.destroy())
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
socket.pipe(tunnel)
|
|
227
|
-
})
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** Adjudicate one plain-HTTP request; deny/ask blocks with a structured 403 before any forwarding. */
|
|
231
|
-
private async forwardOrBlock(res: ServerResponse, target: NetworkTarget, forward: () => void): Promise<void> {
|
|
232
|
-
const decision = await this.decideWithResolution(target)
|
|
233
|
-
if (decision.action !== 'allow') {
|
|
234
|
-
this.recordBlock(decision, target)
|
|
235
|
-
const body = blockMessage(decision)
|
|
236
|
-
res.writeHead(403, { 'content-type': 'text/plain; charset=utf-8', 'content-length': Buffer.byteLength(body) })
|
|
237
|
-
res.end(body)
|
|
238
|
-
return
|
|
239
|
-
}
|
|
240
|
-
forward()
|
|
271
|
+
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n')
|
|
272
|
+
if (head.length > 0) tunnel.write(head)
|
|
273
|
+
tunnel.pipe(socket)
|
|
274
|
+
socket.pipe(tunnel)
|
|
241
275
|
}
|
|
242
276
|
|
|
243
277
|
/** Resolve a hostname once so `ips`-scoped rules see the real addresses, then decide. */
|
|
244
|
-
private async decideWithResolution(target: NetworkTarget): Promise<NetworkDecision> {
|
|
278
|
+
private async decideWithResolution(target: NetworkTarget): Promise<{ decision: NetworkDecision; target: NetworkTarget }> {
|
|
245
279
|
if (!isIpLiteral(target.host)) {
|
|
246
280
|
try {
|
|
247
281
|
const addresses = await lookup(target.host, { all: true, verbatim: true })
|
|
248
282
|
const resolved = addresses.map(entry => entry.address)
|
|
249
|
-
if (resolved.length > 0)
|
|
283
|
+
if (resolved.length > 0) {
|
|
284
|
+
const effective: NetworkTarget = { ...target, ips: [...target.ips, ...resolved] }
|
|
285
|
+
return { decision: await this.options.decide(effective), target: effective }
|
|
286
|
+
}
|
|
250
287
|
} catch {
|
|
251
288
|
// Unresolvable target: decide on the literal name (ip-scoped rules simply cannot fire).
|
|
252
289
|
}
|
|
253
290
|
}
|
|
254
|
-
return this.options.decide(target)
|
|
291
|
+
return { decision: await this.options.decide(target), target }
|
|
255
292
|
}
|
|
256
293
|
|
|
257
294
|
/** Record a block: counters, recent ring, and the runtime hook (logger + session audit). */
|
|
@@ -295,6 +332,32 @@ function connectTarget(authority: string): NetworkTarget | undefined {
|
|
|
295
332
|
return { scheme: 'https', host, port, ips: isIpLiteral(host) ? [host] : [] }
|
|
296
333
|
}
|
|
297
334
|
|
|
335
|
+
/**
|
|
336
|
+
* The literal addresses a connection may use — everything the decision was
|
|
337
|
+
* made on. Returning them keeps the connection on an adjudicated address
|
|
338
|
+
* instead of letting a second DNS lookup pick a different one.
|
|
339
|
+
* @param target - the adjudicated target.
|
|
340
|
+
* @returns the adjudicated literal addresses, in decision order.
|
|
341
|
+
*/
|
|
342
|
+
function pinnedAddresses(target: NetworkTarget): readonly string[] {
|
|
343
|
+
return target.ips.filter(candidate => isIpLiteral(candidate))
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* A `lookup` that answers from the adjudicated address list without a second
|
|
348
|
+
* DNS query. Node's happy-eyeballs path asks with `all: true` and tries the
|
|
349
|
+
* returned addresses in order, so a multi-address host keeps its fallbacks.
|
|
350
|
+
* @param addresses - the adjudicated literal addresses.
|
|
351
|
+
* @returns the lookup function handed to `http(s).request`.
|
|
352
|
+
*/
|
|
353
|
+
function pinnedLookup(addresses: readonly string[]): NonNullable<RequestOptions['lookup']> {
|
|
354
|
+
const resolved = addresses.map(address => ({ address, family: address.includes(':') ? 6 : 4 }))
|
|
355
|
+
return (_hostname, options, callback) => {
|
|
356
|
+
if (options.all === true) callback(null, resolved)
|
|
357
|
+
else callback(null, resolved[0]!.address, resolved[0]!.family)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
298
361
|
/**
|
|
299
362
|
* Inject the proxy environment variables for subprocesses and return a
|
|
300
363
|
* disposer restoring every previous value exactly (so updates and
|
package/src/rules.ts
CHANGED
|
@@ -1087,9 +1087,31 @@ function normalizeHost(host: string): string {
|
|
|
1087
1087
|
return out
|
|
1088
1088
|
}
|
|
1089
1089
|
|
|
1090
|
-
/**
|
|
1090
|
+
/**
|
|
1091
|
+
* Map an IPv4-mapped IPv6 literal (dotted or hex form) back to its dotted IPv4
|
|
1092
|
+
* quad. Node routes `::ffff:169.254.169.254` and `::ffff:a9fe:a9fe` to the same
|
|
1093
|
+
* IPv4 destination as `169.254.169.254`, so both spellings must compare as that
|
|
1094
|
+
* address: without this normalization an `ips` rule written in IPv4 terms never
|
|
1095
|
+
* matches the mapped spelling and the deny is bypassed.
|
|
1096
|
+
* @param host - lowercased, unbracketed host or pattern.
|
|
1097
|
+
* @returns the IPv4 dotted quad when the input is IPv4-mapped, else the input.
|
|
1098
|
+
*/
|
|
1099
|
+
function unmapIpv4(host: string): string {
|
|
1100
|
+
const lower = host.toLowerCase()
|
|
1101
|
+
const dotted = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(lower)
|
|
1102
|
+
if (dotted !== null && dotted[1] !== undefined) return dotted[1]
|
|
1103
|
+
const hex = /^(?:(?:0{0,4}:){5}|::)ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(lower)
|
|
1104
|
+
if (hex !== null && hex[1] !== undefined && hex[2] !== undefined) {
|
|
1105
|
+
const high = Number.parseInt(hex[1], 16)
|
|
1106
|
+
const low = Number.parseInt(hex[2], 16)
|
|
1107
|
+
return `${high >> 8}.${high & 0xff}.${low >> 8}.${low & 0xff}`
|
|
1108
|
+
}
|
|
1109
|
+
return lower
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/** The literal IP of a host, or `[]` when the host is a name. Mapped literals normalize to IPv4. */
|
|
1091
1113
|
function literalIpOf(host: string): readonly string[] {
|
|
1092
|
-
return isIpLiteral(host) ? [host.toLowerCase()] : []
|
|
1114
|
+
return isIpLiteral(host) ? [unmapIpv4(host.toLowerCase())] : []
|
|
1093
1115
|
}
|
|
1094
1116
|
|
|
1095
1117
|
/** Whether a host string is an IP literal (IPv4 or IPv6). */
|
|
@@ -1139,19 +1161,36 @@ function compilePortPattern(pattern: string): CompiledPortRange {
|
|
|
1139
1161
|
return { min: low, max: high }
|
|
1140
1162
|
}
|
|
1141
1163
|
|
|
1164
|
+
/**
|
|
1165
|
+
* Normalize an IP pattern's spelling: a mapped literal becomes its IPv4 quad,
|
|
1166
|
+
* and a mapped glob keeps its IPv4 remainder (`::ffff:10.0.*.*` → `10.0.*.*`),
|
|
1167
|
+
* so IPv4 patterns cover mapped spellings too.
|
|
1168
|
+
* @param pattern - the raw pattern.
|
|
1169
|
+
* @returns the normalized, lowercased pattern.
|
|
1170
|
+
*/
|
|
1171
|
+
function unmapIpv4Pattern(pattern: string): string {
|
|
1172
|
+
const lower = pattern.toLowerCase()
|
|
1173
|
+
const mapped = unmapIpv4(lower)
|
|
1174
|
+
if (mapped !== lower) return mapped
|
|
1175
|
+
const dottedPrefix = /^::ffff:(.+)$/.exec(lower)
|
|
1176
|
+
if (dottedPrefix !== null && dottedPrefix[1] !== undefined) return dottedPrefix[1]
|
|
1177
|
+
return lower
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1142
1180
|
/** Compile one IP pattern: CIDR first, then glob; exact literals glob-compile to themselves. */
|
|
1143
1181
|
function compileIpPattern(pattern: string, maxGlobStars: number): CompiledIpPattern {
|
|
1144
|
-
const
|
|
1182
|
+
const normalized = unmapIpv4Pattern(pattern)
|
|
1183
|
+
const cidr = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/.exec(normalized)
|
|
1145
1184
|
if (cidr !== null) {
|
|
1146
1185
|
const octets = cidr.slice(1, 5).map(Number) as [number, number, number, number]
|
|
1147
1186
|
const prefix = Number(cidr[5])
|
|
1148
1187
|
const network = ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0
|
|
1149
1188
|
return { cidr: { network, prefix } }
|
|
1150
1189
|
}
|
|
1151
|
-
if (!/[[*?]/.test(
|
|
1152
|
-
return { literal:
|
|
1190
|
+
if (!/[[*?]/.test(normalized) && !normalized.includes(':')) {
|
|
1191
|
+
return { literal: normalized }
|
|
1153
1192
|
}
|
|
1154
|
-
return { regex: compileGlob(
|
|
1193
|
+
return { regex: compileGlob(normalized, { segments: false, maxStars: maxGlobStars, caseInsensitive: true }) }
|
|
1155
1194
|
}
|
|
1156
1195
|
|
|
1157
1196
|
/**
|
|
@@ -1226,9 +1265,9 @@ export function targetMatchesNetwork(target: NetworkTarget, network: CompiledNet
|
|
|
1226
1265
|
return true
|
|
1227
1266
|
}
|
|
1228
1267
|
|
|
1229
|
-
/** Whether one compiled IP pattern matches one candidate address (lowercased, unbracketed). */
|
|
1268
|
+
/** Whether one compiled IP pattern matches one candidate address (lowercased, unbracketed, mapped IPv4 unmapped). */
|
|
1230
1269
|
function ipMatches(pattern: CompiledIpPattern, candidate: string): boolean {
|
|
1231
|
-
const normalized = normalizeHost(candidate)
|
|
1270
|
+
const normalized = unmapIpv4(normalizeHost(candidate))
|
|
1232
1271
|
if (pattern.cidr !== undefined) {
|
|
1233
1272
|
const parts = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(normalized)
|
|
1234
1273
|
if (parts === null) return false
|