@openclaw/proxyline 0.3.2 → 0.3.4
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 +11 -0
- package/README.md +7 -2
- package/dist/connect.d.ts +1 -0
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +9 -0
- package/dist/node-http.d.ts.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +55 -20
- package/dist/shared.d.ts.map +1 -1
- package/docs/README.md +2 -1
- package/docs/api-reference.md +2 -0
- package/docs/getting-started.md +1 -1
- package/docs/security.md +2 -2
- package/docs/surfaces.md +3 -1
- package/docs/testing.md +1 -1
- package/package.json +17 -11
- package/scripts/prepack-build.mjs +6 -1
- package/src/connect.ts +11 -0
- package/src/runtime.ts +69 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4 - 2026-07-20
|
|
4
|
+
|
|
5
|
+
- Added `AbortSignal` cancellation to explicit CONNECT tunnels, including active socket cleanup.
|
|
6
|
+
- Fixed managed HTTPS proxy connections to IPv6 literal endpoints so equivalent compressed and expanded certificate addresses validate without sending IP-literal SNI.
|
|
7
|
+
- Updated Undici to 8.5.0 and raised the compatible peer floor to pick up current security fixes; refreshed pnpm, Node types, and transitive esbuild tooling.
|
|
8
|
+
|
|
9
|
+
## 0.3.3 - 2026-05-18
|
|
10
|
+
|
|
11
|
+
- Fixed managed `globalThis.fetch` so later Undici global dispatcher replacement cannot bypass the active Proxyline dispatcher. Thanks @jesse-merhi.
|
|
12
|
+
- Updated package tooling and test dependencies to exact latest versions, including pnpm 11.1.2, TypeScript 6.0.3, and Undici 8.3.0; raised the Node.js floor to 22.19.0 to match Undici 8.
|
|
13
|
+
|
|
3
14
|
## 0.3.2 - 2026-05-17
|
|
4
15
|
|
|
5
16
|
- Fixed managed Undici proxy dispatchers so HTTPS proxy endpoints addressed by IP do not send invalid IP-literal SNI.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# 🌐 Proxyline
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
[](https://www.npmjs.com/package/@openclaw/proxyline)
|
|
4
6
|
[](https://nodejs.org/)
|
|
5
7
|
[](./LICENSE)
|
|
@@ -31,7 +33,7 @@ pnpm add @openclaw/proxyline
|
|
|
31
33
|
npm install @openclaw/proxyline
|
|
32
34
|
```
|
|
33
35
|
|
|
34
|
-
Requires Node
|
|
36
|
+
Requires Node 22.19.0+ and a host `undici` dependency compatible with `>=8.5.0 <9`.
|
|
35
37
|
|
|
36
38
|
## Quick start
|
|
37
39
|
|
|
@@ -76,12 +78,14 @@ const socket = new WebSocket("wss://events.example.com/", {
|
|
|
76
78
|
```ts
|
|
77
79
|
import { openProxyConnectTunnel } from "@openclaw/proxyline";
|
|
78
80
|
|
|
81
|
+
const controller = new AbortController();
|
|
79
82
|
const socket = await openProxyConnectTunnel({
|
|
80
83
|
proxyUrl: "https://proxy.corp.example:8443",
|
|
81
84
|
proxyTls: { caFile: "/etc/proxy-ca.pem" },
|
|
82
85
|
targetHost: "api.example.com",
|
|
83
86
|
targetPort: 443,
|
|
84
87
|
timeoutMs: 2_000,
|
|
88
|
+
signal: controller.signal,
|
|
85
89
|
});
|
|
86
90
|
```
|
|
87
91
|
|
|
@@ -103,7 +107,8 @@ It uses Proxyline's built-in HTTP/HTTPS Node agent, and `proxyTls` applies only
|
|
|
103
107
|
|
|
104
108
|
- `http.request` / `http.get`: covered by global method patching and global agent replacement.
|
|
105
109
|
- `https.request` / `https.get`: covered by global method patching and global agent replacement.
|
|
106
|
-
- `fetch
|
|
110
|
+
- `globalThis.fetch`: covered by the fetch patch, including explicit dispatcher options and later Undici global dispatcher replacement in managed mode.
|
|
111
|
+
- Undici global dispatcher: installed for Undici APIs that read the current process dispatcher.
|
|
107
112
|
- WebSocket clients accepting a Node `agent`: covered with `proxy.createWebSocketAgent()`.
|
|
108
113
|
- Caller-built `http.Agent` / `https.Agent`: overridden in managed and active ambient mode, with TLS options preserved.
|
|
109
114
|
- Explicit HTTP CONNECT sockets: covered with `openProxyConnectTunnel()`.
|
package/dist/connect.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type OpenProxyConnectTunnelOptions = Readonly<{
|
|
|
7
7
|
targetHost: string;
|
|
8
8
|
targetPort: number;
|
|
9
9
|
timeoutMs?: number;
|
|
10
|
+
signal?: AbortSignal;
|
|
10
11
|
}>;
|
|
11
12
|
type ProxySocket = net.Socket | tls.TLSSocket;
|
|
12
13
|
export declare function formatConnectAuthority(targetHost: string, targetPort: number): string;
|
package/dist/connect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAkB,KAAK,mBAAmB,EAAqC,MAAM,aAAa,CAAC;AAE1G,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAkB,KAAK,mBAAmB,EAAqC,MAAM,aAAa,CAAC;AAE1G,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC,CAAC;AAMH,KAAK,WAAW,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC;AAsB9C,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAqBrF;AAkDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,WAAW,CAAC,CAwHtB"}
|
package/dist/connect.js
CHANGED
|
@@ -100,6 +100,7 @@ export async function openProxyConnectTunnel(options) {
|
|
|
100
100
|
socket?.off("close", onClosed);
|
|
101
101
|
socket?.off("connect", onConnected);
|
|
102
102
|
socket?.off("secureConnect", onConnected);
|
|
103
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
103
104
|
};
|
|
104
105
|
const fail = (error) => {
|
|
105
106
|
if (settled) {
|
|
@@ -162,7 +163,15 @@ export async function openProxyConnectTunnel(options) {
|
|
|
162
163
|
const onClosed = () => {
|
|
163
164
|
fail(new Error("proxy socket closed before CONNECT response"));
|
|
164
165
|
};
|
|
166
|
+
const onAbort = () => {
|
|
167
|
+
fail(options.signal?.reason ?? new Error("proxy CONNECT aborted"));
|
|
168
|
+
};
|
|
165
169
|
try {
|
|
170
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
171
|
+
if (options.signal?.aborted) {
|
|
172
|
+
onAbort();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
166
175
|
if (options.timeoutMs !== undefined && options.timeoutMs > 0) {
|
|
167
176
|
timeout = setTimeout(() => {
|
|
168
177
|
fail(new Error(`proxy CONNECT timed out after ${Math.trunc(options.timeoutMs ?? 0)}ms`));
|
package/dist/node-http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,YAAY,CAAC;AAI/B,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAqC,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,GAAG;IAChF,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;CAC5B,CAAC;AAEF,KAAK,cAAc,GAAG,OAAO,IAAI,CAAC,OAAO,CAAC;AAC1C,KAAK,gBAAgB,GAAG,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC,KAAK,CAAC;AACxE,KAAK,gBAAgB,GAAG,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;AAC/D,KAAK,uBAAuB,GAAG,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,GAAG;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAYF,KAAK,qBAAqB,GAAG,gBAAgB,GAAG;IAC9C,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,cAAc,EAAE,CACd,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,gBAAgB,EAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,aAAa,KACzB,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC,CAAC;AAOF,eAAO,MAAM,4BAA4B,
|
|
1
|
+
{"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAIV,eAAe,EACf,gBAAgB,EAIjB,MAAM,YAAY,CAAC;AAqzBpB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAgI3E;AAED,eAAO,MAAM,kBAAkB,yBAAmB,CAAC"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { X509Certificate } from "node:crypto";
|
|
4
5
|
import net from "node:net";
|
|
6
|
+
import tls from "node:tls";
|
|
5
7
|
import { Agent as UndiciAgent, Dispatcher, FormData as UndiciFormData, Headers as UndiciHeaders, Pool as UndiciPool, Request as UndiciRequest, Response as UndiciResponse, errors as undiciErrors, fetch as undiciFetch, getGlobalDispatcher, ProxyAgent as UndiciProxyAgent, setGlobalDispatcher, } from "undici";
|
|
6
8
|
import { createAmbientProxyResolver, EMPTY_PROXY_ENV, resolveAmbientProxyForUrl, readProxyEnv, } from "./env.js";
|
|
7
9
|
import { bindNodeHttpMethod, createDirectNodeAgent, createNodeProxyAgent, } from "./node-http.js";
|
|
@@ -100,25 +102,32 @@ async function normalizeFetchInput(input, init, options) {
|
|
|
100
102
|
preserveDispatcher: options.preserveDispatcher,
|
|
101
103
|
});
|
|
102
104
|
}
|
|
103
|
-
function
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
function withManagedFetchDispatcher(init, dispatcher) {
|
|
106
|
+
if (init !== undefined &&
|
|
107
|
+
init !== null &&
|
|
108
|
+
typeof init !== "object" &&
|
|
109
|
+
typeof init !== "function") {
|
|
110
|
+
throw new TypeError(`Request constructor: Expected ${String(init)} to be one of: Null, Undefined, Object.`);
|
|
111
|
+
}
|
|
112
|
+
const sanitized = init === undefined || init === null ? {} : Object.create(init);
|
|
108
113
|
Reflect.defineProperty(sanitized, "dispatcher", {
|
|
109
114
|
configurable: true,
|
|
110
115
|
enumerable: true,
|
|
111
|
-
value:
|
|
116
|
+
value: dispatcher,
|
|
112
117
|
writable: true,
|
|
113
118
|
});
|
|
114
119
|
return sanitized;
|
|
115
120
|
}
|
|
116
121
|
const proxylineFetch = async (input, init) => {
|
|
117
|
-
const
|
|
122
|
+
const managedDispatcher = activeRuntime?.mode === "managed"
|
|
123
|
+
? activeRuntime.installedDispatcher
|
|
124
|
+
: undefined;
|
|
118
125
|
const normalizedInput = await normalizeFetchInput(input, init, {
|
|
119
|
-
preserveDispatcher:
|
|
126
|
+
preserveDispatcher: managedDispatcher === undefined,
|
|
120
127
|
});
|
|
121
|
-
const normalizedInit =
|
|
128
|
+
const normalizedInit = managedDispatcher === undefined
|
|
129
|
+
? init
|
|
130
|
+
: withManagedFetchDispatcher(init, managedDispatcher);
|
|
122
131
|
const response = await Reflect.apply(undiciFetch, undefined, normalizedInit === undefined ? [normalizedInput] : [normalizedInput, normalizedInit]);
|
|
123
132
|
if (!(response instanceof proxylineResponse)) {
|
|
124
133
|
throw new TypeError("Proxyline fetch returned a non-Response value.");
|
|
@@ -294,6 +303,21 @@ function stripIpServernameFromConnect(connect) {
|
|
|
294
303
|
}
|
|
295
304
|
return (options, callback) => connect(stripIpServernameFromConnectOptions(options), callback);
|
|
296
305
|
}
|
|
306
|
+
const checkProxyServerIdentity = (hostname, certificate) => {
|
|
307
|
+
const normalizedHostname = hostname.replace(/^\[|\]$/g, "");
|
|
308
|
+
if (net.isIP(normalizedHostname) === 6) {
|
|
309
|
+
try {
|
|
310
|
+
// Compare binary addresses so compressed and expanded IPv6 SAN forms remain equivalent.
|
|
311
|
+
if (new X509Certificate(certificate.raw).checkIP(normalizedHostname) !== undefined) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
// Let Node return its canonical certificate validation error below.
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return tls.checkServerIdentity(normalizedHostname, certificate);
|
|
320
|
+
};
|
|
297
321
|
function createProxyClientFactory() {
|
|
298
322
|
return (origin, options) => {
|
|
299
323
|
const clientOptions = isObjectRecord(options)
|
|
@@ -303,11 +327,18 @@ function createProxyClientFactory() {
|
|
|
303
327
|
};
|
|
304
328
|
}
|
|
305
329
|
function createUndiciProxyAgent(proxyUrl, options) {
|
|
330
|
+
const proxyHostname = new URL(proxyUrl).hostname.replace(/^\[|\]$/g, "");
|
|
331
|
+
const proxyTls = options.proxyCa !== undefined || net.isIP(proxyHostname) === 6
|
|
332
|
+
? {
|
|
333
|
+
...(options.proxyCa !== undefined ? { ca: options.proxyCa } : {}),
|
|
334
|
+
...(net.isIP(proxyHostname) === 6 ? { checkServerIdentity: checkProxyServerIdentity } : {}),
|
|
335
|
+
}
|
|
336
|
+
: undefined;
|
|
306
337
|
return new UndiciProxyAgent({
|
|
307
338
|
...resolveUndiciBaseOptions(options.undici),
|
|
308
339
|
uri: proxyUrl,
|
|
309
340
|
clientFactory: createProxyClientFactory(),
|
|
310
|
-
...(
|
|
341
|
+
...(proxyTls !== undefined ? { proxyTls } : {}),
|
|
311
342
|
});
|
|
312
343
|
}
|
|
313
344
|
function createUndiciProxyDispatcher(options, dispatcherOptions) {
|
|
@@ -319,6 +350,18 @@ function createUndiciProxyDispatcher(options, dispatcherOptions) {
|
|
|
319
350
|
}
|
|
320
351
|
return new ManagedUndiciDispatcher(options.resolver, dispatcherOptions);
|
|
321
352
|
}
|
|
353
|
+
function reportClosedDispatchError(handler, error) {
|
|
354
|
+
const compatibleHandler = handler;
|
|
355
|
+
if (compatibleHandler.onResponseError !== undefined) {
|
|
356
|
+
compatibleHandler.onResponseError(null, error);
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
if (compatibleHandler.onError !== undefined) {
|
|
360
|
+
compatibleHandler.onError(error);
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
322
365
|
class ManagedUndiciDispatcher extends Dispatcher {
|
|
323
366
|
[PROXYLINE_DISPATCHER_BRAND] = true;
|
|
324
367
|
#directDispatcher;
|
|
@@ -334,11 +377,7 @@ class ManagedUndiciDispatcher extends Dispatcher {
|
|
|
334
377
|
}
|
|
335
378
|
dispatch(options, handler) {
|
|
336
379
|
if (this.#closedError !== undefined) {
|
|
337
|
-
|
|
338
|
-
throw this.#closedError;
|
|
339
|
-
}
|
|
340
|
-
handler.onError(this.#closedError);
|
|
341
|
-
return false;
|
|
380
|
+
return reportClosedDispatchError(handler, this.#closedError);
|
|
342
381
|
}
|
|
343
382
|
const url = resolveUndiciDispatchUrl(options);
|
|
344
383
|
const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
|
|
@@ -404,11 +443,7 @@ class AmbientUndiciDispatcher extends Dispatcher {
|
|
|
404
443
|
}
|
|
405
444
|
dispatch(options, handler) {
|
|
406
445
|
if (this.#closedError !== undefined) {
|
|
407
|
-
|
|
408
|
-
throw this.#closedError;
|
|
409
|
-
}
|
|
410
|
-
handler.onError(this.#closedError);
|
|
411
|
-
return false;
|
|
446
|
+
return reportClosedDispatchError(handler, this.#closedError);
|
|
412
447
|
}
|
|
413
448
|
const url = resolveUndiciDispatchUrl(options);
|
|
414
449
|
const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
|
package/dist/shared.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B,YAAmB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI/C;CACF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAErD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAO1D"}
|
package/docs/README.md
CHANGED
|
@@ -25,7 +25,8 @@ Process-global proxy routing for Node.js. Proxyline patches the network surfaces
|
|
|
25
25
|
|
|
26
26
|
- `http.request` / `http.get`: covered by global method patching and global agent replacement.
|
|
27
27
|
- `https.request` / `https.get`: covered by global method patching and global agent replacement.
|
|
28
|
-
- `fetch
|
|
28
|
+
- `globalThis.fetch`: covered by the fetch patch, including explicit dispatcher options and later Undici global dispatcher replacement in managed mode.
|
|
29
|
+
- Undici global dispatcher: installed for Undici APIs that read the current process dispatcher.
|
|
29
30
|
- WebSocket clients accepting a Node `agent`: covered with `proxy.createWebSocketAgent()`.
|
|
30
31
|
- WebSocket clients without an `agent` option: partially covered when the upgrade path reuses patched `http.request`.
|
|
31
32
|
- Explicit HTTP CONNECT sockets: covered with `openProxyConnectTunnel()`.
|
package/docs/api-reference.md
CHANGED
|
@@ -257,6 +257,7 @@ type OpenProxyConnectTunnelOptions = Readonly<{
|
|
|
257
257
|
targetHost: string;
|
|
258
258
|
targetPort: number;
|
|
259
259
|
timeoutMs?: number;
|
|
260
|
+
signal?: AbortSignal;
|
|
260
261
|
}>;
|
|
261
262
|
```
|
|
262
263
|
|
|
@@ -264,6 +265,7 @@ type OpenProxyConnectTunnelOptions = Readonly<{
|
|
|
264
265
|
- `proxyTls` — CA trust for HTTPS proxies. See [Proxy TLS](./proxy-tls.md).
|
|
265
266
|
- `targetHost` / `targetPort` — what to ask the proxy to connect to.
|
|
266
267
|
- `timeoutMs` — overall budget for the CONNECT handshake.
|
|
268
|
+
- `signal` — optional caller cancellation; aborting rejects the handshake and destroys its active proxy socket.
|
|
267
269
|
|
|
268
270
|
### `AmbientNodeProxyAgentOptions`
|
|
269
271
|
|
package/docs/getting-started.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Getting Started
|
|
2
2
|
|
|
3
|
-
Proxyline targets Node.js
|
|
3
|
+
Proxyline targets Node.js 22.19.0+. It is published as `@openclaw/proxyline` and ships ESM with TypeScript types.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
package/docs/security.md
CHANGED
|
@@ -30,7 +30,7 @@ Anything that does not flow through the patched APIs:
|
|
|
30
30
|
|
|
31
31
|
- Direct `net.connect` or `tls.connect` calls. Code that opens raw sockets is not seen by Proxyline.
|
|
32
32
|
- Native modules with private transport stacks (e.g. some database drivers, gRPC C bindings).
|
|
33
|
-
- Libraries that import and call `undici.fetch` directly with their own explicit `Dispatcher`. Proxyline's managed `globalThis.fetch`
|
|
33
|
+
- Libraries that import and call `undici.fetch` directly with their own explicit `Dispatcher`. Proxyline's managed `globalThis.fetch` pins its own dispatcher even when callers pass an explicit dispatcher or later replace the Undici global dispatcher, but it cannot rewrite every imported undici function reference.
|
|
34
34
|
- DNS resolution itself. Proxyline tells the proxy a hostname; DNS-based exfiltration via the local resolver is out of scope.
|
|
35
35
|
- Sockets opened **before** `installProxyline` ran. Existing keepalive connections continue to use whatever transport they were created with.
|
|
36
36
|
- Module references captured before `installProxyline` ran. Anything that stored `http.request` in a local variable at import time keeps the un-patched reference.
|
|
@@ -69,7 +69,7 @@ This is deliberate: two competing proxy patches would race on `http.request` and
|
|
|
69
69
|
| Threat | Mitigated | Notes |
|
|
70
70
|
| --- | --- | --- |
|
|
71
71
|
| Library passes a direct `http.Agent` per request | yes (managed) | Replaced before the request runs |
|
|
72
|
-
| Library passes a direct `Dispatcher` to managed `globalThis.fetch` | yes |
|
|
72
|
+
| Library passes a direct `Dispatcher` to managed `globalThis.fetch` | yes | Proxyline pins its dispatcher |
|
|
73
73
|
| Trusted local endpoint needs direct routing | yes, with `bypassPolicy` | Keep the callback narrow and auditable |
|
|
74
74
|
| Library calls imported `undici.fetch` with a direct `Dispatcher` | no | Imported function references are outside the global fetch patch |
|
|
75
75
|
| Library uses `net.connect` directly | no | Out of scope |
|
package/docs/surfaces.md
CHANGED
|
@@ -50,7 +50,7 @@ import { fetch } from "undici";
|
|
|
50
50
|
await fetch("https://api.example.com/health"); // routed through Proxyline
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
In managed mode, Proxyline's patched `globalThis.fetch`
|
|
53
|
+
In managed mode, Proxyline's patched `globalThis.fetch` passes Proxyline's own dispatcher explicitly, so a per-call undici `Agent` or a later `setGlobalDispatcher()` call cannot bypass the managed proxy through that global fetch path. In ambient mode, and for callers using imported `undici.fetch` directly, an explicit undici `Agent` or `Dispatcher` still wins. Use `proxy.createUndiciDispatcher()` to get a dispatcher pre-wired to the same policy.
|
|
54
54
|
|
|
55
55
|
Proxyline also replaces `globalThis.Request`, `Response`, `Headers`, and `FormData` with versions from its undici dependency so `globalThis.fetch` receives compatible objects on Node versions where the built-in fetch no longer shares the package dispatcher. Requests created before Proxyline installs are normalized through the standard public `Request` fields. Install Proxyline first if you need non-standard Request internals, such as a dispatcher embedded in a pre-install native `Request`, to be preserved.
|
|
56
56
|
|
|
@@ -83,6 +83,7 @@ const socket = await openProxyConnectTunnel({
|
|
|
83
83
|
targetHost: "api.example.com",
|
|
84
84
|
targetPort: 443,
|
|
85
85
|
timeoutMs: 2_000,
|
|
86
|
+
signal: AbortSignal.timeout(2_000),
|
|
86
87
|
});
|
|
87
88
|
```
|
|
88
89
|
|
|
@@ -93,6 +94,7 @@ Properties:
|
|
|
93
94
|
- Userinfo in the `proxyUrl` becomes a `Proxy-Authorization: Basic ...` header.
|
|
94
95
|
- A bounded `16 KiB` header buffer protects against malicious or runaway proxy responses.
|
|
95
96
|
- `timeoutMs` is enforced and emits a `CONNECT_FAILED` error on expiry.
|
|
97
|
+
- `signal` aborts an in-progress handshake and destroys its active proxy socket.
|
|
96
98
|
- Bytes the proxy sends after the response headers are re-injected with `socket.unshift()` so the caller sees the full target stream.
|
|
97
99
|
- Non-2xx status lines, header overrun, premature close, and socket errors are all surfaced as `ProxylineError` with code `CONNECT_FAILED`.
|
|
98
100
|
|
package/docs/testing.md
CHANGED
|
@@ -15,7 +15,7 @@ pnpm typecheck # type-only
|
|
|
15
15
|
|
|
16
16
|
`pnpm test` builds `dist/`, then runs `node --test` via `tsx` against `test/index.test.ts` (unit), `test/e2e.test.ts` (integration), and `test/package.test.ts` (package entrypoint).
|
|
17
17
|
`pnpm check` enforces native Node coverage for `src/**/*.ts` with thresholds of 85% lines, 80% branches, and 80% functions on Node versions that expose native threshold flags.
|
|
18
|
-
CI runs that check on Ubuntu, macOS, and Windows across Node
|
|
18
|
+
CI runs that check on Ubuntu, macOS, and Windows across Node 22.19.0, 24, and 26; Node 22.19.0 keeps the declared minimum covered with native coverage enabled.
|
|
19
19
|
|
|
20
20
|
## What the lab covers
|
|
21
21
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/proxyline",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Process-global proxy routing for Node.js.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"url": "https://github.com/openclaw/proxyline/issues"
|
|
18
18
|
},
|
|
19
19
|
"homepage": "https://proxyline.dev",
|
|
20
|
-
"packageManager": "pnpm@
|
|
20
|
+
"packageManager": "pnpm@11.9.0",
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
@@ -53,20 +53,26 @@
|
|
|
53
53
|
"prepare": "node scripts/prepack-build.mjs",
|
|
54
54
|
"publish:dry-run": "pnpm publish --dry-run --access public",
|
|
55
55
|
"test": "pnpm build && tsx --test test/index.test.ts test/e2e.test.ts test/package.test.ts test/scripts.test.ts && tsx --test test/package-artifact.test.ts",
|
|
56
|
-
"typecheck": "pnpm build && tsc --noEmit"
|
|
56
|
+
"typecheck": "pnpm build && tsc --noEmit",
|
|
57
|
+
"check:changed": "pnpm run check",
|
|
58
|
+
"test:changed": "pnpm run test",
|
|
59
|
+
"crabbox:hydrate": "crabbox actions hydrate",
|
|
60
|
+
"crabbox:run": "crabbox run",
|
|
61
|
+
"crabbox:stop": "crabbox stop",
|
|
62
|
+
"crabbox:warmup": "crabbox warmup"
|
|
57
63
|
},
|
|
58
64
|
"devDependencies": {
|
|
59
|
-
"@types/node": "
|
|
60
|
-
"@types/ws": "
|
|
61
|
-
"tsx": "
|
|
62
|
-
"typescript": "^
|
|
63
|
-
"undici": "
|
|
64
|
-
"ws": "
|
|
65
|
+
"@types/node": "26.1.1",
|
|
66
|
+
"@types/ws": "8.18.1",
|
|
67
|
+
"tsx": "4.23.0",
|
|
68
|
+
"typescript": "^7.0.2",
|
|
69
|
+
"undici": "8.6.0",
|
|
70
|
+
"ws": "8.21.0"
|
|
65
71
|
},
|
|
66
72
|
"engines": {
|
|
67
|
-
"node": ">=
|
|
73
|
+
"node": ">=22.19.0"
|
|
68
74
|
},
|
|
69
75
|
"peerDependencies": {
|
|
70
|
-
"undici": ">=
|
|
76
|
+
"undici": ">=8.5.0 <9"
|
|
71
77
|
}
|
|
72
78
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
4
6
|
|
|
5
7
|
const require = createRequire(import.meta.url);
|
|
6
8
|
|
|
7
9
|
function resolveTypeScriptCompiler() {
|
|
8
10
|
try {
|
|
9
|
-
|
|
11
|
+
const packageJsonPath = require.resolve("typescript/package.json");
|
|
12
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
13
|
+
const compiler = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.tsc;
|
|
14
|
+
return typeof compiler === "string" ? resolve(dirname(packageJsonPath), compiler) : undefined;
|
|
10
15
|
} catch {
|
|
11
16
|
return undefined;
|
|
12
17
|
}
|
package/src/connect.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type OpenProxyConnectTunnelOptions = Readonly<{
|
|
|
8
8
|
targetHost: string;
|
|
9
9
|
targetPort: number;
|
|
10
10
|
timeoutMs?: number;
|
|
11
|
+
signal?: AbortSignal;
|
|
11
12
|
}>;
|
|
12
13
|
|
|
13
14
|
const MAX_CONNECT_RESPONSE_HEADER_BYTES = 16 * 1024;
|
|
@@ -131,6 +132,7 @@ export async function openProxyConnectTunnel(
|
|
|
131
132
|
socket?.off("close", onClosed);
|
|
132
133
|
socket?.off("connect", onConnected);
|
|
133
134
|
socket?.off("secureConnect", onConnected);
|
|
135
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
134
136
|
};
|
|
135
137
|
|
|
136
138
|
const fail = (error: unknown): void => {
|
|
@@ -203,7 +205,16 @@ export async function openProxyConnectTunnel(
|
|
|
203
205
|
fail(new Error("proxy socket closed before CONNECT response"));
|
|
204
206
|
};
|
|
205
207
|
|
|
208
|
+
const onAbort = (): void => {
|
|
209
|
+
fail(options.signal?.reason ?? new Error("proxy CONNECT aborted"));
|
|
210
|
+
};
|
|
211
|
+
|
|
206
212
|
try {
|
|
213
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
214
|
+
if (options.signal?.aborted) {
|
|
215
|
+
onAbort();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
207
218
|
if (options.timeoutMs !== undefined && options.timeoutMs > 0) {
|
|
208
219
|
timeout = setTimeout(() => {
|
|
209
220
|
fail(new Error(`proxy CONNECT timed out after ${Math.trunc(options.timeoutMs ?? 0)}ms`));
|
package/src/runtime.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import { X509Certificate } from "node:crypto";
|
|
4
5
|
import net from "node:net";
|
|
6
|
+
import tls from "node:tls";
|
|
5
7
|
import {
|
|
6
8
|
Agent as UndiciAgent,
|
|
7
9
|
Dispatcher,
|
|
@@ -74,6 +76,11 @@ type ProxylineDispatcher = Dispatcher & {
|
|
|
74
76
|
[PROXYLINE_DISPATCHER_BRAND]?: true;
|
|
75
77
|
};
|
|
76
78
|
|
|
79
|
+
type CompatibleDispatchHandler = Dispatcher.DispatchHandler & {
|
|
80
|
+
onError?: (error: Error) => void;
|
|
81
|
+
onResponseError?: (controller: Dispatcher.DispatchController | null, error: Error) => void;
|
|
82
|
+
};
|
|
83
|
+
|
|
77
84
|
// Node's global fetch types come from bundled undici-types, while the runtime
|
|
78
85
|
// implementation intentionally delegates to this package's undici dependency.
|
|
79
86
|
const proxylineHeaders = UndiciHeaders as unknown as typeof globalThis.Headers;
|
|
@@ -216,28 +223,40 @@ async function normalizeFetchInput(
|
|
|
216
223
|
});
|
|
217
224
|
}
|
|
218
225
|
|
|
219
|
-
function
|
|
226
|
+
function withManagedFetchDispatcher(
|
|
220
227
|
init: Parameters<typeof globalThis.fetch>[1],
|
|
228
|
+
dispatcher: Dispatcher,
|
|
221
229
|
): Parameters<typeof globalThis.fetch>[1] {
|
|
222
|
-
if (
|
|
223
|
-
|
|
230
|
+
if (
|
|
231
|
+
init !== undefined &&
|
|
232
|
+
init !== null &&
|
|
233
|
+
typeof init !== "object" &&
|
|
234
|
+
typeof init !== "function"
|
|
235
|
+
) {
|
|
236
|
+
throw new TypeError(
|
|
237
|
+
`Request constructor: Expected ${String(init)} to be one of: Null, Undefined, Object.`,
|
|
238
|
+
);
|
|
224
239
|
}
|
|
225
|
-
const sanitized = Object.create(init);
|
|
240
|
+
const sanitized = init === undefined || init === null ? {} : Object.create(init);
|
|
226
241
|
Reflect.defineProperty(sanitized, "dispatcher", {
|
|
227
242
|
configurable: true,
|
|
228
243
|
enumerable: true,
|
|
229
|
-
value:
|
|
244
|
+
value: dispatcher,
|
|
230
245
|
writable: true,
|
|
231
246
|
});
|
|
232
247
|
return sanitized;
|
|
233
248
|
}
|
|
234
249
|
|
|
235
250
|
const proxylineFetch: typeof globalThis.fetch = async (input, init) => {
|
|
236
|
-
const
|
|
251
|
+
const managedDispatcher = activeRuntime?.mode === "managed"
|
|
252
|
+
? activeRuntime.installedDispatcher
|
|
253
|
+
: undefined;
|
|
237
254
|
const normalizedInput = await normalizeFetchInput(input, init, {
|
|
238
|
-
preserveDispatcher:
|
|
255
|
+
preserveDispatcher: managedDispatcher === undefined,
|
|
239
256
|
});
|
|
240
|
-
const normalizedInit =
|
|
257
|
+
const normalizedInit = managedDispatcher === undefined
|
|
258
|
+
? init
|
|
259
|
+
: withManagedFetchDispatcher(init, managedDispatcher);
|
|
241
260
|
const response: unknown = await Reflect.apply(
|
|
242
261
|
undiciFetch,
|
|
243
262
|
undefined,
|
|
@@ -466,6 +485,21 @@ function stripIpServernameFromConnect(connect: unknown): unknown {
|
|
|
466
485
|
(connect as UnknownFunction)(stripIpServernameFromConnectOptions(options), callback);
|
|
467
486
|
}
|
|
468
487
|
|
|
488
|
+
const checkProxyServerIdentity: typeof tls.checkServerIdentity = (hostname, certificate) => {
|
|
489
|
+
const normalizedHostname = hostname.replace(/^\[|\]$/g, "");
|
|
490
|
+
if (net.isIP(normalizedHostname) === 6) {
|
|
491
|
+
try {
|
|
492
|
+
// Compare binary addresses so compressed and expanded IPv6 SAN forms remain equivalent.
|
|
493
|
+
if (new X509Certificate(certificate.raw).checkIP(normalizedHostname) !== undefined) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
} catch {
|
|
497
|
+
// Let Node return its canonical certificate validation error below.
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return tls.checkServerIdentity(normalizedHostname, certificate);
|
|
501
|
+
};
|
|
502
|
+
|
|
469
503
|
function createProxyClientFactory(): UndiciProxyClientFactory {
|
|
470
504
|
return (origin: URL, options: object): Dispatcher => {
|
|
471
505
|
const clientOptions = isObjectRecord(options)
|
|
@@ -482,11 +516,19 @@ function createUndiciProxyAgent(
|
|
|
482
516
|
proxyUrl: string,
|
|
483
517
|
options: UndiciDispatcherOptions,
|
|
484
518
|
): UndiciProxyAgent {
|
|
519
|
+
const proxyHostname = new URL(proxyUrl).hostname.replace(/^\[|\]$/g, "");
|
|
520
|
+
const proxyTls =
|
|
521
|
+
options.proxyCa !== undefined || net.isIP(proxyHostname) === 6
|
|
522
|
+
? {
|
|
523
|
+
...(options.proxyCa !== undefined ? { ca: options.proxyCa } : {}),
|
|
524
|
+
...(net.isIP(proxyHostname) === 6 ? { checkServerIdentity: checkProxyServerIdentity } : {}),
|
|
525
|
+
}
|
|
526
|
+
: undefined;
|
|
485
527
|
return new UndiciProxyAgent({
|
|
486
528
|
...resolveUndiciBaseOptions(options.undici),
|
|
487
529
|
uri: proxyUrl,
|
|
488
530
|
clientFactory: createProxyClientFactory(),
|
|
489
|
-
...(
|
|
531
|
+
...(proxyTls !== undefined ? { proxyTls } : {}),
|
|
490
532
|
} as ConstructorParameters<typeof UndiciProxyAgent>[0]);
|
|
491
533
|
}
|
|
492
534
|
|
|
@@ -505,6 +547,22 @@ function createUndiciProxyDispatcher(
|
|
|
505
547
|
return new ManagedUndiciDispatcher(options.resolver, dispatcherOptions);
|
|
506
548
|
}
|
|
507
549
|
|
|
550
|
+
function reportClosedDispatchError(
|
|
551
|
+
handler: Dispatcher.DispatchHandler,
|
|
552
|
+
error: Error,
|
|
553
|
+
): boolean {
|
|
554
|
+
const compatibleHandler = handler as CompatibleDispatchHandler;
|
|
555
|
+
if (compatibleHandler.onResponseError !== undefined) {
|
|
556
|
+
compatibleHandler.onResponseError(null, error);
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
if (compatibleHandler.onError !== undefined) {
|
|
560
|
+
compatibleHandler.onError(error);
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
|
|
508
566
|
class ManagedUndiciDispatcher extends Dispatcher {
|
|
509
567
|
public readonly [PROXYLINE_DISPATCHER_BRAND] = true;
|
|
510
568
|
readonly #directDispatcher: UndiciAgent;
|
|
@@ -525,11 +583,7 @@ class ManagedUndiciDispatcher extends Dispatcher {
|
|
|
525
583
|
handler: Dispatcher.DispatchHandler,
|
|
526
584
|
): boolean {
|
|
527
585
|
if (this.#closedError !== undefined) {
|
|
528
|
-
|
|
529
|
-
throw this.#closedError;
|
|
530
|
-
}
|
|
531
|
-
handler.onError(this.#closedError);
|
|
532
|
-
return false;
|
|
586
|
+
return reportClosedDispatchError(handler, this.#closedError);
|
|
533
587
|
}
|
|
534
588
|
const url = resolveUndiciDispatchUrl(options);
|
|
535
589
|
const proxyUrl = url === undefined ? "" : this.#resolver.getProxyForUrl(url, "undici");
|
|
@@ -615,11 +669,7 @@ class AmbientUndiciDispatcher extends Dispatcher {
|
|
|
615
669
|
handler: Dispatcher.DispatchHandler,
|
|
616
670
|
): boolean {
|
|
617
671
|
if (this.#closedError !== undefined) {
|
|
618
|
-
|
|
619
|
-
throw this.#closedError;
|
|
620
|
-
}
|
|
621
|
-
handler.onError(this.#closedError);
|
|
622
|
-
return false;
|
|
672
|
+
return reportClosedDispatchError(handler, this.#closedError);
|
|
623
673
|
}
|
|
624
674
|
const url = resolveUndiciDispatchUrl(options);
|
|
625
675
|
const proxyUrl = url === undefined ? undefined : resolveAmbientProxyForUrl(url, this.#env);
|