@botbuddy/cli 1.62.3 → 1.62.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
- "release_version": "1.62.3",
3
- "source_commit": "f360d97780f33ea528695c412fe995921def79e4",
4
- "source_sha256": "f51d8ebb381b6f6ed5cdcaa820cc37b4b464c87f4f83057336e7db52c57b5d26",
2
+ "release_version": "1.62.4",
3
+ "source_commit": "1cc371424a95b39915a3e882a428688bced7386a",
4
+ "source_sha256": "8d9dd0daf489c418b04d34b57696a6f97a9346d141f9266dda3a74e7ca87d4cf",
5
5
  "target": "darwin-arm64",
6
- "sha256": "5a19b567b8faf0e956bacaab84b5106cc79809fe0330924b4af1af91ce9c90f3"
6
+ "sha256": "4ce65712c580403552a1d15f9dce56149dfdf48d121c634a6f5c748cb48e85fd"
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "release_version": "1.62.3",
3
- "source_commit": "f360d97780f33ea528695c412fe995921def79e4",
4
- "source_sha256": "f51d8ebb381b6f6ed5cdcaa820cc37b4b464c87f4f83057336e7db52c57b5d26",
2
+ "release_version": "1.62.4",
3
+ "source_commit": "1cc371424a95b39915a3e882a428688bced7386a",
4
+ "source_sha256": "8d9dd0daf489c418b04d34b57696a6f97a9346d141f9266dda3a74e7ca87d4cf",
5
5
  "target": "linux-x64",
