@camstack/system 1.2.387 → 1.2.389

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.
@@ -87517,6 +87517,9 @@ var TlsTcpTransporter = class extends TcpTransporter {
87517
87517
  /** Node id (certificate CN) → serial of every peer that completed a mutual-TLS handshake. */
87518
87518
  tlsPeers = /* @__PURE__ */ new Map();
87519
87519
  clearDisabled = false;
87520
+ /** Clear-text `TcpWriter.connect` captured once before the TLS wrap (A→B). */
87521
+ clearWriterConnect = null;
87522
+ writerTlsPatched = false;
87520
87523
  constructor(opts = {}) {
87521
87524
  super({ ...opts });
87522
87525
  this.meshTls = opts.meshTls ?? null;
@@ -87539,20 +87542,43 @@ var TlsTcpTransporter = class extends TcpTransporter {
87539
87542
  }).then(() => this.onConnected());
87540
87543
  }
87541
87544
  /**
87542
- * Agent / outbound: wrap `TcpWriter.connect` so peers are dialed with
87543
- * mutual TLS and the peer CN must match the expected node id.
87545
+ * Outbound dial uses mTLS when the peer is already known on TLS (inbound
87546
+ * handshake), or when dialling the hub node id (agent → hub). Everyone else
87547
+ * (runners, unenrolled agents) stays on the clear writer — the hub must not
87548
+ * TLS-wrap every `connect` after deferred `enableMeshTls`.
87549
+ */
87550
+ shouldDialPeerWithTls(nodeID) {
87551
+ if (nodeID === "hub") return true;
87552
+ return this.tlsPeers.has(nodeID);
87553
+ }
87554
+ peerTlsDialPort() {
87555
+ return this.opts.tlsPort ?? 6443;
87556
+ }
87557
+ /**
87558
+ * Wrap `TcpWriter.connect` so TLS peers are dialed with mutual TLS on the
87559
+ * well-known mesh TLS port (default 6443). Idempotent: the clear connect is
87560
+ * captured once; material refresh rebinds the wrapper.
87544
87561
  */
