@openclaw/proxyline 0.3.3 → 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 CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ## 0.3.3 - 2026-05-18
4
10
 
5
11
  - Fixed managed `globalThis.fetch` so later Undici global dispatcher replacement cannot bypass the active Proxyline dispatcher. Thanks @jesse-merhi.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # 🌐 Proxyline
2
2
 
3
+ ![Proxyline banner](docs/assets/readme-banner.jpg)
4
+
3
5
  [![npm](https://img.shields.io/npm/v/%40openclaw%2Fproxyline.svg)](https://www.npmjs.com/package/@openclaw/proxyline)
4
6
  [![node](https://img.shields.io/node/v/%40openclaw%2Fproxyline.svg)](https://nodejs.org/)
5
7
  [![license](https://img.shields.io/npm/l/%40openclaw%2Fproxyline.svg)](./LICENSE)
@@ -31,7 +33,7 @@ pnpm add @openclaw/proxyline
31
33
  npm install @openclaw/proxyline
32
34
  ```
33
35
 
34
- Requires Node 22.19.0+ and a host `undici` dependency compatible with `>=8.3.0 <9`.
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
 
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;
@@ -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;CACpB,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,CA8GtB"}
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`));
@@ -1 +1 @@
1
- {"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../src/node-http.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAIV,eAAe,EACf,gBAAgB,EAIjB,MAAM,YAAY,CAAC;AA8xBpB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAgI3E;AAED,eAAO,MAAM,kBAAkB,yBAAmB,CAAC"}
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";
@@ -301,6 +303,21 @@ function stripIpServernameFromConnect(connect) {
301
303
  }
302
304
  return (options, callback) => connect(stripIpServernameFromConnectOptions(options), callback);
303
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
+ };
304
321
  function createProxyClientFactory() {
305
322
  return (origin, options) => {
306
323
  const clientOptions = isObjectRecord(options)
@@ -310,11 +327,18 @@ function createProxyClientFactory() {
310
327
  };
311
328
  }
312
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;
313
337
  return new UndiciProxyAgent({
314
338
  ...resolveUndiciBaseOptions(options.undici),
315
339
  uri: proxyUrl,
316
340
  clientFactory: createProxyClientFactory(),
317
- ...(options.proxyCa !== undefined ? { proxyTls: { ca: options.proxyCa } } : {}),
341
+ ...(proxyTls !== undefined ? { proxyTls } : {}),
318
342
  });
319
343
  }
320
344
  function createUndiciProxyDispatcher(options, dispatcherOptions) {
@@ -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;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAKjD;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"}
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"}
@@ -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/surfaces.md CHANGED
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/proxyline",
3
- "version": "0.3.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@11.1.2",
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": "25.8.0",
65
+ "@types/node": "26.1.1",
60
66
  "@types/ws": "8.18.1",
61
- "tsx": "4.22.1",
62
- "typescript": "6.0.3",
63
- "undici": "8.3.0",
64
- "ws": "8.20.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
73
  "node": ">=22.19.0"
68
74
  },
69
75
  "peerDependencies": {
70
- "undici": ">=8.3.0 <9"
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
- return require.resolve("typescript/bin/tsc");
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,
@@ -483,6 +485,21 @@ function stripIpServernameFromConnect(connect: unknown): unknown {
483
485
  (connect as UnknownFunction)(stripIpServernameFromConnectOptions(options), callback);
484
486
  }
485
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
+
486
503
  function createProxyClientFactory(): UndiciProxyClientFactory {
487
504
  return (origin: URL, options: object): Dispatcher => {
488
505
  const clientOptions = isObjectRecord(options)
@@ -499,11 +516,19 @@ function createUndiciProxyAgent(
499
516
  proxyUrl: string,
500
517
  options: UndiciDispatcherOptions,
501
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;
502
527
  return new UndiciProxyAgent({
503
528
  ...resolveUndiciBaseOptions(options.undici),
504
529
  uri: proxyUrl,
505
530
  clientFactory: createProxyClientFactory(),
506
- ...(options.proxyCa !== undefined ? { proxyTls: { ca: options.proxyCa } } : {}),
531
+ ...(proxyTls !== undefined ? { proxyTls } : {}),
507
532
  } as ConstructorParameters<typeof UndiciProxyAgent>[0]);
508
533
  }
509
534