@solongate/proxy 0.83.19 → 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.
- package/dist/cli-launch.d.ts +2 -0
- package/dist/cli-launch.js +69 -0
- package/dist/commands/index.js +4 -1
- package/dist/core/policy.d.ts +2 -2
- package/dist/global-install.d.ts +1 -0
- package/dist/global-install.js +37 -1
- package/dist/index.js +37 -1
- package/dist/tui/index.js +37 -1
- package/hooks/guard.bundled.mjs +132 -35
- package/hooks/guard.mjs +201 -36
- package/package.json +16 -6
|
@@ -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);
|
package/dist/commands/index.js
CHANGED
|
@@ -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,
|
package/dist/core/policy.d.ts
CHANGED
|
@@ -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;
|
package/dist/global-install.d.ts
CHANGED
package/dist/global-install.js
CHANGED
|
@@ -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"));
|
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -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) {
|
|
@@ -6578,7 +6579,88 @@ function sweepLegacyFlagDir() {
|
|
|
6578
6579
|
} catch {
|
|
6579
6580
|
}
|
|
6580
6581
|
}
|
|
6581
|
-
var HOOK_VERSION =
|
|
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;
|
|
@@ -6606,6 +6688,17 @@ function accountMark() {
|
|
|
6606
6688
|
return "";
|
|
6607
6689
|
}
|
|
6608
6690
|
}
|
|
6691
|
+
function postAuditDetached(entry) {
|
|
6692
|
+
try {
|
|
6693
|
+
const payload = Buffer.from(JSON.stringify({
|
|
6694
|
+
url: API_URL + "/api/v1/audit-logs",
|
|
6695
|
+
headers: AUTH_HEADERS,
|
|
6696
|
+
body: entry
|
|
6697
|
+
}), "utf-8").toString("base64");
|
|
6698
|
+
spawn(process.execPath, [process.argv[1], "--sg-audit-post", payload], { detached: true, stdio: "ignore" }).unref();
|
|
6699
|
+
} catch {
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6609
6702
|
function writeLocalLog(security, entry) {
|
|
6610
6703
|
try {
|
|
6611
6704
|
const mark = accountMark();
|
|
@@ -6818,6 +6911,28 @@ var AGENT_TYPE = process.argv[2] || "claude-code";
|
|
|
6818
6911
|
var POLICY_SELECTOR = process.env.SOLONGATE_AGENT_ID || "";
|
|
6819
6912
|
var AGENT_ID = POLICY_SELECTOR || AGENT_TYPE;
|
|
6820
6913
|
var AGENT_NAME = process.env.SOLONGATE_AGENT_NAME || process.argv[3] || AGENT_TYPE;
|
|
6914
|
+
{
|
|
6915
|
+
const _ai = process.argv.indexOf("--sg-audit-post");
|
|
6916
|
+
if (_ai !== -1) {
|
|
6917
|
+
try {
|
|
6918
|
+
const _raw = Buffer.from(process.argv[_ai + 1] || "", "base64").toString("utf-8");
|
|
6919
|
+
const _p = JSON.parse(_raw);
|
|
6920
|
+
if (_p && _p.url && _p.body) {
|
|
6921
|
+
fetch(_p.url, {
|
|
6922
|
+
method: "POST",
|
|
6923
|
+
headers: { "Content-Type": "application/json", ..._p.headers || {} },
|
|
6924
|
+
body: JSON.stringify(_p.body),
|
|
6925
|
+
signal: AbortSignal.timeout(1e4)
|
|
6926
|
+
}).catch(() => {
|
|
6927
|
+
}).finally(() => process.exit(0));
|
|
6928
|
+
setTimeout(() => process.exit(0), 11e3).unref();
|
|
6929
|
+
} else
|
|
6930
|
+
process.exit(0);
|
|
6931
|
+
} catch {
|
|
6932
|
+
process.exit(0);
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6821
6936
|
{
|
|
6822
6937
|
const _wi = process.argv.indexOf("--sg-log-write");
|
|
6823
6938
|
if (_wi !== -1) {
|
|
@@ -8418,10 +8533,7 @@ function readReferencedFiles(args, cwd) {
|
|
|
8418
8533
|
return out;
|
|
8419
8534
|
}
|
|
8420
8535
|
if (!REFRESH_MODE) {
|
|
8421
|
-
|
|
8422
|
-
input += readFileSync(0, "utf-8");
|
|
8423
|
-
} catch {
|
|
8424
|
-
}
|
|
8536
|
+
input += SG_STDIN;
|
|
8425
8537
|
}
|
|
8426
8538
|
(async () => {
|
|
8427
8539
|
try {
|
|
@@ -8511,12 +8623,7 @@ if (!REFRESH_MODE) {
|
|
|
8511
8623
|
}
|
|
8512
8624
|
try {
|
|
8513
8625
|
if (!localLogsOnly(_sec))
|
|
8514
|
-
|
|
8515
|
-
method: "POST",
|
|
8516
|
-
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
8517
|
-
body: JSON.stringify(_logEntry),
|
|
8518
|
-
signal: AbortSignal.timeout(3e3)
|
|
8519
|
-
});
|
|
8626
|
+
postAuditDetached(_logEntry);
|
|
8520
8627
|
} catch {
|
|
8521
8628
|
}
|
|
8522
8629
|
if (AGENT_TYPE !== "codex")
|
|
@@ -8629,22 +8736,17 @@ if (!REFRESH_MODE) {
|
|
|
8629
8736
|
writeLocalLog(securityCfg, { ts: (/* @__PURE__ */ new Date()).toISOString(), tool: toolName, arguments: args, decision: "DENY", reason: "ghost path (hidden from agent)", permission: guessPermission(toolName), source: `${AGENT_TYPE}-guard`, agent_id: AGENT_TYPE, agent_name: AGENT_NAME, session_id: call.sessionId, evaluation_time_ms: Date.now() - _evalStart });
|
|
8630
8737
|
try {
|
|
8631
8738
|
if (!localLogsOnly(securityCfg))
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
agent_name: AGENT_NAME,
|
|
8644
|
-
session_id: call.sessionId,
|
|
8645
|
-
evaluation_time_ms: Date.now() - _evalStart
|
|
8646
|
-
}),
|
|
8647
|
-
signal: AbortSignal.timeout(3e3)
|
|
8739
|
+
postAuditDetached({
|
|
8740
|
+
tool: toolName,
|
|
8741
|
+
arguments: args,
|
|
8742
|
+
decision: "DENY",
|
|
8743
|
+
reason: "ghost path (hidden from agent)",
|
|
8744
|
+
permission: guessPermission(toolName),
|
|
8745
|
+
source: `${AGENT_TYPE}-guard`,
|
|
8746
|
+
agent_id: AGENT_TYPE,
|
|
8747
|
+
agent_name: AGENT_NAME,
|
|
8748
|
+
session_id: call.sessionId,
|
|
8749
|
+
evaluation_time_ms: Date.now() - _evalStart
|
|
8648
8750
|
});
|
|
8649
8751
|
} catch {
|
|
8650
8752
|
}
|
|
@@ -8728,12 +8830,7 @@ if (!REFRESH_MODE) {
|
|
|
8728
8830
|
};
|
|
8729
8831
|
writeLocalLog(securityCfg, { ts: (/* @__PURE__ */ new Date()).toISOString(), ...logEntry });
|
|
8730
8832
|
if (!localLogsOnly(securityCfg))
|
|
8731
|
-
|
|
8732
|
-
method: "POST",
|
|
8733
|
-
headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
|
|
8734
|
-
body: JSON.stringify(logEntry),
|
|
8735
|
-
signal: AbortSignal.timeout(3e3)
|
|
8736
|
-
});
|
|
8833
|
+
postAuditDetached(logEntry);
|
|
8737
8834
|
} catch {
|
|
8738
8835
|
}
|
|
8739
8836
|
}
|
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 ─────────────────────────────
|
|
@@ -81,7 +82,136 @@ import { createHash } from 'node:crypto';
|
|
|
81
82
|
// the installed hook self-updates when the cloud version is higher (see
|
|
82
83
|
// maybeSelfUpdate). This is what makes guard fixes propagate without a manual
|
|
83
84
|
// reinstall — the same trust model as the OPA WASM this hook already runs.
|
|
84
|
-
const HOOK_VERSION =
|
|
85
|
+
const HOOK_VERSION = 80;
|
|
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
|
+
}
|
|
85
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
|
|
@@ -170,6 +300,25 @@ function accountMark() {
|
|
|
170
300
|
}
|
|
171
301
|
}
|
|
172
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Record a denial in the cloud WITHOUT making the agent wait for it.
|
|
305
|
+
*
|
|
306
|
+
* The verdict is the product; the audit line is bookkeeping. Awaiting the POST
|
|
307
|
+
* put a full network round trip between "blocked" and the agent hearing it —
|
|
308
|
+
* measured at 1643ms per denial, against 78ms with the API unreachable. Handing
|
|
309
|
+
* it to a detached child keeps the record and gives the time back.
|
|
310
|
+
*/
|
|
311
|
+
function postAuditDetached(entry) {
|
|
312
|
+
try {
|
|
313
|
+
const payload = Buffer.from(JSON.stringify({
|
|
314
|
+
url: API_URL + '/api/v1/audit-logs',
|
|
315
|
+
headers: AUTH_HEADERS,
|
|
316
|
+
body: entry,
|
|
317
|
+
}), 'utf-8').toString('base64');
|
|
318
|
+
spawn(process.execPath, [process.argv[1], '--sg-audit-post', payload], { detached: true, stdio: 'ignore' }).unref();
|
|
319
|
+
} catch { /* the denial still stands; only the record is at risk */ }
|
|
320
|
+
}
|
|
321
|
+
|
|
173
322
|
function writeLocalLog(security, entry) {
|
|
174
323
|
try {
|
|
175
324
|
const mark = accountMark();
|
|
@@ -460,6 +609,31 @@ const AGENT_NAME = process.env.SOLONGATE_AGENT_NAME || process.argv[3] || AGENT_
|
|
|
460
609
|
// lands even if Claude Code kills the parent hook mid-write during a concurrent
|
|
461
610
|
// denial burst. Handled FIRST and exits immediately — the child never loads the
|
|
462
611
|
// heavy guard logic below.
|
|
612
|
+
// Survivable CLOUD writer, same idea. A denial used to `await` its audit POST
|
|
613
|
+
// before telling the agent it was blocked, so the agent sat waiting on a network
|
|
614
|
+
// round trip to be told "no": measured at 1643ms per denial against 78ms with
|
|
615
|
+
// the API unreachable. The record still has to land, so it is handed to a
|
|
616
|
+
// detached child rather than dropped — the verdict goes out at once and the POST
|
|
617
|
+
// finishes on its own.
|
|
618
|
+
{
|
|
619
|
+
const _ai = process.argv.indexOf('--sg-audit-post');
|
|
620
|
+
if (_ai !== -1) {
|
|
621
|
+
try {
|
|
622
|
+
const _raw = Buffer.from(process.argv[_ai + 1] || '', 'base64').toString('utf-8');
|
|
623
|
+
const _p = JSON.parse(_raw);
|
|
624
|
+
if (_p && _p.url && _p.body) {
|
|
625
|
+
fetch(_p.url, {
|
|
626
|
+
method: 'POST',
|
|
627
|
+
headers: { 'Content-Type': 'application/json', ...(_p.headers || {}) },
|
|
628
|
+
body: JSON.stringify(_p.body),
|
|
629
|
+
signal: AbortSignal.timeout(10000),
|
|
630
|
+
}).catch(() => {}).finally(() => process.exit(0));
|
|
631
|
+
setTimeout(() => process.exit(0), 11000).unref();
|
|
632
|
+
} else process.exit(0);
|
|
633
|
+
} catch { process.exit(0); }
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
463
637
|
{
|
|
464
638
|
const _wi = process.argv.indexOf('--sg-log-write');
|
|
465
639
|
if (_wi !== -1) {
|
|
@@ -2411,15 +2585,21 @@ function readReferencedFiles(args, cwd) {
|
|
|
2411
2585
|
return out;
|
|
2412
2586
|
}
|
|
2413
2587
|
|
|
2414
|
-
//
|
|
2415
|
-
//
|
|
2416
|
-
//
|
|
2417
|
-
//
|
|
2418
|
-
//
|
|
2419
|
-
//
|
|
2420
|
-
//
|
|
2421
|
-
//
|
|
2422
|
-
|
|
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; }
|
|
2423
2603
|
;(async () => {
|
|
2424
2604
|
// Safety backstop ONLY: the normal path exits by natural drain (~1ms after work
|
|
2425
2605
|
// finishes). If the loop somehow fails to drain, force-exit — 8s is well past every
|
|
@@ -2523,12 +2703,7 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2523
2703
|
};
|
|
2524
2704
|
try { writeLocalLog(_sec, { ts: new Date().toISOString(), ..._logEntry }); } catch {}
|
|
2525
2705
|
try {
|
|
2526
|
-
if (!localLogsOnly(_sec))
|
|
2527
|
-
method: 'POST',
|
|
2528
|
-
headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
|
|
2529
|
-
body: JSON.stringify(_logEntry),
|
|
2530
|
-
signal: AbortSignal.timeout(3000),
|
|
2531
|
-
});
|
|
2706
|
+
if (!localLogsOnly(_sec)) postAuditDetached(_logEntry);
|
|
2532
2707
|
} catch {}
|
|
2533
2708
|
// Not on Codex: its block reason IS the hook's stderr (see the ROUTE
|
|
2534
2709
|
// write at the end of the slow path for the full rationale).
|
|
@@ -2676,18 +2851,13 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2676
2851
|
try { writeDenyFlag(toolName); } catch {}
|
|
2677
2852
|
writeLocalLog(securityCfg, { ts: new Date().toISOString(), tool: toolName, arguments: args, decision: 'DENY', reason: 'ghost path (hidden from agent)', permission: guessPermission(toolName), source: `${AGENT_TYPE}-guard`, agent_id: AGENT_TYPE, agent_name: AGENT_NAME, session_id: call.sessionId, evaluation_time_ms: Date.now() - _evalStart });
|
|
2678
2853
|
try {
|
|
2679
|
-
if (!localLogsOnly(securityCfg))
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
source: `${AGENT_TYPE}-guard`, agent_id: AGENT_TYPE, agent_name: AGENT_NAME,
|
|
2687
|
-
session_id: call.sessionId,
|
|
2688
|
-
evaluation_time_ms: Date.now() - _evalStart,
|
|
2689
|
-
}),
|
|
2690
|
-
signal: AbortSignal.timeout(3000),
|
|
2854
|
+
if (!localLogsOnly(securityCfg)) postAuditDetached({
|
|
2855
|
+
tool: toolName, arguments: args, decision: 'DENY',
|
|
2856
|
+
reason: 'ghost path (hidden from agent)',
|
|
2857
|
+
permission: guessPermission(toolName),
|
|
2858
|
+
source: `${AGENT_TYPE}-guard`, agent_id: AGENT_TYPE, agent_name: AGENT_NAME,
|
|
2859
|
+
session_id: call.sessionId,
|
|
2860
|
+
evaluation_time_ms: Date.now() - _evalStart,
|
|
2691
2861
|
});
|
|
2692
2862
|
} catch {}
|
|
2693
2863
|
// NOT maybeSelfUpdate() first: see the deny at the end of this flow.
|
|
@@ -2819,12 +2989,7 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2819
2989
|
writeLocalLog(securityCfg, { ts: new Date().toISOString(), ...logEntry });
|
|
2820
2990
|
// PI hook layer removed — piResult fields no longer attached.
|
|
2821
2991
|
// Local-only mode: keep the log on the user's machine, skip the cloud.
|
|
2822
|
-
if (!localLogsOnly(securityCfg))
|
|
2823
|
-
method: 'POST',
|
|
2824
|
-
headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
|
|
2825
|
-
body: JSON.stringify(logEntry),
|
|
2826
|
-
signal: AbortSignal.timeout(3000),
|
|
2827
|
-
});
|
|
2992
|
+
if (!localLogsOnly(securityCfg)) postAuditDetached(logEntry);
|
|
2828
2993
|
} catch {}
|
|
2829
2994
|
}
|
|
2830
2995
|
writeDenyFlag(toolName);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.83.
|
|
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/
|
|
8
|
-
"solongate-proxy": "./dist/
|
|
9
|
-
"
|
|
10
|
-
"
|
|
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",
|