@solongate/proxy 0.83.20 → 0.83.21

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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli-launch.ts
4
+ import { accessSync, constants, existsSync } from "fs";
5
+ import { spawnSync } from "child_process";
6
+ import { createRequire } from "module";
7
+ import { homedir } from "os";
8
+ import { dirname, join, resolve } from "path";
9
+ var GO_SUBCOMMANDS = /* @__PURE__ */ new Set([
10
+ "update",
11
+ "repair",
12
+ "logs-server",
13
+ "local-logs",
14
+ "policy",
15
+ "ratelimit",
16
+ "dlp",
17
+ "stats",
18
+ "audit",
19
+ "sessions",
20
+ "session",
21
+ "doctor",
22
+ "watch",
23
+ "alerts",
24
+ "webhooks",
25
+ "dataroom"
26
+ ]);
27
+ var GO_INFO_ARGS = /* @__PURE__ */ new Set(["login", "help", "--help", "-h", "--version", "-v", "version"]);
28
+ var argv = process.argv.slice(2);
29
+ var first = argv[0] ?? "";
30
+ var delegable = argv.length === 0 || GO_SUBCOMMANDS.has(first) || GO_INFO_ARGS.has(first);
31
+ function candidates() {
32
+ const out = [];
33
+ if (process.env.SOLONGATE_CLI_BIN) out.push(process.env.SOLONGATE_CLI_BIN);
34
+ const exe = process.platform === "win32" ? "solongate.exe" : "solongate";
35
+ const os_ = process.platform === "win32" ? "win32" : process.platform;
36
+ const cpu = process.arch === "x64" ? "x64" : process.arch;
37
+ try {
38
+ const req = createRequire(import.meta.url);
39
+ out.push(join(dirname(req.resolve(`@solongate/guard-${os_}-${cpu}/package.json`)), exe));
40
+ } catch {
41
+ }
42
+ out.push(resolve(homedir(), ".solongate", "bin", exe));
43
+ return out;
44
+ }
45
+ function runGo() {
46
+ for (const bin of candidates()) {
47
+ try {
48
+ if (!existsSync(bin)) continue;
49
+ if (process.platform !== "win32") accessSync(bin, constants.X_OK);
50
+ } catch {
51
+ continue;
52
+ }
53
+ const r = spawnSync(bin, argv, { stdio: "inherit" });
54
+ if (r.error || r.signal) return null;
55
+ if (r.status === 69) return null;
56
+ return r.status ?? 0;
57
+ }
58
+ return null;
59
+ }
60
+ if (delegable && process.env.SOLONGATE_NO_GO_CLI !== "1") {
61
+ let code = null;
62
+ try {
63
+ code = runGo();
64
+ } catch {
65
+ code = null;
66
+ }
67
+ if (code !== null) process.exit(code);
68
+ }
69
+ await import(new URL("./index.js", import.meta.url).href);
@@ -10,9 +10,10 @@ import { resolve as resolve2, join as join2 } from "path";
10
10
  import { homedir as homedir2 } from "os";
11
11
 
12
12
  // src/global-install.ts
13
- import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, rmdirSync, readdirSync, statSync, chmodSync } from "fs";
13
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, rmdirSync, readdirSync, statSync, chmodSync, copyFileSync, renameSync } from "fs";
14
14
  import { resolve, join, dirname } from "path";
15
15
  import { homedir } from "os";
16
+ import { createRequire } from "module";
16
17
  import { fileURLToPath } from "url";
17
18
  import { createInterface } from "readline";
18
19
  import { execFileSync, spawn } from "child_process";
