claude-use 7.5.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.cjs +198 -90
- package/dist/index.cjs +117 -46
- package/dist/index.mjs +117 -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/tap.d.ts +8 -0
- package/dist/types/index.d.ts +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,8 +63,8 @@ Every top-level entry in `~/.claude` is classified into one of five categories,
|
|
|
63
63
|
|
|
64
64
|
| Category | Default shared? | Example entries |
|
|
65
65
|
|---|---|---|
|
|
66
|
-
| `secret` | **Never** — hardcoded, cannot be overridden by any configuration layer | `.credentials.json`, `backups` |
|
|
67
|
-
| `runtime` | No | `daemon*`, `.git*`, `.DS_Store`, `mcp-needs-auth-cache.json`, `shell-snapshots`, `statsig`, `telemetry`, `stats-cache.json`, `usage-data`, `ide`, `cache`, `scheduled_tasks.lock` |
|
|
66
|
+
| `secret` | **Never** — hardcoded, cannot be overridden by any configuration layer | `.credentials.json`, `backups`, `settings.json.bak*` |
|
|
67
|
+
| `runtime` | No | `daemon*`, `.git*`, `.DS_Store`, `mcp-needs-auth-cache.json`, `codex-quota.json`, `file-transfers`, `shell-snapshots`, `statsig`, `telemetry`, `stats-cache.json`, `usage-data`, `ide`, `cache`, `scheduled_tasks.lock` |
|
|
68
68
|
| `history` | Yes | `projects`, `sessions`, `session-env`, `teams`, `tasks`, `todos`, `history.jsonl`, `transcripts`, `paste-cache`, `file-history`, `plans`, `workflows`, `jobs`, `debug`, `downloads`, `chrome` |
|
|
69
69
|
| `knowledge` | Yes | `skills`, `agents`, `rules`, `memory`, `commands`, `plugins`, `hooks`, `AGENTS.md`, `CLAUDE.md`, `README.md` |
|
|
70
70
|
| `settings` | Yes | `settings.json`, `settings.local.json` |
|
package/dist/cli.cjs
CHANGED
|
@@ -8756,7 +8756,7 @@ var require_connect = __commonJS({
|
|
|
8756
8756
|
const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions);
|
|
8757
8757
|
timeout = timeout == null ? 1e4 : timeout;
|
|
8758
8758
|
allowH2 = allowH2 != null ? allowH2 : false;
|
|
8759
|
-
return function
|
|
8759
|
+
return function connect3({ hostname: hostname3, host, protocol, port, servername, localAddress, httpSocket }, callback) {
|
|
8760
8760
|
let socket;
|
|
8761
8761
|
if (protocol === "https:") {
|
|
8762
8762
|
if (!tls2) {
|
|
@@ -14554,7 +14554,7 @@ var require_client = __commonJS({
|
|
|
14554
14554
|
tls: tls2,
|
|
14555
14555
|
strictContentLength,
|
|
14556
14556
|
maxCachedSessions,
|
|
14557
|
-
connect:
|
|
14557
|
+
connect: connect4,
|
|
14558
14558
|
maxRequestsPerClient,
|
|
14559
14559
|
localAddress,
|
|
14560
14560
|
maxResponseSize,
|
|
@@ -14612,7 +14612,7 @@ var require_client = __commonJS({
|
|
|
14612
14612
|
if (bodyTimeout != null && (!Number.isInteger(bodyTimeout) || bodyTimeout < 0)) {
|
|
14613
14613
|
throw new InvalidArgumentError2("bodyTimeout must be a positive integer or zero");
|
|
14614
14614
|
}
|
|
14615
|
-
if (
|
|
14615
|
+
if (connect4 != null && typeof connect4 !== "function" && typeof connect4 !== "object") {
|
|
14616
14616
|
throw new InvalidArgumentError2("connect must be a function or an object");
|
|
14617
14617
|
}
|
|
14618
14618
|
if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
|
|
@@ -14646,8 +14646,8 @@ var require_client = __commonJS({
|
|
|
14646
14646
|
throw new InvalidArgumentError2("pingInterval must be a positive integer, greater or equal to 0");
|
|
14647
14647
|
}
|
|
14648
14648
|
super({ webSocket });
|
|
14649
|
-
if (typeof
|
|
14650
|
-
|
|
14649
|
+
if (typeof connect4 !== "function") {
|
|
14650
|
+
connect4 = buildConnector({
|
|
14651
14651
|
...tls2,
|
|
14652
14652
|
maxCachedSessions,
|
|
14653
14653
|
allowH2,
|
|
@@ -14655,18 +14655,18 @@ var require_client = __commonJS({
|
|
|
14655
14655
|
socketPath,
|
|
14656
14656
|
timeout: connectTimeout,
|
|
14657
14657
|
...typeof autoSelectFamily === "boolean" ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0,
|
|
14658
|
-
...
|
|
14658
|
+
...connect4
|
|
14659
14659
|
});
|
|
14660
14660
|
} else {
|
|
14661
|
-
const customConnect =
|
|
14662
|
-
|
|
14661
|
+
const customConnect = connect4;
|
|
14662
|
+
connect4 = (opts, callback) => customConnect({
|
|
14663
14663
|
...opts,
|
|
14664
14664
|
...socketPath != null ? { socketPath } : null,
|
|
14665
14665
|
...allowH2 != null ? { allowH2 } : null
|
|
14666
14666
|
}, callback);
|
|
14667
14667
|
}
|
|
14668
14668
|
this[kUrl] = util.parseOrigin(url2);
|
|
14669
|
-
this[kConnector] =
|
|
14669
|
+
this[kConnector] = connect4;
|
|
14670
14670
|
this[kPipelining] = pipelining != null ? pipelining : 1;
|
|
14671
14671
|
this[kMaxHeadersSize] = maxHeaderSize;
|
|
14672
14672
|
this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout;
|
|
@@ -14724,7 +14724,7 @@ var require_client = __commonJS({
|
|
|
14724
14724
|
);
|
|
14725
14725
|
}
|
|
14726
14726
|
[kConnect](cb) {
|
|
14727
|
-
|
|
14727
|
+
connect3(this);
|
|
14728
14728
|
this.once("connect", cb);
|
|
14729
14729
|
}
|
|
14730
14730
|
[kDispatch](opts, handler) {
|
|
@@ -14790,7 +14790,7 @@ var require_client = __commonJS({
|
|
|
14790
14790
|
assert2(client[kSize] === 0);
|
|
14791
14791
|
}
|
|
14792
14792
|
}
|
|
14793
|
-
function
|
|
14793
|
+
function connect3(client) {
|
|
14794
14794
|
assert2(!client[kConnecting]);
|
|
14795
14795
|
assert2(!client[kHTTPContext]);
|
|
14796
14796
|
let { host, hostname: hostname3, protocol, port } = client[kUrl];
|
|
@@ -14969,7 +14969,7 @@ var require_client = __commonJS({
|
|
|
14969
14969
|
return;
|
|
14970
14970
|
}
|
|
14971
14971
|
if (!client[kHTTPContext]) {
|
|
14972
|
-
|
|
14972
|
+
connect3(client);
|
|
14973
14973
|
return;
|
|
14974
14974
|
}
|
|
14975
14975
|
if (client[kHTTPContext].destroyed) {
|
|
@@ -15265,7 +15265,7 @@ var require_pool = __commonJS({
|
|
|
15265
15265
|
constructor(origin, {
|
|
15266
15266
|
connections,
|
|
15267
15267
|
factory = defaultFactory,
|
|
15268
|
-
connect:
|
|
15268
|
+
connect: connect3,
|
|
15269
15269
|
connectTimeout,
|
|
15270
15270
|
tls: tls2,
|
|
15271
15271
|
maxCachedSessions,
|
|
@@ -15282,24 +15282,24 @@ var require_pool = __commonJS({
|
|
|
15282
15282
|
if (typeof factory !== "function") {
|
|
15283
15283
|
throw new InvalidArgumentError2("factory must be a function.");
|
|
15284
15284
|
}
|
|
15285
|
-
if (
|
|
15285
|
+
if (connect3 != null && typeof connect3 !== "function" && typeof connect3 !== "object") {
|
|
15286
15286
|
throw new InvalidArgumentError2("connect must be a function or an object");
|
|
15287
15287
|
}
|
|
15288
|
-
if (typeof
|
|
15289
|
-
|
|
15288
|
+
if (typeof connect3 !== "function") {
|
|
15289
|
+
connect3 = buildConnector({
|
|
15290
15290
|
...tls2,
|
|
15291
15291
|
maxCachedSessions,
|
|
15292
15292
|
allowH2,
|
|
15293
15293
|
socketPath,
|
|
15294
15294
|
timeout: connectTimeout,
|
|
15295
15295
|
...typeof autoSelectFamily === "boolean" ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0,
|
|
15296
|
-
...
|
|
15296
|
+
...connect3
|
|
15297
15297
|
});
|
|
15298
15298
|
}
|
|
15299
15299
|
super(options);
|
|
15300
15300
|
this[kConnections] = connections || null;
|
|
15301
15301
|
this[kUrl] = util.parseOrigin(origin);
|
|
15302
|
-
this[kOptions] = { ...util.deepClone(options), connect:
|
|
15302
|
+
this[kOptions] = { ...util.deepClone(options), connect: connect3, allowH2, clientTtl, socketPath };
|
|
15303
15303
|
this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0;
|
|
15304
15304
|
this[kFactory] = factory;
|
|
15305
15305
|
this.on("connect", (origin2, targets) => {
|
|
@@ -15378,14 +15378,14 @@ var require_balanced_pool = __commonJS({
|
|
|
15378
15378
|
return new Pool(origin, opts);
|
|
15379
15379
|
}
|
|
15380
15380
|
var BalancedPool = class extends PoolBase {
|
|
15381
|
-
constructor(upstreams = [], { factory = defaultFactory, connect:
|
|
15381
|
+
constructor(upstreams = [], { factory = defaultFactory, connect: connect3, tls: tls2, ...opts } = {}) {
|
|
15382
15382
|
if (typeof factory !== "function") {
|
|
15383
15383
|
throw new InvalidArgumentError2("factory must be a function.");
|
|
15384
15384
|
}
|
|
15385
15385
|
super(opts);
|
|
15386
|
-
if (
|
|
15386
|
+
if (connect3 && typeof connect3 !== "function") connect3 = { ...connect3 };
|
|
15387
15387
|
if (tls2 && typeof tls2 !== "function") tls2 = { ...tls2 };
|
|
15388
|
-
this[kOptions] = { ...util.deepClone(opts), connect:
|
|
15388
|
+
this[kOptions] = { ...util.deepClone(opts), connect: connect3, tls: tls2 };
|
|
15389
15389
|
this[kOptions].interceptors = opts.interceptors ? { ...opts.interceptors } : void 0;
|
|
15390
15390
|
this[kIndex] = -1;
|
|
15391
15391
|
this[kCurrentWeight] = 0;
|
|
@@ -15518,7 +15518,7 @@ var require_round_robin_pool = __commonJS({
|
|
|
15518
15518
|
constructor(origin, {
|
|
15519
15519
|
connections,
|
|
15520
15520
|
factory = defaultFactory,
|
|
15521
|
-
connect:
|
|
15521
|
+
connect: connect3,
|
|
15522
15522
|
connectTimeout,
|
|
15523
15523
|
tls: tls2,
|
|
15524
15524
|
maxCachedSessions,
|
|
@@ -15535,24 +15535,24 @@ var require_round_robin_pool = __commonJS({
|
|
|
15535
15535
|
if (typeof factory !== "function") {
|
|
15536
15536
|
throw new InvalidArgumentError2("factory must be a function.");
|
|
15537
15537
|
}
|
|
15538
|
-
if (
|
|
15538
|
+
if (connect3 != null && typeof connect3 !== "function" && typeof connect3 !== "object") {
|
|
15539
15539
|
throw new InvalidArgumentError2("connect must be a function or an object");
|
|
15540
15540
|
}
|
|
15541
|
-
if (typeof
|
|
15542
|
-
|
|
15541
|
+
if (typeof connect3 !== "function") {
|
|
15542
|
+
connect3 = buildConnector({
|
|
15543
15543
|
...tls2,
|
|
15544
15544
|
maxCachedSessions,
|
|
15545
15545
|
allowH2,
|
|
15546
15546
|
socketPath,
|
|
15547
15547
|
timeout: connectTimeout,
|
|
15548
15548
|
...typeof autoSelectFamily === "boolean" ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0,
|
|
15549
|
-
...
|
|
15549
|
+
...connect3
|
|
15550
15550
|
});
|
|
15551
15551
|
}
|
|
15552
15552
|
super();
|
|
15553
15553
|
this[kConnections] = connections || null;
|
|
15554
15554
|
this[kUrl] = util.parseOrigin(origin);
|
|
15555
|
-
this[kOptions] = { ...util.deepClone(options), connect:
|
|
15555
|
+
this[kOptions] = { ...util.deepClone(options), connect: connect3, allowH2, clientTtl, socketPath };
|
|
15556
15556
|
this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0;
|
|
15557
15557
|
this[kFactory] = factory;
|
|
15558
15558
|
this[kIndex] = -1;
|
|
@@ -15626,21 +15626,21 @@ var require_agent = __commonJS({
|
|
|
15626
15626
|
return opts && opts.connections === 1 ? new Client(origin, opts) : new Pool(origin, opts);
|
|
15627
15627
|
}
|
|
15628
15628
|
var Agent6 = class extends DispatcherBase {
|
|
15629
|
-
constructor({ factory = defaultFactory, maxOrigins = Infinity, connect:
|
|
15629
|
+
constructor({ factory = defaultFactory, maxOrigins = Infinity, connect: connect3, ...options } = {}) {
|
|
15630
15630
|
if (typeof factory !== "function") {
|
|
15631
15631
|
throw new InvalidArgumentError2("factory must be a function.");
|
|
15632
15632
|
}
|
|
15633
|
-
if (
|
|
15633
|
+
if (connect3 != null && typeof connect3 !== "function" && typeof connect3 !== "object") {
|
|
15634
15634
|
throw new InvalidArgumentError2("connect must be a function or an object");
|
|
15635
15635
|
}
|
|
15636
15636
|
if (typeof maxOrigins !== "number" || Number.isNaN(maxOrigins) || maxOrigins <= 0) {
|
|
15637
15637
|
throw new InvalidArgumentError2("maxOrigins must be a number greater than 0");
|
|
15638
15638
|
}
|
|
15639
15639
|
super(options);
|
|
15640
|
-
if (
|
|
15641
|
-
|
|
15640
|
+
if (connect3 && typeof connect3 !== "function") {
|
|
15641
|
+
connect3 = { ...connect3 };
|
|
15642
15642
|
}
|
|
15643
|
-
this[kOptions] = { ...util.deepClone(options), maxOrigins, connect:
|
|
15643
|
+
this[kOptions] = { ...util.deepClone(options), maxOrigins, connect: connect3 };
|
|
15644
15644
|
this[kFactory] = factory;
|
|
15645
15645
|
this[kClients] = /* @__PURE__ */ new Map();
|
|
15646
15646
|
this[kOrigins] = /* @__PURE__ */ new Set();
|
|
@@ -16471,16 +16471,16 @@ var require_proxy_agent = __commonJS({
|
|
|
16471
16471
|
}
|
|
16472
16472
|
var Http1ProxyWrapper = class extends DispatcherBase {
|
|
16473
16473
|
#client;
|
|
16474
|
-
constructor(proxyUrl, { headers = {}, connect:
|
|
16474
|
+
constructor(proxyUrl, { headers = {}, connect: connect3, factory }) {
|
|
16475
16475
|
if (!proxyUrl) {
|
|
16476
16476
|
throw new InvalidArgumentError2("Proxy URL is mandatory");
|
|
16477
16477
|
}
|
|
16478
16478
|
super();
|
|
16479
16479
|
this[kProxyHeaders] = headers;
|
|
16480
16480
|
if (factory) {
|
|
16481
|
-
this.#client = factory(proxyUrl, { connect:
|
|
16481
|
+
this.#client = factory(proxyUrl, { connect: connect3 });
|
|
16482
16482
|
} else {
|
|
16483
|
-
this.#client = new Client(proxyUrl, { connect:
|
|
16483
|
+
this.#client = new Client(proxyUrl, { connect: connect3 });
|
|
16484
16484
|
}
|
|
16485
16485
|
}
|
|
16486
16486
|
[kDispatch](opts, handler) {
|
|
@@ -16541,7 +16541,7 @@ var require_proxy_agent = __commonJS({
|
|
|
16541
16541
|
} else if (username && password) {
|
|
16542
16542
|
this[kProxyHeaders]["proxy-authorization"] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString("base64")}`;
|
|
16543
16543
|
}
|
|
16544
|
-
const
|
|
16544
|
+
const connect3 = buildConnector({ ...opts.proxyTls });
|
|
16545
16545
|
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls });
|
|
16546
16546
|
const agentFactory = opts.factory || defaultAgentFactory;
|
|
16547
16547
|
const factory = (origin2, options) => {
|
|
@@ -16549,7 +16549,7 @@ var require_proxy_agent = __commonJS({
|
|
|
16549
16549
|
if (this[kProxy].protocol === "socks5:" || this[kProxy].protocol === "socks:") {
|
|
16550
16550
|
return new Socks5ProxyAgent(this[kProxy].uri, {
|
|
16551
16551
|
headers: this[kProxyHeaders],
|
|
16552
|
-
connect:
|
|
16552
|
+
connect: connect3,
|
|
16553
16553
|
factory: agentFactory,
|
|
16554
16554
|
username: opts.username || username,
|
|
16555
16555
|
password: opts.password || password,
|
|
@@ -16560,7 +16560,7 @@ var require_proxy_agent = __commonJS({
|
|
|
16560
16560
|
if (!this[kTunnelProxy] && protocol2 === "http:" && this[kProxy].protocol === "http:") {
|
|
16561
16561
|
return new Http1ProxyWrapper(this[kProxy].uri, {
|
|
16562
16562
|
headers: this[kProxyHeaders],
|
|
16563
|
-
connect:
|
|
16563
|
+
connect: connect3,
|
|
16564
16564
|
factory: agentFactory
|
|
16565
16565
|
});
|
|
16566
16566
|
}
|
|
@@ -16569,7 +16569,7 @@ var require_proxy_agent = __commonJS({
|
|
|
16569
16569
|
if (protocol === "socks5:" || protocol === "socks:") {
|
|
16570
16570
|
this[kClient] = null;
|
|
16571
16571
|
} else {
|
|
16572
|
-
this[kClient] = clientFactory(url2, { connect:
|
|
16572
|
+
this[kClient] = clientFactory(url2, { connect: connect3 });
|
|
16573
16573
|
}
|
|
16574
16574
|
this[kAgent] = new Agent6({
|
|
16575
16575
|
...opts,
|
|
@@ -18402,10 +18402,10 @@ var require_api_connect = __commonJS({
|
|
|
18402
18402
|
}
|
|
18403
18403
|
}
|
|
18404
18404
|
};
|
|
18405
|
-
function
|
|
18405
|
+
function connect3(opts, callback) {
|
|
18406
18406
|
if (callback === void 0) {
|
|
18407
18407
|
return new Promise((resolve, reject) => {
|
|
18408
|
-
|
|
18408
|
+
connect3.call(this, opts, (err, data) => {
|
|
18409
18409
|
return err ? reject(err) : resolve(data);
|
|
18410
18410
|
});
|
|
18411
18411
|
});
|
|
@@ -18422,7 +18422,7 @@ var require_api_connect = __commonJS({
|
|
|
18422
18422
|
queueMicrotask(() => callback(err, { opaque }));
|
|
18423
18423
|
}
|
|
18424
18424
|
}
|
|
18425
|
-
module2.exports =
|
|
18425
|
+
module2.exports = connect3;
|
|
18426
18426
|
}
|
|
18427
18427
|
});
|
|
18428
18428
|
|
|
@@ -57330,12 +57330,14 @@ var import_picomatch = __toESM(require_picomatch2(), 1);
|
|
|
57330
57330
|
|
|
57331
57331
|
// src/config/categories.default.json
|
|
57332
57332
|
var categories_default_default = {
|
|
57333
|
-
secret: [".credentials.json", "backups"],
|
|
57333
|
+
secret: [".credentials.json", "backups", "settings.json.bak*"],
|
|
57334
57334
|
runtime: [
|
|
57335
57335
|
"daemon*",
|
|
57336
57336
|
".git*",
|
|
57337
57337
|
".DS_Store",
|
|
57338
57338
|
"mcp-needs-auth-cache.json",
|
|
57339
|
+
"codex-quota.json",
|
|
57340
|
+
"file-transfers",
|
|
57339
57341
|
"shell-snapshots",
|
|
57340
57342
|
"statsig",
|
|
57341
57343
|
"telemetry",
|
|
@@ -61051,7 +61053,7 @@ function resolveLaunchIdentity(paths, cwd) {
|
|
|
61051
61053
|
// package.json
|
|
61052
61054
|
var package_default = {
|
|
61053
61055
|
name: "claude-use",
|
|
61054
|
-
version: "7.
|
|
61056
|
+
version: "7.6.0",
|
|
61055
61057
|
description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
61056
61058
|
license: "MIT",
|
|
61057
61059
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -64450,6 +64452,39 @@ function createFileCapture(file2, now = () => /* @__PURE__ */ new Date()) {
|
|
|
64450
64452
|
upgrade: (request6) => {
|
|
64451
64453
|
append({ kind: "upgrade", method: request6.method ?? "?", url: request6.url ?? "?", headers: redactHeaders(request6.headers) });
|
|
64452
64454
|
},
|
|
64455
|
+
tapStream: (host) => {
|
|
64456
|
+
nextId += 1;
|
|
64457
|
+
const id = nextId;
|
|
64458
|
+
append({ kind: "stream", id, host });
|
|
64459
|
+
const stored = { "client-to-server": 0, "server-to-client": 0 };
|
|
64460
|
+
const sequences = { "client-to-server": 0, "server-to-client": 0 };
|
|
64461
|
+
return {
|
|
64462
|
+
onChunk: (direction, chunk) => {
|
|
64463
|
+
sequences[direction] += 1;
|
|
64464
|
+
const budget = STREAM_LOG_CAP_BYTES - stored[direction];
|
|
64465
|
+
if (budget <= 0) {
|
|
64466
|
+
return;
|
|
64467
|
+
}
|
|
64468
|
+
const excerpt = chunk.subarray(0, Math.min(CHUNK_LOG_CAP_BYTES, budget));
|
|
64469
|
+
stored[direction] += excerpt.length;
|
|
64470
|
+
const text2 = excerpt.toString("utf8");
|
|
64471
|
+
const faithful = Buffer.from(text2, "utf8").equals(excerpt);
|
|
64472
|
+
append({
|
|
64473
|
+
kind: "stream-chunk",
|
|
64474
|
+
id,
|
|
64475
|
+
dir: direction,
|
|
64476
|
+
seq: sequences[direction],
|
|
64477
|
+
bytes: chunk.length,
|
|
64478
|
+
stored: excerpt.length,
|
|
64479
|
+
truncated: excerpt.length < chunk.length,
|
|
64480
|
+
...faithful ? { body: redactText(text2) } : { b64: excerpt.toString("base64") }
|
|
64481
|
+
});
|
|
64482
|
+
},
|
|
64483
|
+
onEnd: () => {
|
|
64484
|
+
append({ kind: "stream-end", id, clientToServerCapped: stored["client-to-server"] >= STREAM_LOG_CAP_BYTES, serverToClientCapped: stored["server-to-client"] >= STREAM_LOG_CAP_BYTES });
|
|
64485
|
+
}
|
|
64486
|
+
};
|
|
64487
|
+
},
|
|
64453
64488
|
observePassthrough: (request6) => {
|
|
64454
64489
|
nextId += 1;
|
|
64455
64490
|
const id = nextId;
|
|
@@ -64495,10 +64530,6 @@ function createFileCapture(file2, now = () => /* @__PURE__ */ new Date()) {
|
|
|
64495
64530
|
// src/frontdoor/connect.ts
|
|
64496
64531
|
var import_node_crypto10 = require("node:crypto");
|
|
64497
64532
|
var fs12 = __toESM(require("node:fs"), 1);
|
|
64498
|
-
var http = __toESM(require("node:http"), 1);
|
|
64499
|
-
var https = __toESM(require("node:https"), 1);
|
|
64500
|
-
var net3 = __toESM(require("node:net"), 1);
|
|
64501
|
-
var tls = __toESM(require("node:tls"), 1);
|
|
64502
64533
|
|
|
64503
64534
|
// src/frontdoor/x509.ts
|
|
64504
64535
|
var import_node_crypto9 = require("node:crypto");
|
|
@@ -64771,6 +64802,54 @@ function issueLeafCertificate(ca, names, notBefore, notAfter) {
|
|
|
64771
64802
|
return { certPem: toPem("CERTIFICATE", der), keyPem: keyPem(privateKey) };
|
|
64772
64803
|
}
|
|
64773
64804
|
|
|
64805
|
+
// src/frontdoor/tap.ts
|
|
64806
|
+
function pumpTapSession(secure, openUpstream, tap) {
|
|
64807
|
+
let upstream;
|
|
64808
|
+
let ended = false;
|
|
64809
|
+
const end = () => {
|
|
64810
|
+
if (!ended) {
|
|
64811
|
+
ended = true;
|
|
64812
|
+
tap?.onEnd();
|
|
64813
|
+
}
|
|
64814
|
+
};
|
|
64815
|
+
const drop = () => {
|
|
64816
|
+
end();
|
|
64817
|
+
secure.destroy();
|
|
64818
|
+
upstream?.destroy();
|
|
64819
|
+
};
|
|
64820
|
+
secure.once("close", () => {
|
|
64821
|
+
end();
|
|
64822
|
+
upstream?.destroy();
|
|
64823
|
+
});
|
|
64824
|
+
secure.once("error", drop);
|
|
64825
|
+
const clientAlpn = "alpnProtocol" in secure && typeof secure.alpnProtocol === "string" ? secure.alpnProtocol : void 0;
|
|
64826
|
+
void (async () => {
|
|
64827
|
+
try {
|
|
64828
|
+
upstream = await openUpstream(clientAlpn);
|
|
64829
|
+
} catch {
|
|
64830
|
+
drop();
|
|
64831
|
+
return;
|
|
64832
|
+
}
|
|
64833
|
+
if (secure.destroyed) {
|
|
64834
|
+
end();
|
|
64835
|
+
upstream.destroy();
|
|
64836
|
+
return;
|
|
64837
|
+
}
|
|
64838
|
+
if (tap !== void 0) {
|
|
64839
|
+
secure.on("data", (chunk) => {
|
|
64840
|
+
tap.onChunk("client-to-server", chunk);
|
|
64841
|
+
});
|
|
64842
|
+
upstream.on("data", (chunk) => {
|
|
64843
|
+
tap.onChunk("server-to-client", chunk);
|
|
64844
|
+
});
|
|
64845
|
+
}
|
|
64846
|
+
secure.pipe(upstream);
|
|
64847
|
+
upstream.pipe(secure);
|
|
64848
|
+
upstream.once("close", drop);
|
|
64849
|
+
upstream.once("error", drop);
|
|
64850
|
+
})();
|
|
64851
|
+
}
|
|
64852
|
+
|
|
64774
64853
|
// src/frontdoor/supervisor.ts
|
|
64775
64854
|
var FRONTDOOR_SUPERVISOR_STILL_RUNNING = -1;
|
|
64776
64855
|
var FRONTDOOR_POLL_MS = 1e3;
|
|
@@ -64805,23 +64884,23 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
64805
64884
|
}
|
|
64806
64885
|
lastPort = http5.port;
|
|
64807
64886
|
const previousStickyConnect = lastConnectPort;
|
|
64808
|
-
let
|
|
64887
|
+
let connect3;
|
|
64809
64888
|
try {
|
|
64810
|
-
|
|
64889
|
+
connect3 = await ports.startConnectListener(lastConnectPort);
|
|
64811
64890
|
} catch (error62) {
|
|
64812
64891
|
await http5.close();
|
|
64813
64892
|
return fail(`could not start the CONNECT surface: ${error62 instanceof Error ? error62.message : String(error62)}`);
|
|
64814
64893
|
}
|
|
64815
|
-
if (previousStickyConnect !== void 0 &&
|
|
64816
|
-
ports.log(`agent-shim frontdoor supervisor: sticky CONNECT port ${String(previousStickyConnect)} was occupied; moving to ${String(
|
|
64894
|
+
if (previousStickyConnect !== void 0 && connect3.port !== previousStickyConnect) {
|
|
64895
|
+
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)`);
|
|
64817
64896
|
}
|
|
64818
|
-
lastConnectPort =
|
|
64897
|
+
lastConnectPort = connect3.port;
|
|
64819
64898
|
const previousStickyDirect = lastDirectPort;
|
|
64820
64899
|
let direct;
|
|
64821
64900
|
try {
|
|
64822
64901
|
direct = await ports.startDirectListener(lastDirectPort);
|
|
64823
64902
|
} catch (error62) {
|
|
64824
|
-
await
|
|
64903
|
+
await connect3.close();
|
|
64825
64904
|
await http5.close();
|
|
64826
64905
|
return fail(`could not start the direct listener: ${error62 instanceof Error ? error62.message : String(error62)}`);
|
|
64827
64906
|
}
|
|
@@ -64829,9 +64908,9 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
64829
64908
|
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)`);
|
|
64830
64909
|
}
|
|
64831
64910
|
lastDirectPort = direct.port;
|
|
64832
|
-
writeFrontDoorState(fs18, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http5.port, connectPort:
|
|
64911
|
+
writeFrontDoorState(fs18, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http5.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
|
|
64833
64912
|
fs18.removeRecursive(paths.frontdoorLockFile);
|
|
64834
|
-
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http5.port)}, CONNECT surface on 127.0.0.1:${String(
|
|
64913
|
+
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http5.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
|
|
64835
64914
|
let idleSince;
|
|
64836
64915
|
let ticks = 0;
|
|
64837
64916
|
for (; ; ) {
|
|
@@ -64845,7 +64924,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
64845
64924
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
64846
64925
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
64847
64926
|
await direct.close();
|
|
64848
|
-
await
|
|
64927
|
+
await connect3.close();
|
|
64849
64928
|
await http5.close();
|
|
64850
64929
|
writeFrontDoorState(fs18, paths.frontdoorStateFile, sticky());
|
|
64851
64930
|
return 0;
|
|
@@ -64860,6 +64939,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
64860
64939
|
// src/frontdoor/connect.ts
|
|
64861
64940
|
var CONNECT_INTERCEPT_HOST = "api.anthropic.com";
|
|
64862
64941
|
var CONNECT_INTERCEPT_HOSTS = [CONNECT_INTERCEPT_HOST, "platform.claude.com"];
|
|
64942
|
+
var CONNECT_TAP_HOSTS = ["platform.claude.com"];
|
|
64863
64943
|
var ROUTED_PATH_PREFIX = "/v1/";
|
|
64864
64944
|
var HTTPS_PORT = 443;
|
|
64865
64945
|
var MAX_PORT = 65535;
|
|
@@ -64967,9 +65047,20 @@ function caParses(ca) {
|
|
|
64967
65047
|
return false;
|
|
64968
65048
|
}
|
|
64969
65049
|
}
|
|
65050
|
+
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
64970
65051
|
async function startConnectServer(config2, effects, preferredPort) {
|
|
64971
65052
|
const onUpgrade = config2.capture === void 0 ? void 0 : (request6) => config2.capture?.upgrade(request6);
|
|
64972
65053
|
const sessions = config2.interceptHosts.map((host) => {
|
|
65054
|
+
if (config2.tapHosts.includes(host)) {
|
|
65055
|
+
const tlsAcceptor2 = effects.createTlsAcceptor(
|
|
65056
|
+
config2.leafFor(host),
|
|
65057
|
+
(secure) => {
|
|
65058
|
+
pumpTapSession(secure, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), config2.capture?.tapStream?.(host));
|
|
65059
|
+
},
|
|
65060
|
+
TAP_ALPN_PROTOCOLS
|
|
65061
|
+
);
|
|
65062
|
+
return { host, tlsAcceptor: tlsAcceptor2 };
|
|
65063
|
+
}
|
|
64973
65064
|
const handler = (request6, response) => {
|
|
64974
65065
|
if (host === config2.routedHost && servedByPipeline(request6.url)) {
|
|
64975
65066
|
config2.serveRouted(request6, response);
|
|
@@ -64994,7 +65085,7 @@ async function startConnectServer(config2, effects, preferredPort) {
|
|
|
64994
65085
|
await listener.close();
|
|
64995
65086
|
for (const { httpSession, tlsAcceptor } of sessions) {
|
|
64996
65087
|
tlsAcceptor.close();
|
|
64997
|
-
httpSession
|
|
65088
|
+
httpSession?.close();
|
|
64998
65089
|
}
|
|
64999
65090
|
}
|
|
65000
65091
|
};
|
|
@@ -65170,6 +65261,40 @@ async function blindTunnel(socket, target, effects) {
|
|
|
65170
65261
|
socket.destroy();
|
|
65171
65262
|
});
|
|
65172
65263
|
}
|
|
65264
|
+
function isEnoent5(error62) {
|
|
65265
|
+
return typeof error62 === "object" && error62 !== null && "code" in error62 && error62.code === "ENOENT";
|
|
65266
|
+
}
|
|
65267
|
+
function realConnectCertStore(paths) {
|
|
65268
|
+
const read = (file2) => {
|
|
65269
|
+
try {
|
|
65270
|
+
return fs12.readFileSync(file2, "utf8");
|
|
65271
|
+
} catch (error62) {
|
|
65272
|
+
if (isEnoent5(error62)) {
|
|
65273
|
+
return void 0;
|
|
65274
|
+
}
|
|
65275
|
+
throw error62;
|
|
65276
|
+
}
|
|
65277
|
+
};
|
|
65278
|
+
return {
|
|
65279
|
+
loadCa: () => {
|
|
65280
|
+
const certPem = read(paths.frontdoorCaCertFile);
|
|
65281
|
+
const keyPem2 = read(paths.frontdoorCaKeyFile);
|
|
65282
|
+
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
65283
|
+
},
|
|
65284
|
+
writeCa: (ca) => {
|
|
65285
|
+
fs12.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
65286
|
+
fs12.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
65287
|
+
fs12.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
65288
|
+
fs12.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
65289
|
+
}
|
|
65290
|
+
};
|
|
65291
|
+
}
|
|
65292
|
+
|
|
65293
|
+
// src/frontdoor/connectEffects.ts
|
|
65294
|
+
var http = __toESM(require("node:http"), 1);
|
|
65295
|
+
var https = __toESM(require("node:https"), 1);
|
|
65296
|
+
var net3 = __toESM(require("node:net"), 1);
|
|
65297
|
+
var tls = __toESM(require("node:tls"), 1);
|
|
65173
65298
|
function realConnectEffects() {
|
|
65174
65299
|
const plainAgent = new http.Agent({ keepAlive: true });
|
|
65175
65300
|
const tlsAgent = new https.Agent({ keepAlive: true });
|
|
@@ -65215,10 +65340,13 @@ function realConnectEffects() {
|
|
|
65215
65340
|
}
|
|
65216
65341
|
return await listenOnce(0, onSocket);
|
|
65217
65342
|
},
|
|
65218
|
-
createTlsAcceptor: (leaf, onSecure) => {
|
|
65219
|
-
const server = tls.createServer(
|
|
65220
|
-
|
|
65221
|
-
|
|
65343
|
+
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
65344
|
+
const server = tls.createServer(
|
|
65345
|
+
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
65346
|
+
(secure) => {
|
|
65347
|
+
onSecure(secure);
|
|
65348
|
+
}
|
|
65349
|
+
);
|
|
65222
65350
|
server.on("clientError", (_error, socket) => {
|
|
65223
65351
|
socket.destroy();
|
|
65224
65352
|
});
|
|
@@ -65253,6 +65381,13 @@ function realConnectEffects() {
|
|
|
65253
65381
|
}
|
|
65254
65382
|
};
|
|
65255
65383
|
},
|
|
65384
|
+
connectTlsUpstream: async (host, port, clientAlpn) => await new Promise((resolve, reject) => {
|
|
65385
|
+
const socket = tls.connect({ host, port, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
|
|
65386
|
+
socket.once("secureConnect", () => {
|
|
65387
|
+
resolve(socket);
|
|
65388
|
+
});
|
|
65389
|
+
socket.once("error", reject);
|
|
65390
|
+
}),
|
|
65256
65391
|
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
65257
65392
|
const socket = net3.connect({ host, port });
|
|
65258
65393
|
socket.once("connect", () => {
|
|
@@ -65316,34 +65451,6 @@ function realConnectEffects() {
|
|
|
65316
65451
|
}
|
|
65317
65452
|
};
|
|
65318
65453
|
}
|
|
65319
|
-
function isEnoent5(error62) {
|
|
65320
|
-
return typeof error62 === "object" && error62 !== null && "code" in error62 && error62.code === "ENOENT";
|
|
65321
|
-
}
|
|
65322
|
-
function realConnectCertStore(paths) {
|
|
65323
|
-
const read = (file2) => {
|
|
65324
|
-
try {
|
|
65325
|
-
return fs12.readFileSync(file2, "utf8");
|
|
65326
|
-
} catch (error62) {
|
|
65327
|
-
if (isEnoent5(error62)) {
|
|
65328
|
-
return void 0;
|
|
65329
|
-
}
|
|
65330
|
-
throw error62;
|
|
65331
|
-
}
|
|
65332
|
-
};
|
|
65333
|
-
return {
|
|
65334
|
-
loadCa: () => {
|
|
65335
|
-
const certPem = read(paths.frontdoorCaCertFile);
|
|
65336
|
-
const keyPem2 = read(paths.frontdoorCaKeyFile);
|
|
65337
|
-
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
65338
|
-
},
|
|
65339
|
-
writeCa: (ca) => {
|
|
65340
|
-
fs12.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
65341
|
-
fs12.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
65342
|
-
fs12.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
65343
|
-
fs12.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
65344
|
-
}
|
|
65345
|
-
};
|
|
65346
|
-
}
|
|
65347
65454
|
|
|
65348
65455
|
// src/frontdoor/ensure.ts
|
|
65349
65456
|
var import_node_crypto11 = require("node:crypto");
|
|
@@ -66973,6 +67080,7 @@ function realFrontDoorSupervisorPorts(paths) {
|
|
|
66973
67080
|
const server = await startConnectServer(
|
|
66974
67081
|
{
|
|
66975
67082
|
interceptHosts: CONNECT_INTERCEPT_HOSTS,
|
|
67083
|
+
tapHosts: CONNECT_TAP_HOSTS,
|
|
66976
67084
|
routedHost: CONNECT_INTERCEPT_HOST,
|
|
66977
67085
|
serveRouted: (request6, response) => {
|
|
66978
67086
|
const abort = new AbortController();
|
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";
|
|
@@ -2307,23 +2351,23 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2307
2351
|
}
|
|
2308
2352
|
lastPort = http2.port;
|
|
2309
2353
|
const previousStickyConnect = lastConnectPort;
|
|
2310
|
-
let
|
|
2354
|
+
let connect3;
|
|
2311
2355
|
try {
|
|
2312
|
-
|
|
2356
|
+
connect3 = await ports.startConnectListener(lastConnectPort);
|
|
2313
2357
|
} catch (error) {
|
|
2314
2358
|
await http2.close();
|
|
2315
2359
|
return fail(`could not start the CONNECT surface: ${error instanceof Error ? error.message : String(error)}`);
|
|
2316
2360
|
}
|
|
2317
|
-
if (previousStickyConnect !== void 0 &&
|
|
2318
|
-
ports.log(`agent-shim frontdoor supervisor: sticky CONNECT port ${String(previousStickyConnect)} was occupied; moving to ${String(
|
|
2361
|
+
if (previousStickyConnect !== void 0 && connect3.port !== previousStickyConnect) {
|
|
2362
|
+
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
2363
|
}
|
|
2320
|
-
lastConnectPort =
|
|
2364
|
+
lastConnectPort = connect3.port;
|
|
2321
2365
|
const previousStickyDirect = lastDirectPort;
|
|
2322
2366
|
let direct;
|
|
2323
2367
|
try {
|
|
2324
2368
|
direct = await ports.startDirectListener(lastDirectPort);
|
|
2325
2369
|
} catch (error) {
|
|
2326
|
-
await
|
|
2370
|
+
await connect3.close();
|
|
2327
2371
|
await http2.close();
|
|
2328
2372
|
return fail(`could not start the direct listener: ${error instanceof Error ? error.message : String(error)}`);
|
|
2329
2373
|
}
|
|
@@ -2331,9 +2375,9 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2331
2375
|
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
2376
|
}
|
|
2333
2377
|
lastDirectPort = direct.port;
|
|
2334
|
-
writeFrontDoorState(fs3, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http2.port, connectPort:
|
|
2378
|
+
writeFrontDoorState(fs3, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http2.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
|
|
2335
2379
|
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(
|
|
2380
|
+
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
2381
|
let idleSince;
|
|
2338
2382
|
let ticks = 0;
|
|
2339
2383
|
for (; ; ) {
|
|
@@ -2347,7 +2391,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2347
2391
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
2348
2392
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2349
2393
|
await direct.close();
|
|
2350
|
-
await
|
|
2394
|
+
await connect3.close();
|
|
2351
2395
|
await http2.close();
|
|
2352
2396
|
writeFrontDoorState(fs3, paths.frontdoorStateFile, sticky());
|
|
2353
2397
|
return 0;
|
|
@@ -2471,9 +2515,20 @@ function caParses(ca) {
|
|
|
2471
2515
|
return false;
|
|
2472
2516
|
}
|
|
2473
2517
|
}
|
|
2518
|
+
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2474
2519
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2475
2520
|
const onUpgrade = config.capture === void 0 ? void 0 : (request3) => config.capture?.upgrade(request3);
|
|
2476
2521
|
const sessions = config.interceptHosts.map((host) => {
|
|
2522
|
+
if (config.tapHosts.includes(host)) {
|
|
2523
|
+
const tlsAcceptor2 = effects.createTlsAcceptor(
|
|
2524
|
+
config.leafFor(host),
|
|
2525
|
+
(secure) => {
|
|
2526
|
+
pumpTapSession(secure, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), config.capture?.tapStream?.(host));
|
|
2527
|
+
},
|
|
2528
|
+
TAP_ALPN_PROTOCOLS
|
|
2529
|
+
);
|
|
2530
|
+
return { host, tlsAcceptor: tlsAcceptor2 };
|
|
2531
|
+
}
|
|
2477
2532
|
const handler = (request3, response) => {
|
|
2478
2533
|
if (host === config.routedHost && servedByPipeline(request3.url)) {
|
|
2479
2534
|
config.serveRouted(request3, response);
|
|
@@ -2498,7 +2553,7 @@ async function startConnectServer(config, effects, preferredPort) {
|
|
|
2498
2553
|
await listener.close();
|
|
2499
2554
|
for (const { httpSession, tlsAcceptor } of sessions) {
|
|
2500
2555
|
tlsAcceptor.close();
|
|
2501
|
-
httpSession
|
|
2556
|
+
httpSession?.close();
|
|
2502
2557
|
}
|
|
2503
2558
|
}
|
|
2504
2559
|
};
|
|
@@ -2674,6 +2729,40 @@ async function blindTunnel(socket, target, effects) {
|
|
|
2674
2729
|
socket.destroy();
|
|
2675
2730
|
});
|
|
2676
2731
|
}
|
|
2732
|
+
function isEnoent(error) {
|
|
2733
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2734
|
+
}
|
|
2735
|
+
function realConnectCertStore(paths) {
|
|
2736
|
+
const read = (file) => {
|
|
2737
|
+
try {
|
|
2738
|
+
return fs.readFileSync(file, "utf8");
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
if (isEnoent(error)) {
|
|
2741
|
+
return void 0;
|
|
2742
|
+
}
|
|
2743
|
+
throw error;
|
|
2744
|
+
}
|
|
2745
|
+
};
|
|
2746
|
+
return {
|
|
2747
|
+
loadCa: () => {
|
|
2748
|
+
const certPem = read(paths.frontdoorCaCertFile);
|
|
2749
|
+
const keyPem2 = read(paths.frontdoorCaKeyFile);
|
|
2750
|
+
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2751
|
+
},
|
|
2752
|
+
writeCa: (ca) => {
|
|
2753
|
+
fs.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
2754
|
+
fs.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
2755
|
+
fs.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
2756
|
+
fs.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
2757
|
+
}
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
// src/frontdoor/connectEffects.ts
|
|
2762
|
+
var http = __toESM(require("node:http"), 1);
|
|
2763
|
+
var https = __toESM(require("node:https"), 1);
|
|
2764
|
+
var net2 = __toESM(require("node:net"), 1);
|
|
2765
|
+
var tls = __toESM(require("node:tls"), 1);
|
|
2677
2766
|
function realConnectEffects() {
|
|
2678
2767
|
const plainAgent = new http.Agent({ keepAlive: true });
|
|
2679
2768
|
const tlsAgent = new https.Agent({ keepAlive: true });
|
|
@@ -2719,10 +2808,13 @@ function realConnectEffects() {
|
|
|
2719
2808
|
}
|
|
2720
2809
|
return await listenOnce(0, onSocket);
|
|
2721
2810
|
},
|
|
2722
|
-
createTlsAcceptor: (leaf, onSecure) => {
|
|
2723
|
-
const server = tls.createServer(
|
|
2724
|
-
|
|
2725
|
-
|
|
2811
|
+
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
2812
|
+
const server = tls.createServer(
|
|
2813
|
+
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
2814
|
+
(secure) => {
|
|
2815
|
+
onSecure(secure);
|
|
2816
|
+
}
|
|
2817
|
+
);
|
|
2726
2818
|
server.on("clientError", (_error, socket) => {
|
|
2727
2819
|
socket.destroy();
|
|
2728
2820
|
});
|
|
@@ -2757,6 +2849,13 @@ function realConnectEffects() {
|
|
|
2757
2849
|
}
|
|
2758
2850
|
};
|
|
2759
2851
|
},
|
|
2852
|
+
connectTlsUpstream: async (host, port, clientAlpn) => await new Promise((resolve, reject) => {
|
|
2853
|
+
const socket = tls.connect({ host, port, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
|
|
2854
|
+
socket.once("secureConnect", () => {
|
|
2855
|
+
resolve(socket);
|
|
2856
|
+
});
|
|
2857
|
+
socket.once("error", reject);
|
|
2858
|
+
}),
|
|
2760
2859
|
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
2761
2860
|
const socket = net2.connect({ host, port });
|
|
2762
2861
|
socket.once("connect", () => {
|
|
@@ -2820,34 +2919,6 @@ function realConnectEffects() {
|
|
|
2820
2919
|
}
|
|
2821
2920
|
};
|
|
2822
2921
|
}
|
|
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
2922
|
|
|
2852
2923
|
// src/frontdoor/ensure.ts
|
|
2853
2924
|
var import_node_crypto5 = require("node:crypto");
|
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";
|
|
@@ -2207,23 +2251,23 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2207
2251
|
}
|
|
2208
2252
|
lastPort = http2.port;
|
|
2209
2253
|
const previousStickyConnect = lastConnectPort;
|
|
2210
|
-
let
|
|
2254
|
+
let connect3;
|
|
2211
2255
|
try {
|
|
2212
|
-
|
|
2256
|
+
connect3 = await ports.startConnectListener(lastConnectPort);
|
|
2213
2257
|
} catch (error) {
|
|
2214
2258
|
await http2.close();
|
|
2215
2259
|
return fail(`could not start the CONNECT surface: ${error instanceof Error ? error.message : String(error)}`);
|
|
2216
2260
|
}
|
|
2217
|
-
if (previousStickyConnect !== void 0 &&
|
|
2218
|
-
ports.log(`agent-shim frontdoor supervisor: sticky CONNECT port ${String(previousStickyConnect)} was occupied; moving to ${String(
|
|
2261
|
+
if (previousStickyConnect !== void 0 && connect3.port !== previousStickyConnect) {
|
|
2262
|
+
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
2263
|
}
|
|
2220
|
-
lastConnectPort =
|
|
2264
|
+
lastConnectPort = connect3.port;
|
|
2221
2265
|
const previousStickyDirect = lastDirectPort;
|
|
2222
2266
|
let direct;
|
|
2223
2267
|
try {
|
|
2224
2268
|
direct = await ports.startDirectListener(lastDirectPort);
|
|
2225
2269
|
} catch (error) {
|
|
2226
|
-
await
|
|
2270
|
+
await connect3.close();
|
|
2227
2271
|
await http2.close();
|
|
2228
2272
|
return fail(`could not start the direct listener: ${error instanceof Error ? error.message : String(error)}`);
|
|
2229
2273
|
}
|
|
@@ -2231,9 +2275,9 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2231
2275
|
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
2276
|
}
|
|
2233
2277
|
lastDirectPort = direct.port;
|
|
2234
|
-
writeFrontDoorState(fs3, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http2.port, connectPort:
|
|
2278
|
+
writeFrontDoorState(fs3, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http2.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
|
|
2235
2279
|
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(
|
|
2280
|
+
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
2281
|
let idleSince;
|
|
2238
2282
|
let ticks = 0;
|
|
2239
2283
|
for (; ; ) {
|
|
@@ -2247,7 +2291,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2247
2291
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
2248
2292
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2249
2293
|
await direct.close();
|
|
2250
|
-
await
|
|
2294
|
+
await connect3.close();
|
|
2251
2295
|
await http2.close();
|
|
2252
2296
|
writeFrontDoorState(fs3, paths.frontdoorStateFile, sticky());
|
|
2253
2297
|
return 0;
|
|
@@ -2371,9 +2415,20 @@ function caParses(ca) {
|
|
|
2371
2415
|
return false;
|
|
2372
2416
|
}
|
|
2373
2417
|
}
|
|
2418
|
+
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2374
2419
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2375
2420
|
const onUpgrade = config.capture === void 0 ? void 0 : (request3) => config.capture?.upgrade(request3);
|
|
2376
2421
|
const sessions = config.interceptHosts.map((host) => {
|
|
2422
|
+
if (config.tapHosts.includes(host)) {
|
|
2423
|
+
const tlsAcceptor2 = effects.createTlsAcceptor(
|
|
2424
|
+
config.leafFor(host),
|
|
2425
|
+
(secure) => {
|
|
2426
|
+
pumpTapSession(secure, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), config.capture?.tapStream?.(host));
|
|
2427
|
+
},
|
|
2428
|
+
TAP_ALPN_PROTOCOLS
|
|
2429
|
+
);
|
|
2430
|
+
return { host, tlsAcceptor: tlsAcceptor2 };
|
|
2431
|
+
}
|
|
2377
2432
|
const handler = (request3, response) => {
|
|
2378
2433
|
if (host === config.routedHost && servedByPipeline(request3.url)) {
|
|
2379
2434
|
config.serveRouted(request3, response);
|
|
@@ -2398,7 +2453,7 @@ async function startConnectServer(config, effects, preferredPort) {
|
|
|
2398
2453
|
await listener.close();
|
|
2399
2454
|
for (const { httpSession, tlsAcceptor } of sessions) {
|
|
2400
2455
|
tlsAcceptor.close();
|
|
2401
|
-
httpSession
|
|
2456
|
+
httpSession?.close();
|
|
2402
2457
|
}
|
|
2403
2458
|
}
|
|
2404
2459
|
};
|
|
@@ -2574,6 +2629,40 @@ async function blindTunnel(socket, target, effects) {
|
|
|
2574
2629
|
socket.destroy();
|
|
2575
2630
|
});
|
|
2576
2631
|
}
|
|
2632
|
+
function isEnoent(error) {
|
|
2633
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2634
|
+
}
|
|
2635
|
+
function realConnectCertStore(paths) {
|
|
2636
|
+
const read = (file) => {
|
|
2637
|
+
try {
|
|
2638
|
+
return fs.readFileSync(file, "utf8");
|
|
2639
|
+
} catch (error) {
|
|
2640
|
+
if (isEnoent(error)) {
|
|
2641
|
+
return void 0;
|
|
2642
|
+
}
|
|
2643
|
+
throw error;
|
|
2644
|
+
}
|
|
2645
|
+
};
|
|
2646
|
+
return {
|
|
2647
|
+
loadCa: () => {
|
|
2648
|
+
const certPem = read(paths.frontdoorCaCertFile);
|
|
2649
|
+
const keyPem2 = read(paths.frontdoorCaKeyFile);
|
|
2650
|
+
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2651
|
+
},
|
|
2652
|
+
writeCa: (ca) => {
|
|
2653
|
+
fs.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
2654
|
+
fs.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
2655
|
+
fs.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
2656
|
+
fs.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
2657
|
+
}
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
// src/frontdoor/connectEffects.ts
|
|
2662
|
+
import * as http from "node:http";
|
|
2663
|
+
import * as https from "node:https";
|
|
2664
|
+
import * as net2 from "node:net";
|
|
2665
|
+
import * as tls from "node:tls";
|
|
2577
2666
|
function realConnectEffects() {
|
|
2578
2667
|
const plainAgent = new http.Agent({ keepAlive: true });
|
|
2579
2668
|
const tlsAgent = new https.Agent({ keepAlive: true });
|
|
@@ -2619,10 +2708,13 @@ function realConnectEffects() {
|
|
|
2619
2708
|
}
|
|
2620
2709
|
return await listenOnce(0, onSocket);
|
|
2621
2710
|
},
|
|
2622
|
-
createTlsAcceptor: (leaf, onSecure) => {
|
|
2623
|
-
const server = tls.createServer(
|
|
2624
|
-
|
|
2625
|
-
|
|
2711
|
+
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
2712
|
+
const server = tls.createServer(
|
|
2713
|
+
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
2714
|
+
(secure) => {
|
|
2715
|
+
onSecure(secure);
|
|
2716
|
+
}
|
|
2717
|
+
);
|
|
2626
2718
|
server.on("clientError", (_error, socket) => {
|
|
2627
2719
|
socket.destroy();
|
|
2628
2720
|
});
|
|
@@ -2657,6 +2749,13 @@ function realConnectEffects() {
|
|
|
2657
2749
|
}
|
|
2658
2750
|
};
|
|
2659
2751
|
},
|
|
2752
|
+
connectTlsUpstream: async (host, port, clientAlpn) => await new Promise((resolve, reject) => {
|
|
2753
|
+
const socket = tls.connect({ host, port, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
|
|
2754
|
+
socket.once("secureConnect", () => {
|
|
2755
|
+
resolve(socket);
|
|
2756
|
+
});
|
|
2757
|
+
socket.once("error", reject);
|
|
2758
|
+
}),
|
|
2660
2759
|
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
2661
2760
|
const socket = net2.connect({ host, port });
|
|
2662
2761
|
socket.once("connect", () => {
|
|
@@ -2720,34 +2819,6 @@ function realConnectEffects() {
|
|
|
2720
2819
|
}
|
|
2721
2820
|
};
|
|
2722
2821
|
}
|
|
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
2822
|
|
|
2752
2823
|
// src/frontdoor/ensure.ts
|
|
2753
2824
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -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;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type * as net from "node:net";
|
|
2
|
+
import type { StreamTap } from "./capture";
|
|
3
|
+
/**
|
|
4
|
+
* Pumps one already-TLS-terminated stream byte for byte to its real host over a fresh TLS connection, teeing every chunk in both directions into `tap` when one is given. This is the instrumentation for a host whose protocol the surface does not parse: the claude.ai control plane carries Remote Control on a channel that mixes shapes (observed under capture: an HTTP/1.1-shaped token request first, then binary HTTP/2 frames around it), so parsing it as either protocol would destroy or distort it. Pass everything through unchanged and record, never interpret: the frames are decoded offline, and a protocol the door does not understand keeps working exactly as the blind tunnel served it.
|
|
5
|
+
*
|
|
6
|
+
* Lifecycle mirrors `blindTunnel`: whoever goes away first takes the other side with it, so neither half of a tap session ever outlives its peer. The tap's listeners ride beside the pipes, additive like every other observer in this surface, so a recording failure fails loudly rather than silently stopping a diagnostic.
|
|
7
|
+
*/
|
|
8
|
+
export declare function pumpTapSession(secure: net.Socket, openUpstream: (clientAlpn: string | undefined) => Promise<net.Socket>, tap: StreamTap | undefined): void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
export { resolveDecisions, topLevelNames, type ResolveDecisionsInput, type ResolvedState } from "./resolve/pipeline";
|
|
13
13
|
export type { CompiledRule, Decision, Diagnostic, EliminatedRule, EntryFact, EntryFacts, FlattenedCascade, Layer, LayerId } from "./resolve/types";
|
|
14
14
|
export { buildEntryFacts, carryOver, FARM_MANIFEST_FILENAME, readFarmManifest, recoverFarm, recoveryDiagnostics, resyncFarm, type BuildEntryFactsParams, type CarryOverParams, type CarryOverResult, type RecoverFarmParams, type RecoveryResult, type ResyncFarmParams, type ResyncFarmResult, } from "./launcher/farm";
|
|
15
|
-
export { CONNECT_INTERCEPT_HOST, CONNECT_INTERCEPT_HOSTS, CONNECT_LIMITS, createLeafCache, ensureCa, forwardableHeaders, generateCa, isInterceptedHost, mintLeaf, parseConnectTarget, realConnectCertStore,
|
|
15
|
+
export { CONNECT_INTERCEPT_HOST, CONNECT_INTERCEPT_HOSTS, CONNECT_LIMITS, createLeafCache, ensureCa, forwardableHeaders, generateCa, isInterceptedHost, mintLeaf, parseConnectTarget, realConnectCertStore, ROUTED_PATH_PREFIX, servedByPipeline, startConnectServer, type CaMaterial, type ConnectCertStore, type ConnectEffects, type ConnectLimits, type ConnectServerConfig, type ConnectServerHandle, type ConnectTarget, type LeafCert, } from "./frontdoor/connect";
|
|
16
|
+
export { realConnectEffects } from "./frontdoor/connectEffects";
|
|
16
17
|
export { ensureFrontDoor, FrontDoorStartError, type EnsureFrontDoorPorts } from "./frontdoor/ensure";
|
|
17
18
|
export { runFrontDoorSupervisor, type FrontDoorSupervisorPorts, type RunFrontDoorSupervisorOptions } from "./frontdoor/supervisor";
|
|
18
19
|
export { listUsageSnapshots, readUsageSnapshot, snapshotPath, UsageSnapshotError, type UsageReadFs } from "./usage/read";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-use",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.6.0",
|
|
4
4
|
"description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Joseph Mearman <joseph@mearman.co.uk>",
|