claude-use 7.5.0 → 7.6.1
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/README.md +2 -2
- package/dist/cli.cjs +218 -90
- package/dist/index.cjs +134 -46
- package/dist/index.mjs +134 -46
- package/dist/types/frontdoor/capture.d.ts +9 -0
- package/dist/types/frontdoor/connect.d.ts +14 -6
- package/dist/types/frontdoor/connectEffects.d.ts +3 -0
- package/dist/types/frontdoor/ensure.d.ts +2 -0
- package/dist/types/frontdoor/state.d.ts +7 -0
- package/dist/types/frontdoor/tap.d.ts +8 -0
- package/dist/types/index.d.ts +2 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1887,10 +1887,6 @@ function recoveryDiagnostics(recovery, identity) {
|
|
|
1887
1887
|
// src/frontdoor/connect.ts
|
|
1888
1888
|
var import_node_crypto3 = require("node:crypto");
|
|
1889
1889
|
var fs = __toESM(require("node:fs"), 1);
|
|
1890
|
-
var http = __toESM(require("node:http"), 1);
|
|
1891
|
-
var https = __toESM(require("node:https"), 1);
|
|
1892
|
-
var net2 = __toESM(require("node:net"), 1);
|
|
1893
|
-
var tls = __toESM(require("node:tls"), 1);
|
|
1894
1890
|
|
|
1895
1891
|
// src/frontdoor/x509.ts
|
|
1896
1892
|
var import_node_crypto2 = require("node:crypto");
|
|
@@ -2163,6 +2159,54 @@ function issueLeafCertificate(ca, names, notBefore, notAfter) {
|
|
|
2163
2159
|
return { certPem: toPem("CERTIFICATE", der), keyPem: keyPem(privateKey) };
|
|
2164
2160
|
}
|
|
2165
2161
|
|
|
2162
|
+
// src/frontdoor/tap.ts
|
|
2163
|
+
function pumpTapSession(secure, openUpstream, tap) {
|
|
2164
|
+
let upstream;
|
|
2165
|
+
let ended = false;
|
|
2166
|
+
const end = () => {
|
|
2167
|
+
if (!ended) {
|
|
2168
|
+
ended = true;
|
|
2169
|
+
tap?.onEnd();
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
const drop = () => {
|
|
2173
|
+
end();
|
|
2174
|
+
secure.destroy();
|
|
2175
|
+
upstream?.destroy();
|
|
2176
|
+
};
|
|
2177
|
+
secure.once("close", () => {
|
|
2178
|
+
end();
|
|
2179
|
+
upstream?.destroy();
|
|
2180
|
+
});
|
|
2181
|
+
secure.once("error", drop);
|
|
2182
|
+
const clientAlpn = "alpnProtocol" in secure && typeof secure.alpnProtocol === "string" ? secure.alpnProtocol : void 0;
|
|
2183
|
+
void (async () => {
|
|
2184
|
+
try {
|
|
2185
|
+
upstream = await openUpstream(clientAlpn);
|
|
2186
|
+
} catch {
|
|
2187
|
+
drop();
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
if (secure.destroyed) {
|
|
2191
|
+
end();
|
|
2192
|
+
upstream.destroy();
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
if (tap !== void 0) {
|
|
2196
|
+
secure.on("data", (chunk) => {
|
|
2197
|
+
tap.onChunk("client-to-server", chunk);
|
|
2198
|
+
});
|
|
2199
|
+
upstream.on("data", (chunk) => {
|
|
2200
|
+
tap.onChunk("server-to-client", chunk);
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
secure.pipe(upstream);
|
|
2204
|
+
upstream.pipe(secure);
|
|
2205
|
+
upstream.once("close", drop);
|
|
2206
|
+
upstream.once("error", drop);
|
|
2207
|
+
})();
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2166
2210
|
// src/frontdoor/capability.ts
|
|
2167
2211
|
var CONNECT_PROXY_REALM = "agent-shim front door";
|
|
2168
2212
|
var BASIC_SCHEME = "basic";
|
|
@@ -2184,8 +2228,13 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2184
2228
|
// src/frontdoor/state.ts
|
|
2185
2229
|
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
2186
2230
|
var import_zod3 = require("zod");
|
|
2231
|
+
var FRONT_DOOR_PROTOCOL = 1;
|
|
2187
2232
|
var FrontDoorSessionSchema = import_zod3.z.strictObject({ pid: import_zod3.z.number().int().positive(), startedAt: import_zod3.z.number(), token: import_zod3.z.string().min(1) });
|
|
2188
2233
|
var FrontDoorStateSchema = import_zod3.z.strictObject({
|
|
2234
|
+
/**
|
|
2235
|
+
* The launcher-to-door wire protocol this supervisor speaks, written once it is serving; absent in state written by a release that predates the field, which `ensureFrontDoor` reads as protocol 0. A launcher replaces a door whose protocol is lower than its own (see `FRONT_DOOR_PROTOCOL`).
|
|
2236
|
+
*/
|
|
2237
|
+
protocol: import_zod3.z.number().int().nonnegative().optional(),
|
|
2189
2238
|
/** The supervisor process serving the front door. Alive means both listeners are up in that process. */
|
|
2190
2239
|
supervisorPid: import_zod3.z.number().int().positive().optional(),
|
|
2191
2240
|
/** The loopback port the HTTPS provider listener serves on. Absent until the listener has bound and answered its health probe, so "port is set" is itself the ready signal a launcher polls for. */
|
|
@@ -2307,23 +2356,23 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2307
2356
|
}
|
|
2308
2357
|
lastPort = http2.port;
|
|
2309
2358
|
const previousStickyConnect = lastConnectPort;
|
|
2310
|
-
let
|
|
2359
|
+
let connect3;
|
|
2311
2360
|
try {
|
|
2312
|
-
|
|
2361
|
+
connect3 = await ports.startConnectListener(lastConnectPort);
|
|
2313
2362
|
} catch (error) {
|
|
2314
2363
|
await http2.close();
|
|
2315
2364
|
return fail(`could not start the CONNECT surface: ${error instanceof Error ? error.message : String(error)}`);
|
|
2316
2365
|
}
|
|
2317
|
-
if (previousStickyConnect !== void 0 &&
|
|
2318
|
-
ports.log(`agent-shim frontdoor supervisor: sticky CONNECT port ${String(previousStickyConnect)} was occupied; moving to ${String(
|
|
2366
|
+
if (previousStickyConnect !== void 0 && connect3.port !== previousStickyConnect) {
|
|
2367
|
+
ports.log(`agent-shim frontdoor supervisor: sticky CONNECT port ${String(previousStickyConnect)} was occupied; moving to ${String(connect3.port)} (OAuth sessions launched against the old port are stale until they relaunch)`);
|
|
2319
2368
|
}
|
|
2320
|
-
lastConnectPort =
|
|
2369
|
+
lastConnectPort = connect3.port;
|
|
2321
2370
|
const previousStickyDirect = lastDirectPort;
|
|
2322
2371
|
let direct;
|
|
2323
2372
|
try {
|
|
2324
2373
|
direct = await ports.startDirectListener(lastDirectPort);
|
|
2325
2374
|
} catch (error) {
|
|
2326
|
-
await
|
|
2375
|
+
await connect3.close();
|
|
2327
2376
|
await http2.close();
|
|
2328
2377
|
return fail(`could not start the direct listener: ${error instanceof Error ? error.message : String(error)}`);
|
|
2329
2378
|
}
|
|
@@ -2331,9 +2380,9 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2331
2380
|
ports.log(`agent-shim frontdoor supervisor: sticky direct port ${String(previousStickyDirect)} was occupied; moving to ${String(direct.port)} (headroom must be restarted to admit the new address)`);
|
|
2332
2381
|
}
|
|
2333
2382
|
lastDirectPort = direct.port;
|
|
2334
|
-
writeFrontDoorState(fs3, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http2.port, connectPort:
|
|
2383
|
+
writeFrontDoorState(fs3, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http2.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
|
|
2335
2384
|
fs3.removeRecursive(paths.frontdoorLockFile);
|
|
2336
|
-
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http2.port)}, CONNECT surface on 127.0.0.1:${String(
|
|
2385
|
+
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http2.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
|
|
2337
2386
|
let idleSince;
|
|
2338
2387
|
let ticks = 0;
|
|
2339
2388
|
for (; ; ) {
|
|
@@ -2347,7 +2396,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2347
2396
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
2348
2397
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2349
2398
|
await direct.close();
|
|
2350
|
-
await
|
|
2399
|
+
await connect3.close();
|
|
2351
2400
|
await http2.close();
|
|
2352
2401
|
writeFrontDoorState(fs3, paths.frontdoorStateFile, sticky());
|
|
2353
2402
|
return 0;
|
|
@@ -2471,9 +2520,20 @@ function caParses(ca) {
|
|
|
2471
2520
|
return false;
|
|
2472
2521
|
}
|
|
2473
2522
|
}
|
|
2523
|
+
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2474
2524
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2475
2525
|
const onUpgrade = config.capture === void 0 ? void 0 : (request3) => config.capture?.upgrade(request3);
|
|
2476
2526
|
const sessions = config.interceptHosts.map((host) => {
|
|
2527
|
+
if (config.tapHosts.includes(host)) {
|
|
2528
|
+
const tlsAcceptor2 = effects.createTlsAcceptor(
|
|
2529
|
+
config.leafFor(host),
|
|
2530
|
+
(secure) => {
|
|
2531
|
+
pumpTapSession(secure, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), config.capture?.tapStream?.(host));
|
|
2532
|
+
},
|
|
2533
|
+
TAP_ALPN_PROTOCOLS
|
|
2534
|
+
);
|
|
2535
|
+
return { host, tlsAcceptor: tlsAcceptor2 };
|
|
2536
|
+
}
|
|
2477
2537
|
const handler = (request3, response) => {
|
|
2478
2538
|
if (host === config.routedHost && servedByPipeline(request3.url)) {
|
|
2479
2539
|
config.serveRouted(request3, response);
|
|
@@ -2498,7 +2558,7 @@ async function startConnectServer(config, effects, preferredPort) {
|
|
|
2498
2558
|
await listener.close();
|
|
2499
2559
|
for (const { httpSession, tlsAcceptor } of sessions) {
|
|
2500
2560
|
tlsAcceptor.close();
|
|
2501
|
-
httpSession
|
|
2561
|
+
httpSession?.close();
|
|
2502
2562
|
}
|
|
2503
2563
|
}
|
|
2504
2564
|
};
|
|
@@ -2674,6 +2734,40 @@ async function blindTunnel(socket, target, effects) {
|
|
|
2674
2734
|
socket.destroy();
|
|
2675
2735
|
});
|
|
2676
2736
|
}
|
|
2737
|
+
function isEnoent(error) {
|
|
2738
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2739
|
+
}
|
|
2740
|
+
function realConnectCertStore(paths) {
|
|
2741
|
+
const read = (file) => {
|
|
2742
|
+
try {
|
|
2743
|
+
return fs.readFileSync(file, "utf8");
|
|
2744
|
+
} catch (error) {
|
|
2745
|
+
if (isEnoent(error)) {
|
|
2746
|
+
return void 0;
|
|
2747
|
+
}
|
|
2748
|
+
throw error;
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
return {
|
|
2752
|
+
loadCa: () => {
|
|
2753
|
+
const certPem = read(paths.frontdoorCaCertFile);
|
|
2754
|
+
const keyPem2 = read(paths.frontdoorCaKeyFile);
|
|
2755
|
+
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2756
|
+
},
|
|
2757
|
+
writeCa: (ca) => {
|
|
2758
|
+
fs.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
2759
|
+
fs.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
2760
|
+
fs.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
2761
|
+
fs.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// src/frontdoor/connectEffects.ts
|
|
2767
|
+
var http = __toESM(require("node:http"), 1);
|
|
2768
|
+
var https = __toESM(require("node:https"), 1);
|
|
2769
|
+
var net2 = __toESM(require("node:net"), 1);
|
|
2770
|
+
var tls = __toESM(require("node:tls"), 1);
|
|
2677
2771
|
function realConnectEffects() {
|
|
2678
2772
|
const plainAgent = new http.Agent({ keepAlive: true });
|
|
2679
2773
|
const tlsAgent = new https.Agent({ keepAlive: true });
|
|
@@ -2719,10 +2813,13 @@ function realConnectEffects() {
|
|
|
2719
2813
|
}
|
|
2720
2814
|
return await listenOnce(0, onSocket);
|
|
2721
2815
|
},
|
|
2722
|
-
createTlsAcceptor: (leaf, onSecure) => {
|
|
2723
|
-
const server = tls.createServer(
|
|
2724
|
-
|
|
2725
|
-
|
|
2816
|
+
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
2817
|
+
const server = tls.createServer(
|
|
2818
|
+
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
2819
|
+
(secure) => {
|
|
2820
|
+
onSecure(secure);
|
|
2821
|
+
}
|
|
2822
|
+
);
|
|
2726
2823
|
server.on("clientError", (_error, socket) => {
|
|
2727
2824
|
socket.destroy();
|
|
2728
2825
|
});
|
|
@@ -2757,6 +2854,13 @@ function realConnectEffects() {
|
|
|
2757
2854
|
}
|
|
2758
2855
|
};
|
|
2759
2856
|
},
|
|
2857
|
+
connectTlsUpstream: async (host, port, clientAlpn) => await new Promise((resolve, reject) => {
|
|
2858
|
+
const socket = tls.connect({ host, port, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
|
|
2859
|
+
socket.once("secureConnect", () => {
|
|
2860
|
+
resolve(socket);
|
|
2861
|
+
});
|
|
2862
|
+
socket.once("error", reject);
|
|
2863
|
+
}),
|
|
2760
2864
|
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
2761
2865
|
const socket = net2.connect({ host, port });
|
|
2762
2866
|
socket.once("connect", () => {
|
|
@@ -2820,34 +2924,6 @@ function realConnectEffects() {
|
|
|
2820
2924
|
}
|
|
2821
2925
|
};
|
|
2822
2926
|
}
|
|
2823
|
-
function isEnoent(error) {
|
|
2824
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2825
|
-
}
|
|
2826
|
-
function realConnectCertStore(paths) {
|
|
2827
|
-
const read = (file) => {
|
|
2828
|
-
try {
|
|
2829
|
-
return fs.readFileSync(file, "utf8");
|
|
2830
|
-
} catch (error) {
|
|
2831
|
-
if (isEnoent(error)) {
|
|
2832
|
-
return void 0;
|
|
2833
|
-
}
|
|
2834
|
-
throw error;
|
|
2835
|
-
}
|
|
2836
|
-
};
|
|
2837
|
-
return {
|
|
2838
|
-
loadCa: () => {
|
|
2839
|
-
const certPem = read(paths.frontdoorCaCertFile);
|
|
2840
|
-
const keyPem2 = read(paths.frontdoorCaKeyFile);
|
|
2841
|
-
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2842
|
-
},
|
|
2843
|
-
writeCa: (ca) => {
|
|
2844
|
-
fs.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
2845
|
-
fs.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
2846
|
-
fs.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
2847
|
-
fs.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
2848
|
-
}
|
|
2849
|
-
};
|
|
2850
|
-
}
|
|
2851
2927
|
|
|
2852
2928
|
// src/frontdoor/ensure.ts
|
|
2853
2929
|
var import_node_crypto5 = require("node:crypto");
|
|
@@ -2993,6 +3069,7 @@ function ensureFrontDoor(params) {
|
|
|
2993
3069
|
const { paths, ports } = params;
|
|
2994
3070
|
const deadline = ports.now() + FRONTDOOR_START_TIMEOUT_MS;
|
|
2995
3071
|
let spawned = false;
|
|
3072
|
+
let replaced = false;
|
|
2996
3073
|
const distrusted = /* @__PURE__ */ new Set();
|
|
2997
3074
|
let lastRejection;
|
|
2998
3075
|
for (; ; ) {
|
|
@@ -3001,6 +3078,17 @@ function ensureFrontDoor(params) {
|
|
|
3001
3078
|
if (supervisorPid !== void 0 && !distrusted.has(supervisorPid) && ports.isRunning(supervisorPid)) {
|
|
3002
3079
|
if (state?.port !== void 0 && state.connectPort !== void 0) {
|
|
3003
3080
|
const verdict = ports.verifyListener(state.port);
|
|
3081
|
+
if (verdict.ok && (state.protocol ?? 0) < FRONT_DOOR_PROTOCOL && !replaced) {
|
|
3082
|
+
ports.stopSupervisor(supervisorPid);
|
|
3083
|
+
while (ports.isRunning(supervisorPid)) {
|
|
3084
|
+
if (ports.now() >= deadline) {
|
|
3085
|
+
throw new FrontDoorStartError(`agent-shim: the front door (pid ${String(supervisorPid)}, protocol ${String(state.protocol ?? 0)}) is older than this launcher (protocol ${String(FRONT_DOOR_PROTOCOL)}) and did not exit when asked. Daemon log: ${paths.frontdoorLogPath}`);
|
|
3086
|
+
}
|
|
3087
|
+
ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
|
|
3088
|
+
}
|
|
3089
|
+
replaced = true;
|
|
3090
|
+
continue;
|
|
3091
|
+
}
|
|
3004
3092
|
if (verdict.ok) {
|
|
3005
3093
|
const token = (0, import_node_crypto5.randomUUID)();
|
|
3006
3094
|
writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
|
package/dist/index.mjs
CHANGED
|
@@ -1787,10 +1787,6 @@ function recoveryDiagnostics(recovery, identity) {
|
|
|
1787
1787
|
// src/frontdoor/connect.ts
|
|
1788
1788
|
import { X509Certificate as X509Certificate2, createPrivateKey as createPrivateKey2 } from "node:crypto";
|
|
1789
1789
|
import * as fs from "node:fs";
|
|
1790
|
-
import * as http from "node:http";
|
|
1791
|
-
import * as https from "node:https";
|
|
1792
|
-
import * as net2 from "node:net";
|
|
1793
|
-
import * as tls from "node:tls";
|
|
1794
1790
|
|
|
1795
1791
|
// src/frontdoor/x509.ts
|
|
1796
1792
|
import { createHash, createPrivateKey, generateKeyPairSync, randomBytes, sign, X509Certificate } from "node:crypto";
|
|
@@ -2063,6 +2059,54 @@ function issueLeafCertificate(ca, names, notBefore, notAfter) {
|
|
|
2063
2059
|
return { certPem: toPem("CERTIFICATE", der), keyPem: keyPem(privateKey) };
|
|
2064
2060
|
}
|
|
2065
2061
|
|
|
2062
|
+
// src/frontdoor/tap.ts
|
|
2063
|
+
function pumpTapSession(secure, openUpstream, tap) {
|
|
2064
|
+
let upstream;
|
|
2065
|
+
let ended = false;
|
|
2066
|
+
const end = () => {
|
|
2067
|
+
if (!ended) {
|
|
2068
|
+
ended = true;
|
|
2069
|
+
tap?.onEnd();
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
const drop = () => {
|
|
2073
|
+
end();
|
|
2074
|
+
secure.destroy();
|
|
2075
|
+
upstream?.destroy();
|
|
2076
|
+
};
|
|
2077
|
+
secure.once("close", () => {
|
|
2078
|
+
end();
|
|
2079
|
+
upstream?.destroy();
|
|
2080
|
+
});
|
|
2081
|
+
secure.once("error", drop);
|
|
2082
|
+
const clientAlpn = "alpnProtocol" in secure && typeof secure.alpnProtocol === "string" ? secure.alpnProtocol : void 0;
|
|
2083
|
+
void (async () => {
|
|
2084
|
+
try {
|
|
2085
|
+
upstream = await openUpstream(clientAlpn);
|
|
2086
|
+
} catch {
|
|
2087
|
+
drop();
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
if (secure.destroyed) {
|
|
2091
|
+
end();
|
|
2092
|
+
upstream.destroy();
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
if (tap !== void 0) {
|
|
2096
|
+
secure.on("data", (chunk) => {
|
|
2097
|
+
tap.onChunk("client-to-server", chunk);
|
|
2098
|
+
});
|
|
2099
|
+
upstream.on("data", (chunk) => {
|
|
2100
|
+
tap.onChunk("server-to-client", chunk);
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
secure.pipe(upstream);
|
|
2104
|
+
upstream.pipe(secure);
|
|
2105
|
+
upstream.once("close", drop);
|
|
2106
|
+
upstream.once("error", drop);
|
|
2107
|
+
})();
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2066
2110
|
// src/frontdoor/capability.ts
|
|
2067
2111
|
var CONNECT_PROXY_REALM = "agent-shim front door";
|
|
2068
2112
|
var BASIC_SCHEME = "basic";
|
|
@@ -2084,8 +2128,13 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2084
2128
|
// src/frontdoor/state.ts
|
|
2085
2129
|
import path5 from "node:path";
|
|
2086
2130
|
import { z as z3 } from "zod";
|
|
2131
|
+
var FRONT_DOOR_PROTOCOL = 1;
|
|
2087
2132
|
var FrontDoorSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number(), token: z3.string().min(1) });
|
|
2088
2133
|
var FrontDoorStateSchema = z3.strictObject({
|
|
2134
|
+
/**
|
|
2135
|
+
* The launcher-to-door wire protocol this supervisor speaks, written once it is serving; absent in state written by a release that predates the field, which `ensureFrontDoor` reads as protocol 0. A launcher replaces a door whose protocol is lower than its own (see `FRONT_DOOR_PROTOCOL`).
|
|
2136
|
+
*/
|
|
2137
|
+
protocol: z3.number().int().nonnegative().optional(),
|
|
2089
2138
|
/** The supervisor process serving the front door. Alive means both listeners are up in that process. */
|
|
2090
2139
|
supervisorPid: z3.number().int().positive().optional(),
|
|
2091
2140
|
/** The loopback port the HTTPS provider listener serves on. Absent until the listener has bound and answered its health probe, so "port is set" is itself the ready signal a launcher polls for. */
|
|
@@ -2207,23 +2256,23 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2207
2256
|
}
|
|
2208
2257
|
lastPort = http2.port;
|
|
2209
2258
|
const previousStickyConnect = lastConnectPort;
|
|
2210
|
-
let
|
|
2259
|
+
let connect3;
|
|
2211
2260
|
try {
|
|
2212
|
-
|
|
2261
|
+
connect3 = await ports.startConnectListener(lastConnectPort);
|
|
2213
2262
|
} catch (error) {
|
|
2214
2263
|
await http2.close();
|
|
2215
2264
|
return fail(`could not start the CONNECT surface: ${error instanceof Error ? error.message : String(error)}`);
|
|
2216
2265
|
}
|
|
2217
|
-
if (previousStickyConnect !== void 0 &&
|
|
2218
|
-
ports.log(`agent-shim frontdoor supervisor: sticky CONNECT port ${String(previousStickyConnect)} was occupied; moving to ${String(
|
|
2266
|
+
if (previousStickyConnect !== void 0 && connect3.port !== previousStickyConnect) {
|
|
2267
|
+
ports.log(`agent-shim frontdoor supervisor: sticky CONNECT port ${String(previousStickyConnect)} was occupied; moving to ${String(connect3.port)} (OAuth sessions launched against the old port are stale until they relaunch)`);
|
|
2219
2268
|
}
|
|
2220
|
-
lastConnectPort =
|
|
2269
|
+
lastConnectPort = connect3.port;
|
|
2221
2270
|
const previousStickyDirect = lastDirectPort;
|
|
2222
2271
|
let direct;
|
|
2223
2272
|
try {
|
|
2224
2273
|
direct = await ports.startDirectListener(lastDirectPort);
|
|
2225
2274
|
} catch (error) {
|
|
2226
|
-
await
|
|
2275
|
+
await connect3.close();
|
|
2227
2276
|
await http2.close();
|
|
2228
2277
|
return fail(`could not start the direct listener: ${error instanceof Error ? error.message : String(error)}`);
|
|
2229
2278
|
}
|
|
@@ -2231,9 +2280,9 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2231
2280
|
ports.log(`agent-shim frontdoor supervisor: sticky direct port ${String(previousStickyDirect)} was occupied; moving to ${String(direct.port)} (headroom must be restarted to admit the new address)`);
|
|
2232
2281
|
}
|
|
2233
2282
|
lastDirectPort = direct.port;
|
|
2234
|
-
writeFrontDoorState(fs3, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http2.port, connectPort:
|
|
2283
|
+
writeFrontDoorState(fs3, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http2.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
|
|
2235
2284
|
fs3.removeRecursive(paths.frontdoorLockFile);
|
|
2236
|
-
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http2.port)}, CONNECT surface on 127.0.0.1:${String(
|
|
2285
|
+
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http2.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
|
|
2237
2286
|
let idleSince;
|
|
2238
2287
|
let ticks = 0;
|
|
2239
2288
|
for (; ; ) {
|
|
@@ -2247,7 +2296,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2247
2296
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
2248
2297
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2249
2298
|
await direct.close();
|
|
2250
|
-
await
|
|
2299
|
+
await connect3.close();
|
|
2251
2300
|
await http2.close();
|
|
2252
2301
|
writeFrontDoorState(fs3, paths.frontdoorStateFile, sticky());
|
|
2253
2302
|
return 0;
|
|
@@ -2371,9 +2420,20 @@ function caParses(ca) {
|
|
|
2371
2420
|
return false;
|
|
2372
2421
|
}
|
|
2373
2422
|
}
|
|
2423
|
+
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2374
2424
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2375
2425
|
const onUpgrade = config.capture === void 0 ? void 0 : (request3) => config.capture?.upgrade(request3);
|
|
2376
2426
|
const sessions = config.interceptHosts.map((host) => {
|
|
2427
|
+
if (config.tapHosts.includes(host)) {
|
|
2428
|
+
const tlsAcceptor2 = effects.createTlsAcceptor(
|
|
2429
|
+
config.leafFor(host),
|
|
2430
|
+
(secure) => {
|
|
2431
|
+
pumpTapSession(secure, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), config.capture?.tapStream?.(host));
|
|
2432
|
+
},
|
|
2433
|
+
TAP_ALPN_PROTOCOLS
|
|
2434
|
+
);
|
|
2435
|
+
return { host, tlsAcceptor: tlsAcceptor2 };
|
|
2436
|
+
}
|
|
2377
2437
|
const handler = (request3, response) => {
|
|
2378
2438
|
if (host === config.routedHost && servedByPipeline(request3.url)) {
|
|
2379
2439
|
config.serveRouted(request3, response);
|
|
@@ -2398,7 +2458,7 @@ async function startConnectServer(config, effects, preferredPort) {
|
|
|
2398
2458
|
await listener.close();
|
|
2399
2459
|
for (const { httpSession, tlsAcceptor } of sessions) {
|
|
2400
2460
|
tlsAcceptor.close();
|
|
2401
|
-
httpSession
|
|
2461
|
+
httpSession?.close();
|
|
2402
2462
|
}
|
|
2403
2463
|
}
|
|
2404
2464
|
};
|
|
@@ -2574,6 +2634,40 @@ async function blindTunnel(socket, target, effects) {
|
|
|
2574
2634
|
socket.destroy();
|
|
2575
2635
|
});
|
|
2576
2636
|
}
|
|
2637
|
+
function isEnoent(error) {
|
|
2638
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2639
|
+
}
|
|
2640
|
+
function realConnectCertStore(paths) {
|
|
2641
|
+
const read = (file) => {
|
|
2642
|
+
try {
|
|
2643
|
+
return fs.readFileSync(file, "utf8");
|
|
2644
|
+
} catch (error) {
|
|
2645
|
+
if (isEnoent(error)) {
|
|
2646
|
+
return void 0;
|
|
2647
|
+
}
|
|
2648
|
+
throw error;
|
|
2649
|
+
}
|
|
2650
|
+
};
|
|
2651
|
+
return {
|
|
2652
|
+
loadCa: () => {
|
|
2653
|
+
const certPem = read(paths.frontdoorCaCertFile);
|
|
2654
|
+
const keyPem2 = read(paths.frontdoorCaKeyFile);
|
|
2655
|
+
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2656
|
+
},
|
|
2657
|
+
writeCa: (ca) => {
|
|
2658
|
+
fs.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
2659
|
+
fs.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
2660
|
+
fs.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
2661
|
+
fs.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// src/frontdoor/connectEffects.ts
|
|
2667
|
+
import * as http from "node:http";
|
|
2668
|
+
import * as https from "node:https";
|
|
2669
|
+
import * as net2 from "node:net";
|
|
2670
|
+
import * as tls from "node:tls";
|
|
2577
2671
|
function realConnectEffects() {
|
|
2578
2672
|
const plainAgent = new http.Agent({ keepAlive: true });
|
|
2579
2673
|
const tlsAgent = new https.Agent({ keepAlive: true });
|
|
@@ -2619,10 +2713,13 @@ function realConnectEffects() {
|
|
|
2619
2713
|
}
|
|
2620
2714
|
return await listenOnce(0, onSocket);
|
|
2621
2715
|
},
|
|
2622
|
-
createTlsAcceptor: (leaf, onSecure) => {
|
|
2623
|
-
const server = tls.createServer(
|
|
2624
|
-
|
|
2625
|
-
|
|
2716
|
+
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
2717
|
+
const server = tls.createServer(
|
|
2718
|
+
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
2719
|
+
(secure) => {
|
|
2720
|
+
onSecure(secure);
|
|
2721
|
+
}
|
|
2722
|
+
);
|
|
2626
2723
|
server.on("clientError", (_error, socket) => {
|
|
2627
2724
|
socket.destroy();
|
|
2628
2725
|
});
|
|
@@ -2657,6 +2754,13 @@ function realConnectEffects() {
|
|
|
2657
2754
|
}
|
|
2658
2755
|
};
|
|
2659
2756
|
},
|
|
2757
|
+
connectTlsUpstream: async (host, port, clientAlpn) => await new Promise((resolve, reject) => {
|
|
2758
|
+
const socket = tls.connect({ host, port, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
|
|
2759
|
+
socket.once("secureConnect", () => {
|
|
2760
|
+
resolve(socket);
|
|
2761
|
+
});
|
|
2762
|
+
socket.once("error", reject);
|
|
2763
|
+
}),
|
|
2660
2764
|
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
2661
2765
|
const socket = net2.connect({ host, port });
|
|
2662
2766
|
socket.once("connect", () => {
|
|
@@ -2720,34 +2824,6 @@ function realConnectEffects() {
|
|
|
2720
2824
|
}
|
|
2721
2825
|
};
|
|
2722
2826
|
}
|
|
2723
|
-
function isEnoent(error) {
|
|
2724
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2725
|
-
}
|
|
2726
|
-
function realConnectCertStore(paths) {
|
|
2727
|
-
const read = (file) => {
|
|
2728
|
-
try {
|
|
2729
|
-
return fs.readFileSync(file, "utf8");
|
|
2730
|
-
} catch (error) {
|
|
2731
|
-
if (isEnoent(error)) {
|
|
2732
|
-
return void 0;
|
|
2733
|
-
}
|
|
2734
|
-
throw error;
|
|
2735
|
-
}
|
|
2736
|
-
};
|
|
2737
|
-
return {
|
|
2738
|
-
loadCa: () => {
|
|
2739
|
-
const certPem = read(paths.frontdoorCaCertFile);
|
|
2740
|
-
const keyPem2 = read(paths.frontdoorCaKeyFile);
|
|
2741
|
-
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2742
|
-
},
|
|
2743
|
-
writeCa: (ca) => {
|
|
2744
|
-
fs.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
2745
|
-
fs.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
2746
|
-
fs.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
2747
|
-
fs.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
2748
|
-
}
|
|
2749
|
-
};
|
|
2750
|
-
}
|
|
2751
2827
|
|
|
2752
2828
|
// src/frontdoor/ensure.ts
|
|
2753
2829
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -2893,6 +2969,7 @@ function ensureFrontDoor(params) {
|
|
|
2893
2969
|
const { paths, ports } = params;
|
|
2894
2970
|
const deadline = ports.now() + FRONTDOOR_START_TIMEOUT_MS;
|
|
2895
2971
|
let spawned = false;
|
|
2972
|
+
let replaced = false;
|
|
2896
2973
|
const distrusted = /* @__PURE__ */ new Set();
|
|
2897
2974
|
let lastRejection;
|
|
2898
2975
|
for (; ; ) {
|
|
@@ -2901,6 +2978,17 @@ function ensureFrontDoor(params) {
|
|
|
2901
2978
|
if (supervisorPid !== void 0 && !distrusted.has(supervisorPid) && ports.isRunning(supervisorPid)) {
|
|
2902
2979
|
if (state?.port !== void 0 && state.connectPort !== void 0) {
|
|
2903
2980
|
const verdict = ports.verifyListener(state.port);
|
|
2981
|
+
if (verdict.ok && (state.protocol ?? 0) < FRONT_DOOR_PROTOCOL && !replaced) {
|
|
2982
|
+
ports.stopSupervisor(supervisorPid);
|
|
2983
|
+
while (ports.isRunning(supervisorPid)) {
|
|
2984
|
+
if (ports.now() >= deadline) {
|
|
2985
|
+
throw new FrontDoorStartError(`agent-shim: the front door (pid ${String(supervisorPid)}, protocol ${String(state.protocol ?? 0)}) is older than this launcher (protocol ${String(FRONT_DOOR_PROTOCOL)}) and did not exit when asked. Daemon log: ${paths.frontdoorLogPath}`);
|
|
2986
|
+
}
|
|
2987
|
+
ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
|
|
2988
|
+
}
|
|
2989
|
+
replaced = true;
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2904
2992
|
if (verdict.ok) {
|
|
2905
2993
|
const token = randomUUID2();
|
|
2906
2994
|
writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
|
|
@@ -20,8 +20,17 @@ export interface PassthroughObserver {
|
|
|
20
20
|
readonly onResponseChunk: (chunk: Buffer) => void;
|
|
21
21
|
readonly onEnd: () => void;
|
|
22
22
|
}
|
|
23
|
+
/** The direction one tapped byte chunk flowed, named from the session's point of view. */
|
|
24
|
+
type StreamDirection = "client-to-server" | "server-to-client";
|
|
25
|
+
/** The byte-level tap for one terminated stream whose protocol the surface does not parse: every chunk both directions, then the end. */
|
|
26
|
+
export interface StreamTap {
|
|
27
|
+
readonly onChunk: (direction: StreamDirection, chunk: Readonly<Buffer>) => void;
|
|
28
|
+
readonly onEnd: () => void;
|
|
29
|
+
}
|
|
23
30
|
/** The tap the connect surface drives. `undefined` everywhere means "not capturing"; one object covers the whole door process. */
|
|
24
31
|
export interface ConnectCapture {
|
|
32
|
+
/** Opens the byte-level tap for one host's terminated stream, whose chunks are recorded verbatim (text, or base64 when not valid UTF-8) under the same caps as piped bodies. Optional because a capture can be stream-silent by construction. */
|
|
33
|
+
readonly tapStream?: (host: string) => StreamTap;
|
|
25
34
|
/** Records one authenticated CONNECT target: the host and port only, since a blind tunnel's bytes are unreadable by construction. */
|
|
26
35
|
readonly connect: (target: {
|
|
27
36
|
readonly host: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
import * as net from "node:net";
|
|
2
|
+
import type * as net from "node:net";
|
|
3
3
|
import type { ConnectCapture, PassthroughObserver } from "./capture";
|
|
4
4
|
/**
|
|
5
5
|
* The front door's CONNECT surface, so an OAuth session can have agent-shim's routing AND Claude Code's Remote Control at once: Remote Control refuses any `ANTHROPIC_BASE_URL` other than the real API, but happily honours `HTTPS_PROXY`, so OAuth launches route at the proxy layer instead.
|
|
@@ -14,10 +14,16 @@ export declare const CONNECT_INTERCEPT_HOST = "api.anthropic.com";
|
|
|
14
14
|
* Every CONNECT host whose TLS this surface terminates, each with its own leaf and its own HTTP parser: the API host above, and `platform.claude.com`, the claude.ai control plane where Remote Control's long-lived channel and its handshake live. Terminating the second one is what makes a Remote Control login rejection observable at all: its bytes cross this surface either way, and blind-tunnelling them hides exactly the exchange that failed. Only the API host's `/v1/` paths ever reach the routed pipeline; everything else on either session is piped to that session's own real upstream, so the pipeline's identification and authorisation are never asked to serve another host's traffic.
|
|
15
15
|
*/
|
|
16
16
|
export declare const CONNECT_INTERCEPT_HOSTS: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* The intercept hosts the surface never parses as HTTP: terminated with ALPN for HTTP/2 and HTTP/1.1 both offered, pumped byte for byte to the real host over TLS (offering upstream only the protocol the client negotiated), and teed into the capture's stream tap when capturing is on. The claude.ai control plane's channel mixes an HTTP/1.1-shaped request with binary HTTP/2 frames, so parsing it as either protocol would destroy or distort it; tapping preserves the channel exactly as a blind tunnel served it while making its bytes observable.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CONNECT_TAP_HOSTS: readonly string[];
|
|
17
21
|
/** Paths under this prefix are what the routed pipeline serves; everything else on the terminated session goes to the real upstream. */
|
|
18
22
|
export declare const ROUTED_PATH_PREFIX = "/v1/";
|
|
19
23
|
/** The port HTTPS is served on, both the default when a CONNECT authority names none and the port non-headroom paths are piped to on the real upstream. */
|
|
20
24
|
export declare const HTTPS_PORT = 443;
|
|
25
|
+
/** What the forwarding effect answers with when the upstream is unreachable. */
|
|
26
|
+
export declare const HTTP_BAD_GATEWAY = 502;
|
|
21
27
|
/** Largest CONNECT request head accepted before the connection is refused: a real head is a request line and a few short headers (Host, the proxy credential, a User-Agent), so anything this size is a confused or hostile client. 8 KiB is also the per-header-line limit common HTTP servers apply by default (nginx's `large_client_header_buffers` buffer, Apache's `LimitRequestFieldSize` of 8190 bytes), so no client is built to need a longer line, and a CONNECT head is a few such lines at most. */
|
|
22
28
|
export declare const MAX_CONNECT_HEAD_BYTES = 8192;
|
|
23
29
|
/**
|
|
@@ -112,7 +118,7 @@ interface ConnectForwardTarget {
|
|
|
112
118
|
/** Handles one parsed HTTP request on a terminated TLS session. */
|
|
113
119
|
type ConnectRequestHandler = (request: IncomingMessage, response: ServerResponse) => void;
|
|
114
120
|
/** A bound loopback listener and how to stop it. */
|
|
115
|
-
interface ConnectListenerHandle {
|
|
121
|
+
export interface ConnectListenerHandle {
|
|
116
122
|
readonly port: number;
|
|
117
123
|
/** Stops accepting, ends every live connection, and resolves once the port is released. */
|
|
118
124
|
readonly close: () => Promise<void>;
|
|
@@ -135,8 +141,10 @@ interface HttpParserSession {
|
|
|
135
141
|
export interface ConnectEffects {
|
|
136
142
|
/** Binds a loopback TCP listener; `preferredPort` is tried first and any free port used when it is taken (bind, do not probe: check-then-bind races). Resolves with the bound port and a close handle. */
|
|
137
143
|
readonly listenLoopback: (preferredPort: number | undefined, onSocket: (socket: net.Socket) => void) => Promise<ConnectListenerHandle>;
|
|
138
|
-
/** Builds the TLS terminator for one leaf, handing each successfully handshaked session to `onSecure`. */
|
|
139
|
-
readonly createTlsAcceptor: (leaf: LeafCert, onSecure: (secure: net.Socket) => void) => TlsAcceptor;
|
|
144
|
+
/** Builds the TLS terminator for one leaf, handing each successfully handshaked session to `onSecure`. `alpnProtocols`, when given, is advertised during the handshake, which is how a tap host's session can offer HTTP/2 the way its real server does. */
|
|
145
|
+
readonly createTlsAcceptor: (leaf: LeafCert, onSecure: (secure: net.Socket) => void, alpnProtocols?: readonly string[]) => TlsAcceptor;
|
|
146
|
+
/** Opens a TLS connection to a real host, presenting its name as SNI, for a tap session's upstream half. `clientAlpn`, when given, is the protocol the client negotiated with the surface and the only one offered upstream, so the tap never changes the channel's protocol. */
|
|
147
|
+
readonly connectTlsUpstream: (host: string, port: number, clientAlpn: string | undefined) => Promise<net.Socket>;
|
|
140
148
|
/** Builds the HTTP parser bound to one request handler. `onUpgrade`, when given, sees every HTTP upgrade request arriving on a terminated session; the session destroys the socket afterwards either way, with or without one, matching Node's own no-listener behaviour. */
|
|
141
149
|
readonly createHttpSession: (handler: ConnectRequestHandler, onUpgrade?: (request: IncomingMessage) => void) => HttpParserSession;
|
|
142
150
|
/** Opens a raw TCP connection for a blind tunnel. */
|
|
@@ -162,6 +170,8 @@ export interface ConnectServerConfig {
|
|
|
162
170
|
readonly upstreamFor: (host: string) => ConnectForwardTarget;
|
|
163
171
|
/** Whether a capability presented as a CONNECT request's proxy credential belongs to a live launch: the same constant-time check against the same registry the routed pipeline's admission makes, read fresh on every call because launches come and go. */
|
|
164
172
|
readonly isLiveCapability: (token: string) => boolean;
|
|
173
|
+
/** The intercept hosts that are never HTTP-parsed: terminated, tapped at the byte level, and pumped to their real host. */
|
|
174
|
+
readonly tapHosts: readonly string[];
|
|
165
175
|
/** The connection deadlines and caps; production passes `CONNECT_LIMITS`. */
|
|
166
176
|
readonly limits: ConnectLimits;
|
|
167
177
|
/** The diagnostic tap, when capturing is enabled; absent means nothing is recorded. */
|
|
@@ -179,8 +189,6 @@ export interface ConnectServerHandle {
|
|
|
179
189
|
* The connection flow: read the CONNECT head within the deadline (never more of the stream than that, so an early ClientHello glued to the head is pushed back with `unshift` and still seen by whatever consumes the socket next), authenticate it, then either blind-tunnel the target or terminate its TLS and parse HTTP on the session. Every network effect flows through `effects`; nothing here touches the network or filesystem itself.
|
|
180
190
|
*/
|
|
181
191
|
export declare function startConnectServer(config: ConnectServerConfig, effects: ConnectEffects, preferredPort: number | undefined): Promise<ConnectServerHandle>;
|
|
182
|
-
/** The real `ConnectEffects` over node's own net, tls, and http. `connectTcp` opens real connections to the CONNECTed host, so tests that want a blind tunnel redirect it. */
|
|
183
|
-
export declare function realConnectEffects(): ConnectEffects;
|
|
184
192
|
/** The real `ConnectCertStore` on the filesystem: `ca.pem` world-readable (it is public material), `ca.key` mode 0600 exactly, chmod'ed after the write because a create's mode bits pass through the process umask. */
|
|
185
193
|
export declare function realConnectCertStore(paths: {
|
|
186
194
|
readonly frontdoorCaDir: string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type ConnectEffects } from "./connect";
|
|
2
|
+
/** The real `ConnectEffects` over node's own net, tls, and http. `connectTcp` opens real connections to the CONNECTed host, so tests that want a blind tunnel redirect it. */
|
|
3
|
+
export declare function realConnectEffects(): ConnectEffects;
|