@@ -26,10 +27,12 @@ function globalPaths() {
26
27
  const antigravityDir = join(home, ".gemini", "config");
27
28
  const codexDir = process.env["CODEX_HOME"] ? resolve(process.env["CODEX_HOME"]) : join(home, ".codex");
28
29
  const opencodeDir = join(process.env["XDG_CONFIG_HOME"] ? resolve(process.env["XDG_CONFIG_HOME"]) : join(home, ".config"), "opencode");
30
+ const binDir = join(sgDir, "bin");
29
31
  return {
30
32
  home,
31
33
  sgDir,
32
34
  hooksDir,
35
+ binDir,
33
36
  claudeDir,
34
37
  antigravityDir,
35
38
  codexDir,
@@ -305,12 +305,12 @@ export declare const PolicySetSchema: z.ZodObject<{
305
305
  createdAt: z.ZodString;
306
306
  updatedAt: z.ZodString;
307
307
  }, "strip", z.ZodTypeAny, {
308
+ version: number;
308
309
  name: string;
309
310
  id: string;
310
311
  description: string;
311
312
  createdAt: string;
312
313
  updatedAt: string;
313
- version: number;
314
314
  rules: {
315
315
  id: string;
316
316
  description: string;
@@ -343,12 +343,12 @@ export declare const PolicySetSchema: z.ZodObject<{
343
343
  } | undefined;
344
344
  }[];
345
345
  }, {
346
+ version: number;
346
347
  name: string;
347
348
  id: string;
348
349
  description: string;
349
350
  createdAt: string;
350
351
  updatedAt: string;
351
- version: number;
352
352
  rules: {
353
353
  id: string;
354
354
  description: string;
@@ -19,6 +19,7 @@ export declare function globalPaths(): {
19
19
  home: string;
20
20
  sgDir: string;
21
21
  hooksDir: string;
22
+ binDir: string;
22
23
  claudeDir: string;
23
24
  antigravityDir: string;
24
25
  codexDir: string;
@@ -1,7 +1,8 @@
1
1
  // src/global-install.ts
2
- import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, rmdirSync, readdirSync, statSync, chmodSync } from "fs";
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, rmdirSync, readdirSync, statSync, chmodSync, copyFileSync, renameSync } from "fs";
3
3
  import { resolve, join, dirname } from "path";
4
4
  import { homedir } from "os";
5
+ import { createRequire } from "module";
5
6
  import { fileURLToPath } from "url";
6
7
  import { createInterface } from "readline";
7
8
  import { execFileSync, spawn } from "child_process";
@@ -126,10 +127,12 @@ function globalPaths() {
126
127
  const antigravityDir = join(home, ".gemini", "config");
127
128
  const codexDir = process.env["CODEX_HOME"] ? resolve(process.env["CODEX_HOME"]) : join(home, ".codex");
128
129
  const opencodeDir = join(process.env["XDG_CONFIG_HOME"] ? resolve(process.env["XDG_CONFIG_HOME"]) : join(home, ".config"), "opencode");
130
+ const binDir = join(sgDir, "bin");
129
131
  return {
130
132
  home,
131
133
  sgDir,
132
134
  hooksDir,
135
+ binDir,
133
136
  claudeDir,
134
137
  antigravityDir,
135
138
  codexDir,
@@ -183,6 +186,38 @@ function readGuard() {
183
186
  const bundled = join(HOOKS_DIR, "guard.bundled.mjs");
184
187
  return existsSync(bundled) ? readFileSync(bundled, "utf-8") : readHook("guard.mjs");
185
188
  }
189
+ function installGoBinaries(binDir) {
190
+ const os_ = process.platform === "win32" ? "win32" : process.platform;
191
+ const cpu = process.arch === "x64" ? "x64" : process.arch;
192
+ const suffix = process.platform === "win32" ? ".exe" : "";
193
+ const placed = [];
194
+ let pkgDir;
195
+ try {
196
+ const require_ = createRequire(import.meta.url);
197
+ pkgDir = dirname(require_.resolve(`@solongate/guard-${os_}-${cpu}/package.json`));
198
+ } catch {
199
+ return placed;
200
+ }
201
+ try {
202
+ mkdirSync(binDir, { recursive: true });
203
+ } catch {
204
+ return placed;
205
+ }
206
+ for (const name of ["solongate-guard", "solongate"]) {
207
+ const from = join(pkgDir, name + suffix);
208
+ const to = join(binDir, name + suffix);
209
+ try {
210
+ if (!existsSync(from)) continue;
211
+ const tmp = to + ".new";
212
+ copyFileSync(from, tmp);
213
+ chmodSync(tmp, 493);
214
+ renameSync(tmp, to);
215
+ placed.push(name + suffix);
216
+ } catch {
217
+ }
218
+ }
219
+ return placed;
220
+ }
186
221
  var ANTIGRAVITY_GROUP = "solongate-guard";
187
222
  function antigravityHookCommand(guardAbs) {
188
223
  const nodeBin = process.execPath.replace(/\\/g, "/");
@@ -576,6 +611,7 @@ function installGlobalQuiet() {
576
611
  mkdirSync(p.claudeDir, { recursive: true });
577
612
  unlockProtected();
578
613
  writeFileSync(join(p.hooksDir, "guard.mjs"), readGuard());
614
+ installGoBinaries(p.binDir);
579
615
  writeFileSync(join(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
580
616
  writeFileSync(join(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
581
617
  writeFileSync(join(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
package/dist/index.js CHANGED
@@ -6214,9 +6214,10 @@ __export(global_install_exports, {
6214
6214
  unlockProtected: () => unlockProtected,
6215
6215
  writeProtectedFile: () => writeProtectedFile
6216
6216
  });
6217
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, rmSync as rmSync2, rmdirSync, readdirSync, statSync, chmodSync } from "fs";
6217
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, rmSync as rmSync2, rmdirSync, readdirSync, statSync, chmodSync, copyFileSync, renameSync } from "fs";
6218
6218
  import { resolve as resolve3, join as join4, dirname } from "path";
6219
6219
  import { homedir as homedir2 } from "os";
6220
+ import { createRequire } from "module";
6220
6221
  import { fileURLToPath } from "url";
6221
6222
  import { createInterface } from "readline";
6222
6223
  import { execFileSync as execFileSync2, spawn } from "child_process";
@@ -6339,10 +6340,12 @@ function globalPaths() {
6339
6340
  const antigravityDir = join4(home, ".gemini", "config");
6340
6341
  const codexDir = process.env["CODEX_HOME"] ? resolve3(process.env["CODEX_HOME"]) : join4(home, ".codex");
6341
6342
  const opencodeDir = join4(process.env["XDG_CONFIG_HOME"] ? resolve3(process.env["XDG_CONFIG_HOME"]) : join4(home, ".config"), "opencode");
6343
+ const binDir = join4(sgDir, "bin");
6342
6344
  return {
6343
6345
  home,
6344
6346
  sgDir,
6345
6347
  hooksDir,
6348
+ binDir,
6346
6349
  claudeDir,
6347
6350
  antigravityDir,
6348
6351
  codexDir,
@@ -6396,6 +6399,38 @@ function readGuard() {
6396
6399
  const bundled = join4(HOOKS_DIR, "guard.bundled.mjs");
6397
6400
  return existsSync3(bundled) ? readFileSync4(bundled, "utf-8") : readHook("guard.mjs");
6398
6401
  }
6402
+ function installGoBinaries(binDir) {
6403
+ const os_ = process.platform === "win32" ? "win32" : process.platform;
6404
+ const cpu = process.arch === "x64" ? "x64" : process.arch;
6405
+ const suffix = process.platform === "win32" ? ".exe" : "";
6406
+ const placed = [];
6407
+ let pkgDir;
6408
+ try {
6409
+ const require_ = createRequire(import.meta.url);
6410
+ pkgDir = dirname(require_.resolve(`@solongate/guard-${os_}-${cpu}/package.json`));
6411
+ } catch {
6412
+ return placed;
6413
+ }
6414
+ try {
6415
+ mkdirSync3(binDir, { recursive: true });
6416
+ } catch {
6417
+ return placed;
6418
+ }
6419
+ for (const name of ["solongate-guard", "solongate"]) {
6420
+ const from = join4(pkgDir, name + suffix);
6421
+ const to = join4(binDir, name + suffix);
6422
+ try {
6423
+ if (!existsSync3(from)) continue;
6424
+ const tmp = to + ".new";
6425
+ copyFileSync(from, tmp);
6426
+ chmodSync(tmp, 493);
6427
+ renameSync(tmp, to);
6428
+ placed.push(name + suffix);
6429
+ } catch {
6430
+ }
6431
+ }
6432
+ return placed;
6433
+ }
6399
6434
  function antigravityHookCommand(guardAbs) {
6400
6435
  const nodeBin = process.execPath.replace(/\\/g, "/");
6401
6436
  const call = process.platform === "win32" ? "& " : "";
@@ -6785,6 +6820,7 @@ function installGlobalQuiet() {
6785
6820
  mkdirSync3(p.claudeDir, { recursive: true });
6786
6821
  unlockProtected();
6787
6822
  writeFileSync3(join4(p.hooksDir, "guard.mjs"), readGuard());
6823
+ installGoBinaries(p.binDir);
6788
6824
  writeFileSync3(join4(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
6789
6825
  writeFileSync3(join4(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
6790
6826
  writeFileSync3(join4(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
package/dist/tui/index.js CHANGED
@@ -9,9 +9,10 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/global-install.ts
12
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync, rmSync, rmdirSync, readdirSync as readdirSync2, statSync as statSync2, chmodSync } from "fs";
12
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync, rmSync, rmdirSync, readdirSync as readdirSync2, statSync as statSync2, chmodSync, copyFileSync, renameSync } from "fs";
13
13
  import { resolve, join as join3, dirname } from "path";
14
14
  import { homedir as homedir3 } from "os";
15
+ import { createRequire } from "module";
15
16
  import { fileURLToPath } from "url";
16
17
  import { createInterface } from "readline";
17
18
  import { execFileSync, spawn } from "child_process";
@@ -134,10 +135,12 @@ function globalPaths() {
134
135
  const antigravityDir = join3(home, ".gemini", "config");
135
136
  const codexDir = process.env["CODEX_HOME"] ? resolve(process.env["CODEX_HOME"]) : join3(home, ".codex");
136
137
  const opencodeDir = join3(process.env["XDG_CONFIG_HOME"] ? resolve(process.env["XDG_CONFIG_HOME"]) : join3(home, ".config"), "opencode");
138
+ const binDir = join3(sgDir, "bin");
137
139
  return {
138
140
  home,
139
141
  sgDir,
140
142
  hooksDir,
143
+ binDir,
141
144
  claudeDir,
142
145
  antigravityDir,
143
146
  codexDir,
@@ -183,6 +186,38 @@ function readGuard() {
183
186
  const bundled = join3(HOOKS_DIR, "guard.bundled.mjs");
184
187
  return existsSync2(bundled) ? readFileSync3(bundled, "utf-8") : readHook("guard.mjs");
185
188
  }
189
+ function installGoBinaries(binDir) {
190
+ const os_ = process.platform === "win32" ? "win32" : process.platform;
191
+ const cpu = process.arch === "x64" ? "x64" : process.arch;
192
+ const suffix = process.platform === "win32" ? ".exe" : "";
193
+ const placed = [];
194
+ let pkgDir;
195
+ try {
196
+ const require_ = createRequire(import.meta.url);
197
+ pkgDir = dirname(require_.resolve(`@solongate/guard-${os_}-${cpu}/package.json`));
198
+ } catch {
199
+ return placed;
200
+ }
201
+ try {
202
+ mkdirSync(binDir, { recursive: true });
203
+ } catch {
204
+ return placed;
205
+ }
206
+ for (const name of ["solongate-guard", "solongate"]) {
207
+ const from = join3(pkgDir, name + suffix);
208
+ const to = join3(binDir, name + suffix);
209
+ try {
210
+ if (!existsSync2(from)) continue;
211
+ const tmp = to + ".new";
212
+ copyFileSync(from, tmp);
213
+ chmodSync(tmp, 493);
214
+ renameSync(tmp, to);
215
+ placed.push(name + suffix);
216
+ } catch {
217
+ }
218
+ }
219
+ return placed;
220
+ }
186
221
  function antigravityHookCommand(guardAbs) {
187
222
  const nodeBin = process.execPath.replace(/\\/g, "/");
188
223
  const call = process.platform === "win32" ? "& " : "";
@@ -502,6 +537,7 @@ function installGlobalQuiet() {
502
537
  mkdirSync(p.claudeDir, { recursive: true });
503
538
  unlockProtected();
504
539
  writeFileSync2(join3(p.hooksDir, "guard.mjs"), readGuard());
540
+ installGoBinaries(p.binDir);
505
541
  writeFileSync2(join3(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
506
542
  writeFileSync2(join3(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
507
543
  writeFileSync2(join3(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
@@ -6530,10 +6530,11 @@ var init_src = __esm({
6530
6530
  });
6531
6531
 
6532
6532
  // hooks/guard.mjs
6533
- import { readFileSync, existsSync, statSync, readdirSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync, rmSync, rmdirSync, openSync, readSync, closeSync } from "node:fs";
6534
- import { spawn } from "node:child_process";
6533
+ import { readFileSync, existsSync, statSync, readdirSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync, rmSync, rmdirSync, openSync, readSync, closeSync, accessSync, constants } from "node:fs";
6534
+ import { spawn, spawnSync } from "node:child_process";
6535
6535
  import { resolve, join, dirname, isAbsolute } from "node:path";
6536
6536
  import { homedir } from "node:os";
6537
+ import { createRequire } from "node:module";
6537
6538
  import { gunzipSync } from "node:zlib";
6538
6539
  import { createHash } from "node:crypto";
6539
6540
  function projectKey(dir) {
@@ -6579,6 +6580,87 @@ function sweepLegacyFlagDir() {
6579
6580
  }
6580
6581
  }
6581
6582
  var HOOK_VERSION = 80;
6583
+ var SG_REFRESH_ARG = process.argv.includes("--sg-refresh-policy");
6584
+ var SG_STDIN = SG_REFRESH_ARG ? "" : (() => {
6585
+ try {
6586
+ return readFileSync(0, "utf-8");
6587
+ } catch {
6588
+ return "";
6589
+ }
6590
+ })();
6591
+ function sgGuardCandidates() {
6592
+ const out = [];
6593
+ if (process.env.SOLONGATE_GUARD_BIN)
6594
+ out.push(process.env.SOLONGATE_GUARD_BIN);
6595
+ const exe = process.platform === "win32" ? "solongate-guard.exe" : "solongate-guard";
6596
+ const os_ = process.platform === "win32" ? "win32" : process.platform;
6597
+ const cpu = process.arch === "x64" ? "x64" : process.arch;
6598
+ try {
6599
+ const req = createRequire(import.meta.url);
6600
+ out.push(join(dirname(req.resolve(`@solongate/guard-${os_}-${cpu}/package.json`)), exe));
6601
+ } catch {
6602
+ }
6603
+ out.push(resolve(homedir(), ".solongate", "bin", exe));
6604
+ return out;
6605
+ }
6606
+ function sgTryGoGuard() {
6607
+ for (const bin of sgGuardCandidates()) {
6608
+ try {
6609
+ if (!existsSync(bin))
6610
+ continue;
6611
+ if (process.platform !== "win32")
6612
+ accessSync(bin, constants.X_OK);
6613
+ } catch {
6614
+ continue;
6615
+ }
6616
+ let version;
6617
+ try {
6618
+ const v = spawnSync(bin, ["--sg-version"], { encoding: "utf-8", timeout: 2e3 });
6619
+ if (v.error || v.status !== 0)
6620
+ continue;
6621
+ version = String(v.stdout || "").trim();
6622
+ } catch {
6623
+ continue;
6624
+ }
6625
+ if (version !== String(HOOK_VERSION))
6626
+ continue;
6627
+ let r;
6628
+ try {
6629
+ r = spawnSync(bin, process.argv.slice(2), {
6630
+ input: SG_STDIN,
6631
+ encoding: "utf-8",
6632
+ // Well past every network call the guard makes. A binary still running
6633
+ // at this point is not going to produce an answer worth waiting for.
6634
+ timeout: 1e4
6635
+ });
6636
+ } catch {
6637
+ return;
6638
+ }
6639
+ if (!r || r.error || r.signal)
6640
+ return;
6641
+ const status = r.status;
6642
+ const stdout = r.stdout || "";
6643
+ if (status !== 0 && status !== 2)
6644
+ return;
6645
+ if (status === 2 && stdout.trim() === "")
6646
+ return;
6647
+ try {
6648
+ if (stdout)
6649
+ process.stdout.write(stdout);
6650
+ if (r.stderr)
6651
+ process.stderr.write(r.stderr);
6652
+ } catch {
6653
+ return;
6654
+ }
6655
+ process.exit(status);
6656
+ }
6657
+ }
6658
+ if (!SG_REFRESH_ARG && process.env.SOLONGATE_NO_GO_GUARD !== "1") {
6659
+ try {
6660
+ sgTryGoGuard();
6661
+ } catch {
6662
+ }
6663
+ }
6582
6664
  function localLogsOnly(security) {
6583
6665
  if (security !== void 0) {
6584
6666
  const l = security && security.localLogs;
@@ -8451,10 +8533,7 @@ function readReferencedFiles(args, cwd) {
8451
8533
  return out;
8452
8534
  }
8453
8535
  if (!REFRESH_MODE) {
8454
- try {
8455
- input += readFileSync(0, "utf-8");
8456
- } catch {
8457
- }
8536
+ input += SG_STDIN;
8458
8537
  }
8459
8538
  (async () => {
8460
8539
  try {
package/hooks/guard.mjs CHANGED
@@ -21,10 +21,11 @@
21
21
  * Logs DENY decisions to SolonGate Cloud. ALLOWs are logged by audit.mjs.
22
22
  * Auto-installed by: npx @solongate/proxy init --global
23
23
  */
24
- import { readFileSync, existsSync, statSync, readdirSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync, rmSync, rmdirSync, openSync, readSync, closeSync } from 'node:fs';
25
- import { spawn } from 'node:child_process';
24
+ import { readFileSync, existsSync, statSync, readdirSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync, rmSync, rmdirSync, openSync, readSync, closeSync, accessSync, constants } from 'node:fs';
25
+ import { spawn, spawnSync } from 'node:child_process';
26
26
  import { resolve, join, dirname, isAbsolute } from 'node:path';
27
27
  import { homedir } from 'node:os';
28
+ import { createRequire } from 'node:module';
28
29
  import { gunzipSync } from 'node:zlib';
29
30
 
30
31
  // ── Per-project scratch, kept OUT of the project ─────────────────────────────
@@ -83,6 +84,135 @@ import { createHash } from 'node:crypto';
83
84
  // reinstall — the same trust model as the OPA WASM this hook already runs.
84
85
  const HOOK_VERSION = 80;
85
86
 
87
+ // ── The Go guard, when there is one and it is the right one ──────────────────
88
+ //
89
+ // Everything below this block is the Node decision engine. It stays, it stays
90
+ // correct, and it stays the thing that runs whenever the fast path cannot be
91
+ // trusted. THE RULE: a missing, stale, unreadable or crashing binary must mean
92
+ // SLOW BUT GUARDED, never FAST BUT UNGUARDED. Every failure here falls through.
93
+ //
94
+ // Three things this gets right that a naive `exec the binary` would not:
95
+ //
96
+ // 1. STDIN IS READ HERE, ONCE, BEFORE THE BINARY RUNS. Handing the child fd 0
97
+ // directly would be faster to write and impossible to recover from: a binary
98
+ // that dies after consuming the payload leaves Node with nothing to fall back
99
+ // WITH, so it would evaluate an empty call and allow it. The payload is a few
100
+ // KB and the read is synchronous; the safety is worth more than the copy.
101
+ //
102
+ // 2. EXIT 2 IS AMBIGUOUS. It is this hook's block signal AND the code the Go
103
+ // runtime uses for an unhandled panic. A block always writes a decision to
104
+ // stdout first; a panic writes a stack trace to stderr and nothing to stdout.
105
+ // So exit 2 is honoured only with output to show for it, and a panic falls
106
+ // through to Node — which will reach its own verdict on the same payload.
107
+ //
108
+ // 3. THE VERSION MUST MATCH EXACTLY, not merely exist. This hook self-updates
109
+ // from the cloud on its own schedule while the binary only moves when npm
110
+ // moves it, so a machine can hold a hook from this week and a binary from
111
+ // last month. HOOK_VERSION bumps on every change to this file, and the binary
112
+ // prints the HOOK_VERSION it implements. Different numbers mean different
113
+ // decisions, and a binary that is merely OLD is not a smaller problem than
114
+ // one that is missing.
115
+ //
116
+ // SOLONGATE_NO_GO_GUARD=1 pins execution to Node. It exists so a failure can be
117
+ // bisected without uninstalling anything.
118
+
119
+ // The refresh invocation has no tool call on stdin — it is spawned detached and
120
+ // its stdin may be a pipe nobody ever closes, so reading it would hang the hook
121
+ // forever. It also has nothing for the binary to decide. Both paths skip it.
122
+ const SG_REFRESH_ARG = process.argv.includes('--sg-refresh-policy');
123
+
124
+ // The one read of fd 0 in this process. Everything downstream uses this string.
125
+ const SG_STDIN = SG_REFRESH_ARG ? '' : (() => {
126
+ try { return readFileSync(0, 'utf-8'); } catch { return ''; }
127
+ })();
128
+
129
+ // Where a binary may legitimately live, in the order they are trusted.
130
+ //
131
+ // The env override is first so a build can be pointed at without reinstalling.
132
+ // The platform package is the normal path for a project-local install. The copy
133
+ // under ~/.solongate/bin is what a GLOBAL install has: the hook is written to
134
+ // ~/.solongate/hooks as a lone file and launched from arbitrary working
135
+ // directories, where resolving through node_modules finds nothing.
136
+ function sgGuardCandidates() {
137
+ const out = [];
138
+ if (process.env.SOLONGATE_GUARD_BIN) out.push(process.env.SOLONGATE_GUARD_BIN);
139
+ const exe = process.platform === 'win32' ? 'solongate-guard.exe' : 'solongate-guard';
140
+ const os_ = process.platform === 'win32' ? 'win32' : process.platform;
141
+ const cpu = process.arch === 'x64' ? 'x64' : process.arch;
142
+ try {
143
+ // The manifest, not a main entry: these packages contain a binary and
144
+ // nothing else, so there is no JavaScript in them to resolve.
145
+ //
146
+ // createRequire rather than import.meta.resolve: the latter is not a sync
147
+ // function on every Node 18 this package supports, and a hook is not the
148
+ // place to find that out. node:module is a builtin, so importing it cannot
149
+ // fail the way a real dependency could.
150
+ const req = createRequire(import.meta.url);
151
+ out.push(join(dirname(req.resolve(`@solongate/guard-${os_}-${cpu}/package.json`)), exe));
152
+ } catch {
153
+ // Not installed for this platform, which is what optionalDependencies does
154
+ // on a host with no published binary, and is not an error.
155
+ }
156
+ out.push(resolve(homedir(), '.solongate', 'bin', exe));
157
+ return out;
158
+ }
159
+
160
+ // Hand the whole call to the binary. Returns only when Node must decide instead.
161
+ function sgTryGoGuard() {
162
+ for (const bin of sgGuardCandidates()) {
163
+ try {
164
+ if (!existsSync(bin)) continue;
165
+ if (process.platform !== 'win32') accessSync(bin, constants.X_OK);
166
+ } catch { continue; }
167
+
168
+ // Ask what it is. Bounded, because this runs before anything has decided the
169
+ // binary can be trusted and it must not be able to hang what it speeds up.
170
+ let version;
171
+ try {
172
+ const v = spawnSync(bin, ['--sg-version'], { encoding: 'utf-8', timeout: 2000 });
173
+ if (v.error || v.status !== 0) continue;
174
+ version = String(v.stdout || '').trim();
175
+ } catch { continue; }
176
+ if (version !== String(HOOK_VERSION)) continue;
177
+
178
+ let r;
179
+ try {
180
+ r = spawnSync(bin, process.argv.slice(2), {
181
+ input: SG_STDIN,
182
+ encoding: 'utf-8',
183
+ // Well past every network call the guard makes. A binary still running
184
+ // at this point is not going to produce an answer worth waiting for.
185
+ timeout: 10000,
186
+ });
187
+ } catch { return; }
188
+
189
+ // Killed, timed out, or never started: Node decides.
190
+ if (!r || r.error || r.signal) return;
191
+ const status = r.status;
192
+ const stdout = r.stdout || '';
193
+ if (status !== 0 && status !== 2) return;
194
+ // See (2) above: a block without a decision to show is a crash wearing a
195
+ // block's exit code.
196
+ if (status === 2 && stdout.trim() === '') return;
197
+
198
+ try {
199
+ if (stdout) process.stdout.write(stdout);
200
+ if (r.stderr) process.stderr.write(r.stderr);
201
+ } catch {
202
+ // Writing the answer is the whole job. If it could not be delivered, Node
203
+ // has not written anything either, so it can still do the work.
204
+ return;
205
+ }
206
+ process.exit(status);
207
+ }
208
+ }
209
+
210
+ if (!SG_REFRESH_ARG && process.env.SOLONGATE_NO_GO_GUARD !== '1') {
211
+ // A throw anywhere in here is a bug in the fast path, and a bug in the fast
212
+ // path must not become an unguarded tool call.
213
+ try { sgTryGoGuard(); } catch {}
214
+ }
215
+
86
216
  // True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
87
217
  // nothing is sent to the cloud audit log — local and cloud are one or the
88
218
  // other, never both.
@@ -2455,15 +2585,21 @@ function readReferencedFiles(args, cwd) {
2455
2585
  return out;
2456
2586
  }
2457
2587
 
2458
- // Read stdin SYNCHRONOUSLY (fd 0) instead of via the process.stdin stream. On
2459
- // Windows + Node 24, calling process.exit() from inside the stdin stream's 'end'
2460
- // callback aborts with `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING),
2461
- // file src\win\async.c` libuv double-closes the stdin pipe handle mid-teardown,
2462
- // the hook crashes before its exit code lands, and Claude Code treats the DENY as
2463
- // a non-blocking hook failure (so the block never applies). Reading fd 0 to EOF
2464
- // synchronously never creates that pipe handle, and the exit below no longer runs
2465
- // inside a stream callback, so the loop tears down cleanly.
2466
- if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
2588
+ // The payload was read at the top of this file, synchronously from fd 0, before
2589
+ // the Go guard was offered the call — see SG_STDIN. It is reused rather than
2590
+ // re-read for two reasons: fd 0 is already at EOF so a second read would return
2591
+ // nothing, and the fallback only means anything if Node still holds what the
2592
+ // binary was given.
2593
+ //
2594
+ // Reading fd 0 synchronously rather than through the process.stdin stream is
2595
+ // itself deliberate. On Windows + Node 24, calling process.exit() from inside the
2596
+ // stdin stream's 'end' callback aborts with `Assertion failed: !(handle->flags &
2597
+ // UV_HANDLE_CLOSING), file src\win\async.c` — libuv double-closes the stdin pipe
2598
+ // handle mid-teardown, the hook crashes before its exit code lands, and Claude
2599
+ // Code treats the DENY as a non-blocking hook failure (so the block never
2600
+ // applies). Reading fd 0 to EOF never creates that pipe handle, and the exit
2601
+ // below no longer runs inside a stream callback, so the loop tears down cleanly.
2602
+ if (!REFRESH_MODE) { input += SG_STDIN; }
2467
2603
  ;(async () => {
2468
2604
  // Safety backstop ONLY: the normal path exits by natural drain (~1ms after work
2469
2605
  // finishes). If the loop somehow fails to drain, force-exit — 8s is well past every
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.83.20",
3
+ "version": "0.83.21",
4
4
  "description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "solongate": "./dist/index.js",
8
- "solongate-proxy": "./dist/index.js",
9
- "solongate-audit": "./dist/audit/index.js",
10
- "proxy": "./dist/index.js"
7
+ "solongate": "./dist/cli-launch.js",
8
+ "solongate-proxy": "./dist/cli-launch.js",
9
+ "proxy": "./dist/cli-launch.js",
10
+ "solongate-audit": "./dist/audit/index.js"
11
11
  },
12
12
  "main": "./dist/lib.js",
13
13
  "types": "./dist/lib.d.ts",
@@ -32,7 +32,9 @@
32
32
  "build:hooks": "node scripts/bundle-hooks.mjs",
33
33
  "dev": "tsx src/index.ts",
34
34
  "typecheck": "tsc --noEmit",
35
- "clean": "rm -rf dist .turbo"
35
+ "clean": "rm -rf dist .turbo platforms",
36
+ "test": "node test/run-all.mjs",
37
+ "build:go": "node scripts/build-go-binaries.mjs"
36
38
  },
37
39
  "keywords": [
38
40
  "ai-tool-security",
@@ -58,6 +60,14 @@
58
60
  "engines": {
59
61
  "node": ">=20.0.0"
60
62
  },
63
+ "optionalDependencies": {
64
+ "@solongate/guard-linux-x64": "0.83.21",
65
+ "@solongate/guard-linux-arm64": "0.83.21",
66
+ "@solongate/guard-darwin-x64": "0.83.21",
67
+ "@solongate/guard-darwin-arm64": "0.83.21",
68
+ "@solongate/guard-win32-x64": "0.83.21",
69
+ "@solongate/guard-win32-arm64": "0.83.21"
70
+ },
61
71
  "dependencies": {
62
72
  "@modelcontextprotocol/sdk": "^1.26.0",
63
73
  "ink": "^5.0.1",