87545
87562
  patchWriterForTlsClient() {
87546
- const material = this.meshTls;
87547
- if (material === null) return;
87563
+ if (this.meshTls === null) return;
87548
87564
  const writer = this.writer;
87549
87565
  if (writer === null || typeof writer.connect !== "function") return;
87566
+ if (!this.writerTlsPatched) {
87567
+ this.clearWriterConnect = writer.connect.bind(writer);
87568
+ this.writerTlsPatched = true;
87569
+ }
87570
+ const clearConnect = this.clearWriterConnect;
87571
+ if (clearConnect === null) return;
87550
87572
  const crl = this.crl;
87573
+ const self = this;
87551
87574
  writer.connect = (nodeID) => {
87575
+ if (!self.shouldDialPeerWithTls(nodeID)) return clearConnect(nodeID);
87576
+ const material = self.meshTls;
87577
+ if (material === null) return clearConnect(nodeID);
87552
87578
  const node = writer.transporter.getNode(nodeID);
87553
87579
  if (!node) return writer.Promise.reject(/* @__PURE__ */ new Error(`Missing node info for '${nodeID}'!`));
87554
87580
  const host = writer.transporter.getNodeAddress(node);
87555
- const port = node.port;
87581
+ const port = self.peerTlsDialPort();
87556
87582
  writer.logger.debug(`Connecting (TLS) to '${nodeID}' via ${host}:${port}`);
87557
87583
  return new writer.Promise((resolve, reject) => {
87558
87584
  const socket = node_tls.connect({
@@ -87581,6 +87607,11 @@ var TlsTcpTransporter = class extends TcpTransporter {
87581
87607
  if (identity) {
87582
87608
  tlsSock.meshPeerCn = identity.cn;
87583
87609
  tlsSock.meshPeerSerial = identity.serial;
87610
+ self.tlsPeers.set(identity.cn, identity.serial);
87611
+ self.tlsSockets.add(tlsSock);
87612
+ tlsSock.on("close", () => {
87613
+ self.tlsSockets.delete(tlsSock);
87614
+ });
87584
87615
  }
87585
87616
  writer.addSocket(nodeID, tlsSock, true);
87586
87617
  writer.logger.debug(`Connected successfully (TLS) to '${nodeID}'.`);
@@ -87603,11 +87634,13 @@ var TlsTcpTransporter = class extends TcpTransporter {
87603
87634
  }
87604
87635
  /**
87605
87636
  * Enable (or refresh) the TLS listener once the mesh CA / hub leaf are ready.
87606
- * Safe to call after connect — starts listening on `tlsPort` alongside clear.
87637
+ * Safe to call after connect — starts listening on `tlsPort` alongside clear
87638
+ * and patches the writer so hub→agent dials use mTLS (Release A→B).
87607
87639
  */
87608
87640
  async enableMeshTls(material) {
87609
87641
  this.meshTls = material;
87610
87642
  await this.startTlsServerIfReady();
87643
+ this.patchWriterForTlsClient();
87611
87644
  }
87612
87645
  /**
87613
87646
  * How `nodeId` reaches this transporter: `tls` once it completed a mutual-TLS
@@ -87508,6 +87508,9 @@ var TlsTcpTransporter = class extends TcpTransporter {
87508
87508
  /** Node id (certificate CN) → serial of every peer that completed a mutual-TLS handshake. */
87509
87509
  tlsPeers = /* @__PURE__ */ new Map();
87510
87510
  clearDisabled = false;
87511
+ /** Clear-text `TcpWriter.connect` captured once before the TLS wrap (A→B). */
87512
+ clearWriterConnect = null;
87513
+ writerTlsPatched = false;
87511
87514
  constructor(opts = {}) {
87512
87515
  super({ ...opts });
87513
87516
  this.meshTls = opts.meshTls ?? null;
@@ -87530,20 +87533,43 @@ var TlsTcpTransporter = class extends TcpTransporter {
87530
87533
  }).then(() => this.onConnected());
87531
87534
  }
87532
87535
  /**
87533
- * Agent / outbound: wrap `TcpWriter.connect` so peers are dialed with
87534
- * mutual TLS and the peer CN must match the expected node id.
87536
+ * Outbound dial uses mTLS when the peer is already known on TLS (inbound
87537
+ * handshake), or when dialling the hub node id (agent → hub). Everyone else
87538
+ * (runners, unenrolled agents) stays on the clear writer — the hub must not
87539
+ * TLS-wrap every `connect` after deferred `enableMeshTls`.
87540
+ */
87541
+ shouldDialPeerWithTls(nodeID) {
87542
+ if (nodeID === "hub") return true;
87543
+ return this.tlsPeers.has(nodeID);
87544
+ }
87545
+ peerTlsDialPort() {
87546
+ return this.opts.tlsPort ?? 6443;
87547
+ }
87548
+ /**
87549
+ * Wrap `TcpWriter.connect` so TLS peers are dialed with mutual TLS on the
87550
+ * well-known mesh TLS port (default 6443). Idempotent: the clear connect is
87551
+ * captured once; material refresh rebinds the wrapper.
87535
87552
  */
87536
87553
  patchWriterForTlsClient() {
87537
- const material = this.meshTls;
87538
- if (material === null) return;
87554
+ if (this.meshTls === null) return;
87539
87555
  const writer = this.writer;
87540
87556
  if (writer === null || typeof writer.connect !== "function") return;
87557
+ if (!this.writerTlsPatched) {
87558
+ this.clearWriterConnect = writer.connect.bind(writer);
87559
+ this.writerTlsPatched = true;
87560
+ }
87561
+ const clearConnect = this.clearWriterConnect;
87562
+ if (clearConnect === null) return;
87541
87563
  const crl = this.crl;
87564
+ const self = this;
87542
87565
  writer.connect = (nodeID) => {
87566
+ if (!self.shouldDialPeerWithTls(nodeID)) return clearConnect(nodeID);
87567
+ const material = self.meshTls;
87568
+ if (material === null) return clearConnect(nodeID);
87543
87569
  const node = writer.transporter.getNode(nodeID);
87544
87570
  if (!node) return writer.Promise.reject(/* @__PURE__ */ new Error(`Missing node info for '${nodeID}'!`));
87545
87571
  const host = writer.transporter.getNodeAddress(node);
87546
- const port = node.port;
87572
+ const port = self.peerTlsDialPort();
87547
87573
  writer.logger.debug(`Connecting (TLS) to '${nodeID}' via ${host}:${port}`);
87548
87574
  return new writer.Promise((resolve, reject) => {
87549
87575
  const socket = tls.connect({
@@ -87572,6 +87598,11 @@ var TlsTcpTransporter = class extends TcpTransporter {
87572
87598
  if (identity) {
87573
87599
  tlsSock.meshPeerCn = identity.cn;
87574
87600
  tlsSock.meshPeerSerial = identity.serial;
87601
+ self.tlsPeers.set(identity.cn, identity.serial);
87602
+ self.tlsSockets.add(tlsSock);
87603
+ tlsSock.on("close", () => {
87604
+ self.tlsSockets.delete(tlsSock);
87605
+ });
87575
87606
  }
87576
87607
  writer.addSocket(nodeID, tlsSock, true);
87577
87608
  writer.logger.debug(`Connected successfully (TLS) to '${nodeID}'.`);
@@ -87594,11 +87625,13 @@ var TlsTcpTransporter = class extends TcpTransporter {
87594
87625
  }
87595
87626
  /**
87596
87627
  * Enable (or refresh) the TLS listener once the mesh CA / hub leaf are ready.
87597
- * Safe to call after connect — starts listening on `tlsPort` alongside clear.
87628
+ * Safe to call after connect — starts listening on `tlsPort` alongside clear
87629
+ * and patches the writer so hub→agent dials use mTLS (Release A→B).
87598
87630
  */
87599
87631
  async enableMeshTls(material) {
87600
87632
  this.meshTls = material;
87601
87633
  await this.startTlsServerIfReady();
87634
+ this.patchWriterForTlsClient();
87602
87635
  }
87603
87636
  /**
87604
87637
  * How `nodeId` reaches this transporter: `tls` once it completed a mutual-TLS
package/dist/index.js CHANGED
@@ -11608,7 +11608,7 @@ function delay(ms, signal) {
11608
11608
  */
11609
11609
  async function callRegisterNodeWithRetry(broker, params, opts) {
11610
11610
  const action = opts.target === "hub" ? "$hub.registerNode" : "$agent.registerNode";
11611
- const callOpts = opts.target === "agent" && opts.targetNodeId ? { nodeID: opts.targetNodeId } : void 0;
11611
+ const callOpts = opts.target === "hub" ? { nodeID: "hub" } : opts.target === "agent" && opts.targetNodeId ? { nodeID: opts.targetNodeId } : void 0;
11612
11612
  const base = opts.baseDelayMs ?? 250;
11613
11613
  let attempt = 0;
11614
11614
  for (;;) {
package/dist/index.mjs CHANGED
@@ -11605,7 +11605,7 @@ function delay(ms, signal) {
11605
11605
  */
11606
11606
  async function callRegisterNodeWithRetry(broker, params, opts) {
11607
11607
  const action = opts.target === "hub" ? "$hub.registerNode" : "$agent.registerNode";
11608
- const callOpts = opts.target === "agent" && opts.targetNodeId ? { nodeID: opts.targetNodeId } : void 0;
11608
+ const callOpts = opts.target === "hub" ? { nodeID: "hub" } : opts.target === "agent" && opts.targetNodeId ? { nodeID: opts.targetNodeId } : void 0;
11609
11609
  const base = opts.baseDelayMs ?? 250;
11610
11610
  let attempt = 0;
11611
11611
  for (;;) {
@@ -77,17 +77,30 @@ export declare class TlsTcpTransporter extends TlsTcpTransporter_base {
77
77
  /** Node id (certificate CN) → serial of every peer that completed a mutual-TLS handshake. */
78
78
  private readonly tlsPeers;
79
79
  private clearDisabled;
80
+ /** Clear-text `TcpWriter.connect` captured once before the TLS wrap (A→B). */
81
+ private clearWriterConnect;
82
+ private writerTlsPatched;
80
83
  constructor(opts?: TlsTcpTransporterOptions);
81
84
  startTcpServer(): Promise<number>;
82
85
  connect(): Promise<void>;
83
86
  /**
84
- * Agent / outbound: wrap `TcpWriter.connect` so peers are dialed with
85
- * mutual TLS and the peer CN must match the expected node id.
87
+ * Outbound dial uses mTLS when the peer is already known on TLS (inbound
88
+ * handshake), or when dialling the hub node id (agent → hub). Everyone else
89
+ * (runners, unenrolled agents) stays on the clear writer — the hub must not
90
+ * TLS-wrap every `connect` after deferred `enableMeshTls`.
91
+ */
92
+ private shouldDialPeerWithTls;
93
+ private peerTlsDialPort;
94
+ /**
95
+ * Wrap `TcpWriter.connect` so TLS peers are dialed with mutual TLS on the
96
+ * well-known mesh TLS port (default 6443). Idempotent: the clear connect is
97
+ * captured once; material refresh rebinds the wrapper.
86
98
  */
87
99
  private patchWriterForTlsClient;
88
100
  /**
89
101
  * Enable (or refresh) the TLS listener once the mesh CA / hub leaf are ready.
90
- * Safe to call after connect — starts listening on `tlsPort` alongside clear.
102
+ * Safe to call after connect — starts listening on `tlsPort` alongside clear
103
+ * and patches the writer so hub→agent dials use mTLS (Release A→B).
91
104
  */
92
105
  enableMeshTls(material: MeshTlsMaterial): Promise<void>;
93
106
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.387",
3
+ "version": "1.2.389",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",