6
- "sha256": "89be3a9a5d26a7ba8ef5c7463c174be158ca7567efc0b2b736f3dbcdd50a1a0d"
6
+ "sha256": "4993e29705d75254afd21fadcbef568faf8650471df1bbcd9a96a6c5d3d2a254"
7
7
  }
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "release_version": "1.62.3",
4
- "source_commit": "f360d97780f33ea528695c412fe995921def79e4",
5
- "source_sha256": "f51d8ebb381b6f6ed5cdcaa820cc37b4b464c87f4f83057336e7db52c57b5d26",
3
+ "release_version": "1.62.4",
4
+ "source_commit": "1cc371424a95b39915a3e882a428688bced7386a",
5
+ "source_sha256": "8d9dd0daf489c418b04d34b57696a6f97a9346d141f9266dda3a74e7ca87d4cf",
6
6
  "targets": {
7
7
  "darwin-arm64": {
8
8
  "goos": "darwin",
9
9
  "goarch": "arm64",
10
10
  "binary": "daemon/bin/darwin-arm64/botbuddy-daemon",
11
- "sha256": "5a19b567b8faf0e956bacaab84b5106cc79809fe0330924b4af1af91ce9c90f3",
11
+ "sha256": "4ce65712c580403552a1d15f9dce56149dfdf48d121c634a6f5c748cb48e85fd",
12
12
  "mode": "0755"
13
13
  },
14
14
  "linux-x64": {
15
15
  "goos": "linux",
16
16
  "goarch": "amd64",
17
17
  "binary": "daemon/bin/linux-x64/botbuddy-daemon",
18
- "sha256": "89be3a9a5d26a7ba8ef5c7463c174be158ca7567efc0b2b736f3dbcdd50a1a0d",
18
+ "sha256": "4993e29705d75254afd21fadcbef568faf8650471df1bbcd9a96a6c5d3d2a254",
19
19
  "mode": "0755"
20
20
  }
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.62.3",
3
+ "version": "1.62.4",
4
4
  "description": "BotBuddy \u2014 Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  // Dependency-free (Node stdlib only).
23
23
 
24
24
  import { spawn, spawnSync } from "node:child_process";
25
- import { connect, createServer } from "node:net";
25
+ import { connect, createServer, isIP } from "node:net";
26
26
 
27
27
  export const SSH_FORWARD_OPTIONS = Object.freeze([
28
28
  "-N", "-T",
@@ -299,7 +299,7 @@ export async function openSshForwards({
299
299
  * `GatewayPorts yes` silently binds it on every interface, exposing this machine's
300
300
  * callback service to the host's network. The CLI cannot read sshd_config, so after the
301
301
  * forward is up it asks the host which address the listener really holds and refuses
302
- * anything but loopback (fail closed: no lsof, no listener, unreadable → refused).
302
+ * anything but loopback (fail closed: no inventory, no listener, unreadable → refused).
303
303
  */
304
304
  export function verifyCallbackLoopback({ sshBin, sshTarget, port, env = process.env, run = spawnSync, label = null }) {
305
305
  const what = label ?? `the reverse forward for --callback-port ${port}`;
@@ -307,18 +307,47 @@ export function verifyCallbackLoopback({ sshBin, sshTarget, port, env = process.
307
307
  `${what} on ${sshTarget} could not be proven loopback-only (${why}). ` +
308
308
  "An sshd with GatewayPorts yes binds -R forwards on every interface; set GatewayPorts no (or clientspecified) on the host, " +
309
309
  "or run without --callback-port.", extra);
310
+ const checkedPort = validPort(port);
311
+ if (checkedPort == null) throw fail("invalid listener port");
312
+ // Linux lsof cannot see root-owned sshd sockets from an unprivileged login (BOT-2743).
313
+ // ss reads the socket table without process visibility; -p is deliberately omitted.
314
+ // Dispatch on the remote OS in the SAME SSH invocation, with no guessed fallback.
315
+ const command = `case "$(uname -s)" in Linux) printf 'bb-ss-v1\\n'; ss -H -ltn '( sport = :${checkedPort} )';; ` +
316
+ `Darwin) lsof -nP -iTCP:${checkedPort} -sTCP:LISTEN -Fn;; ` +
317
+ "*) printf 'unsupported listener inventory platform\\n' >&2; exit 69;; esac";
310
318
  // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process -- fixed argv; target validated; integer port.
311
319
  const r = run(sshBin, [
312
320
  "-T", "-o", "BatchMode=yes", "-o", "ControlMaster=no", "-o", "ControlPath=none", "--", sshTarget,
313
- `lsof -nP -iTCP:${Number(port)} -sTCP:LISTEN -Fn`,
321
+ command,
314
322
  ], { encoding: "utf8", env, timeout: 15_000 });
315
323
  if (r.error || r.status !== 0) {
316
324
  const tail = String(r.stderr || r.error?.message || "").trim().split("\n").slice(-1)[0];
317
325
  // lsof exits 1 with no output when nothing listens.
318
- if (r.status === 1 && !String(r.stdout || "").trim()) throw fail(`no listener on the host's port ${port}`, { noListener: true });
319
- throw fail(`could not run lsof on the host (exit ${r.status ?? "?"}${tail ? `: ${tail}` : ""})`);
326
+ if (!r.error && r.status === 1 && !String(r.stdout || "").trim() && !String(r.stderr || "").trim()) {
327
+ throw fail(`no listener on the host's port ${port}`, { noListener: true });
328
+ }
329
+ throw fail(`could not inspect host sockets with ss/lsof (exit ${r.status ?? "?"}${tail ? `: ${tail}` : ""})`);
330
+ }
331
+ const output = String(r.stdout || "");
332
+ let addresses;
333
+ if (output.startsWith("bb-ss-v1\n")) {
334
+ addresses = output.slice("bb-ss-v1\n".length).split("\n").filter((line) => line.trim()).map((line) => {
335
+ // ss -H -ltn: State Recv-Q Send-Q Local-Address:Port Peer-Address:Port.
336
+ // Reject the whole answer on any bad row: silently skipping a wildcard is unsafe.
337
+ const fields = line.trim().split(/\s+/);
338
+ const local = /^(.+):(\d+)$/.exec(fields[3] ?? "");
339
+ let address = local?.[1];
340
+ if (address?.startsWith("[") && address.endsWith("]")) address = address.slice(1, -1);
341
+ const family = isIP(address ?? "");
342
+ if (fields.length !== 5 || fields[0] !== "LISTEN" || !/^\d+$/.test(fields[1]) || !/^\d+$/.test(fields[2]) ||
343
+ !local || Number(local[2]) !== checkedPort || (!family && address !== "*")) {
344
+ throw fail("malformed ss listener inventory");
345
+ }
346
+ return `${family === 6 ? `[${address}]` : address}:${checkedPort}`;
347
+ });
348
+ } else {
349
+ addresses = output.split("\n").filter((l) => l.startsWith("n")).map((l) => l.slice(1).trim());
320
350
  }
321
- const addresses = String(r.stdout || "").split("\n").filter((l) => l.startsWith("n")).map((l) => l.slice(1).trim());
322
351
  if (!addresses.length) throw fail(`no listener on the host's port ${port}`, { noListener: true });
323
352
  const exposed = addresses.filter((a) => !(a.startsWith("127.") || a.startsWith("[::1]:") || a.startsWith("localhost:")));
324
353
  if (exposed.length) throw fail(`the host listens on ${exposed.join(", ")}`, { exposed, listeners: addresses });