@solongate/proxy 0.83.15 → 0.83.16
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/commands/index.js +8 -8
- package/dist/global-install.d.ts +1 -0
- package/dist/global-install.js +58 -1
- package/dist/index.js +98 -33
- package/dist/tui/index.js +77 -13
- package/hooks/audit.mjs +34 -5
- package/hooks/guard.bundled.mjs +47 -4
- package/hooks/guard.mjs +54 -4
- package/package.json +1 -1
package/dist/commands/index.js
CHANGED
|
@@ -10,7 +10,7 @@ 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, chmodSync } from "fs";
|
|
13
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, rmdirSync, readdirSync, statSync, chmodSync } from "fs";
|
|
14
14
|
import { resolve, join, dirname } from "path";
|
|
15
15
|
import { homedir } from "os";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
@@ -1185,12 +1185,12 @@ async function runAgent(argv) {
|
|
|
1185
1185
|
}
|
|
1186
1186
|
|
|
1187
1187
|
// src/commands/doctor.ts
|
|
1188
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as
|
|
1188
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
1189
1189
|
import { homedir as homedir4 } from "os";
|
|
1190
1190
|
import { join as join4 } from "path";
|
|
1191
1191
|
|
|
1192
1192
|
// src/tui/local-log.ts
|
|
1193
|
-
import { closeSync, existsSync as existsSync3, openSync, readdirSync, readFileSync as readFileSync4, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1193
|
+
import { closeSync, existsSync as existsSync3, openSync, readdirSync as readdirSync2, readFileSync as readFileSync4, readSync, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1194
1194
|
import { homedir as homedir3 } from "os";
|
|
1195
1195
|
import { createHash } from "crypto";
|
|
1196
1196
|
import { isAbsolute, join as join3 } from "path";
|
|
@@ -1198,11 +1198,11 @@ var DEFAULT_LOCAL_LOG = join3(homedir3(), ".solongate", "local-logs", "solongate
|
|
|
1198
1198
|
function localLogFile() {
|
|
1199
1199
|
try {
|
|
1200
1200
|
const sgDir = join3(homedir3(), ".solongate");
|
|
1201
|
-
const caches =
|
|
1201
|
+
const caches = readdirSync2(sgDir).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
|
|
1202
1202
|
const p = join3(sgDir, f);
|
|
1203
1203
|
let mtime = 0;
|
|
1204
1204
|
try {
|
|
1205
|
-
mtime =
|
|
1205
|
+
mtime = statSync2(p).mtimeMs;
|
|
1206
1206
|
} catch {
|
|
1207
1207
|
}
|
|
1208
1208
|
return { p, mtime };
|
|
@@ -1316,7 +1316,7 @@ async function collectChecks() {
|
|
|
1316
1316
|
}
|
|
1317
1317
|
const LOCAL_LOG2 = localLogFile();
|
|
1318
1318
|
if (existsSync4(LOCAL_LOG2)) {
|
|
1319
|
-
const st =
|
|
1319
|
+
const st = statSync3(LOCAL_LOG2);
|
|
1320
1320
|
const ageMin = (Date.now() - st.mtimeMs) / 6e4;
|
|
1321
1321
|
checks.push({ name: "local logs", ok: true, detail: `on \xB7 ${(st.size / 1024).toFixed(0)}KB \xB7 last write ${ageMin < 1 ? "just now" : Math.round(ageMin) + "m ago"}` });
|
|
1322
1322
|
} else {
|
|
@@ -1346,12 +1346,12 @@ async function run6(argv) {
|
|
|
1346
1346
|
}
|
|
1347
1347
|
|
|
1348
1348
|
// src/commands/watch.ts
|
|
1349
|
-
import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readSync as readSync2, statSync as
|
|
1349
|
+
import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readSync as readSync2, statSync as statSync4 } from "fs";
|
|
1350
1350
|
var trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
1351
1351
|
var time = (ms) => new Date(ms).toTimeString().slice(0, 8);
|
|
1352
1352
|
function tailLocal(file, maxBytes = 131072) {
|
|
1353
1353
|
try {
|
|
1354
|
-
const size =
|
|
1354
|
+
const size = statSync4(file).size;
|
|
1355
1355
|
const start = Math.max(0, size - maxBytes);
|
|
1356
1356
|
const fd = openSync2(file, "r");
|
|
1357
1357
|
const buf = Buffer.alloc(size - start);
|
package/dist/global-install.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export declare function isOpencodeGuardInstalled(): boolean;
|
|
|
55
55
|
/** True when OpenCode looks present on this device, so callers can decide
|
|
56
56
|
* whether the OpenCode line is worth showing at all. */
|
|
57
57
|
export declare function opencodeDetected(): boolean;
|
|
58
|
+
export declare function sweepStrayScratchDirs(root?: string, maxDepth?: number, budget?: number): number;
|
|
58
59
|
export declare function runGlobalRestore(): void;
|
|
59
60
|
/**
|
|
60
61
|
* TUI-safe (re)install — the dataroom's one-key "install / update guard" action.
|
package/dist/global-install.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/global-install.ts
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, chmodSync } from "fs";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, rmdirSync, readdirSync, statSync, chmodSync } from "fs";
|
|
3
3
|
import { resolve, join, dirname } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
@@ -395,6 +395,57 @@ function opencodeDetected() {
|
|
|
395
395
|
return false;
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
|
+
var SCRATCH_FILES = /* @__PURE__ */ new Set([".eval-ring.jsonl", ".last-eval", ".last-deny", ".last-tool-call", ".debug-guard-log"]);
|
|
399
|
+
function sweepStrayScratchDirs(root = homedir(), maxDepth = 6, budget = 4e4) {
|
|
400
|
+
let removed = 0;
|
|
401
|
+
let visited = 0;
|
|
402
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "build"]);
|
|
403
|
+
const store = join(homedir(), ".solongate");
|
|
404
|
+
const walk = (dir, depth) => {
|
|
405
|
+
if (depth > maxDepth || visited++ > budget) return;
|
|
406
|
+
let entries;
|
|
407
|
+
try {
|
|
408
|
+
entries = readdirSync(dir);
|
|
409
|
+
} catch {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
for (const name of entries) {
|
|
413
|
+
if (skip.has(name)) continue;
|
|
414
|
+
const full = join(dir, name);
|
|
415
|
+
if (full === store) continue;
|
|
416
|
+
let isDir = false;
|
|
417
|
+
try {
|
|
418
|
+
isDir = statSync(full).isDirectory();
|
|
419
|
+
} catch {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (!isDir) continue;
|
|
423
|
+
if (name === ".solongate") {
|
|
424
|
+
try {
|
|
425
|
+
const left = [];
|
|
426
|
+
for (const f of readdirSync(full)) {
|
|
427
|
+
if (SCRATCH_FILES.has(f)) {
|
|
428
|
+
try {
|
|
429
|
+
rmSync(join(full, f), { force: true });
|
|
430
|
+
} catch {
|
|
431
|
+
left.push(f);
|
|
432
|
+
}
|
|
433
|
+
} else left.push(f);
|
|
434
|
+
}
|
|
435
|
+
if (left.length === 0) {
|
|
436
|
+
rmdirSync(full);
|
|
437
|
+
removed++;
|
|
438
|
+
}
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
walk(full, depth + 1);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
walk(root, 0);
|
|
447
|
+
return removed;
|
|
448
|
+
}
|
|
398
449
|
function ask(question) {
|
|
399
450
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
400
451
|
return new Promise((res) => rl.question(question, (a) => {
|
|
@@ -454,6 +505,11 @@ function repairQuiet() {
|
|
|
454
505
|
line("OpenCode hooks", isOpencodeGuardInstalled(), "guard registered", "NOT registered")
|
|
455
506
|
];
|
|
456
507
|
const notes = [];
|
|
508
|
+
try {
|
|
509
|
+
const swept = sweepStrayScratchDirs();
|
|
510
|
+
if (swept > 0) notes.push(`Removed ${swept} leftover .solongate scratch folder(s) from earlier versions; per-call flags now live under ~/.solongate/projects.`);
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
457
513
|
const cx = codexHooksStatus();
|
|
458
514
|
if (cx.registered && !cx.trusted) {
|
|
459
515
|
notes.push("Codex only: run `/hooks` inside Codex once and trust the SolonGate hooks (Codex skips any hook it has not been told to trust).");
|
|
@@ -807,6 +863,7 @@ export {
|
|
|
807
863
|
runGlobalInstall,
|
|
808
864
|
runGlobalRestore,
|
|
809
865
|
runRepair,
|
|
866
|
+
sweepStrayScratchDirs,
|
|
810
867
|
uninstallGlobalQuiet,
|
|
811
868
|
unlockProtected,
|
|
812
869
|
writeProtectedFile
|
package/dist/index.js
CHANGED
|
@@ -3043,12 +3043,12 @@ ${ctx.indent}`;
|
|
|
3043
3043
|
for (const {
|
|
3044
3044
|
format,
|
|
3045
3045
|
test,
|
|
3046
|
-
resolve:
|
|
3046
|
+
resolve: resolve7
|
|
3047
3047
|
} of tags) {
|
|
3048
3048
|
if (test) {
|
|
3049
3049
|
const match = str.match(test);
|
|
3050
3050
|
if (match) {
|
|
3051
|
-
let res =
|
|
3051
|
+
let res = resolve7.apply(null, match);
|
|
3052
3052
|
if (!(res instanceof Scalar)) res = new Scalar(res);
|
|
3053
3053
|
if (format) res.format = format;
|
|
3054
3054
|
return res;
|
|
@@ -6209,11 +6209,12 @@ __export(global_install_exports, {
|
|
|
6209
6209
|
runGlobalInstall: () => runGlobalInstall,
|
|
6210
6210
|
runGlobalRestore: () => runGlobalRestore,
|
|
6211
6211
|
runRepair: () => runRepair,
|
|
6212
|
+
sweepStrayScratchDirs: () => sweepStrayScratchDirs,
|
|
6212
6213
|
uninstallGlobalQuiet: () => uninstallGlobalQuiet,
|
|
6213
6214
|
unlockProtected: () => unlockProtected,
|
|
6214
6215
|
writeProtectedFile: () => writeProtectedFile
|
|
6215
6216
|
});
|
|
6216
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, rmSync as rmSync2, chmodSync } from "fs";
|
|
6217
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, rmSync as rmSync2, rmdirSync, readdirSync, statSync, chmodSync } from "fs";
|
|
6217
6218
|
import { resolve as resolve3, join as join4, dirname } from "path";
|
|
6218
6219
|
import { homedir as homedir2 } from "os";
|
|
6219
6220
|
import { fileURLToPath } from "url";
|
|
@@ -6604,6 +6605,56 @@ function opencodeDetected() {
|
|
|
6604
6605
|
return false;
|
|
6605
6606
|
}
|
|
6606
6607
|
}
|
|
6608
|
+
function sweepStrayScratchDirs(root = homedir2(), maxDepth = 6, budget = 4e4) {
|
|
6609
|
+
let removed = 0;
|
|
6610
|
+
let visited = 0;
|
|
6611
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "build"]);
|
|
6612
|
+
const store = join4(homedir2(), ".solongate");
|
|
6613
|
+
const walk = (dir, depth) => {
|
|
6614
|
+
if (depth > maxDepth || visited++ > budget) return;
|
|
6615
|
+
let entries;
|
|
6616
|
+
try {
|
|
6617
|
+
entries = readdirSync(dir);
|
|
6618
|
+
} catch {
|
|
6619
|
+
return;
|
|
6620
|
+
}
|
|
6621
|
+
for (const name of entries) {
|
|
6622
|
+
if (skip.has(name)) continue;
|
|
6623
|
+
const full = join4(dir, name);
|
|
6624
|
+
if (full === store) continue;
|
|
6625
|
+
let isDir = false;
|
|
6626
|
+
try {
|
|
6627
|
+
isDir = statSync(full).isDirectory();
|
|
6628
|
+
} catch {
|
|
6629
|
+
continue;
|
|
6630
|
+
}
|
|
6631
|
+
if (!isDir) continue;
|
|
6632
|
+
if (name === ".solongate") {
|
|
6633
|
+
try {
|
|
6634
|
+
const left = [];
|
|
6635
|
+
for (const f of readdirSync(full)) {
|
|
6636
|
+
if (SCRATCH_FILES.has(f)) {
|
|
6637
|
+
try {
|
|
6638
|
+
rmSync2(join4(full, f), { force: true });
|
|
6639
|
+
} catch {
|
|
6640
|
+
left.push(f);
|
|
6641
|
+
}
|
|
6642
|
+
} else left.push(f);
|
|
6643
|
+
}
|
|
6644
|
+
if (left.length === 0) {
|
|
6645
|
+
rmdirSync(full);
|
|
6646
|
+
removed++;
|
|
6647
|
+
}
|
|
6648
|
+
} catch {
|
|
6649
|
+
}
|
|
6650
|
+
continue;
|
|
6651
|
+
}
|
|
6652
|
+
walk(full, depth + 1);
|
|
6653
|
+
}
|
|
6654
|
+
};
|
|
6655
|
+
walk(root, 0);
|
|
6656
|
+
return removed;
|
|
6657
|
+
}
|
|
6607
6658
|
function ask(question) {
|
|
6608
6659
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
6609
6660
|
return new Promise((res) => rl.question(question, (a) => {
|
|
@@ -6663,6 +6714,11 @@ function repairQuiet() {
|
|
|
6663
6714
|
line("OpenCode hooks", isOpencodeGuardInstalled(), "guard registered", "NOT registered")
|
|
6664
6715
|
];
|
|
6665
6716
|
const notes = [];
|
|
6717
|
+
try {
|
|
6718
|
+
const swept = sweepStrayScratchDirs();
|
|
6719
|
+
if (swept > 0) notes.push(`Removed ${swept} leftover .solongate scratch folder(s) from earlier versions; per-call flags now live under ~/.solongate/projects.`);
|
|
6720
|
+
} catch {
|
|
6721
|
+
}
|
|
6666
6722
|
const cx = codexHooksStatus();
|
|
6667
6723
|
if (cx.registered && !cx.trusted) {
|
|
6668
6724
|
notes.push("Codex only: run `/hooks` inside Codex once and trust the SolonGate hooks (Codex skips any hook it has not been told to trust).");
|
|
@@ -6994,7 +7050,7 @@ async function runGlobalInstall(opts = {}) {
|
|
|
6994
7050
|
async function installGlobalWithKey(apiKey, apiUrl) {
|
|
6995
7051
|
await runGlobalInstall({ apiKey, apiUrl });
|
|
6996
7052
|
}
|
|
6997
|
-
var __dirname, HOOKS_DIR, ANTIGRAVITY_GROUP, CODEX_EVENTS, CODEX_TIMEOUT_SEC, SHIM_BEGIN, SHIM_END;
|
|
7053
|
+
var __dirname, HOOKS_DIR, ANTIGRAVITY_GROUP, CODEX_EVENTS, CODEX_TIMEOUT_SEC, SCRATCH_FILES, SHIM_BEGIN, SHIM_END;
|
|
6998
7054
|
var init_global_install = __esm({
|
|
6999
7055
|
"src/global-install.ts"() {
|
|
7000
7056
|
"use strict";
|
|
@@ -7003,6 +7059,7 @@ var init_global_install = __esm({
|
|
|
7003
7059
|
ANTIGRAVITY_GROUP = "solongate-guard";
|
|
7004
7060
|
CODEX_EVENTS = ["PreToolUse", "PostToolUse", "Stop"];
|
|
7005
7061
|
CODEX_TIMEOUT_SEC = 30;
|
|
7062
|
+
SCRATCH_FILES = /* @__PURE__ */ new Set([".eval-ring.jsonl", ".last-eval", ".last-deny", ".last-tool-call", ".debug-guard-log"]);
|
|
7006
7063
|
SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
|
|
7007
7064
|
SHIM_END = "# <<< SolonGate shield <<<";
|
|
7008
7065
|
}
|
|
@@ -7183,7 +7240,7 @@ async function runUpdateCommand() {
|
|
|
7183
7240
|
return 0;
|
|
7184
7241
|
}
|
|
7185
7242
|
function runGlobalInstall2(version) {
|
|
7186
|
-
return new Promise((
|
|
7243
|
+
return new Promise((resolve7) => {
|
|
7187
7244
|
try {
|
|
7188
7245
|
mkdirSync4(join5(homedir3(), ".solongate"), { recursive: true });
|
|
7189
7246
|
execFile(
|
|
@@ -7199,11 +7256,11 @@ ${output}
|
|
|
7199
7256
|
`, { flag: "a" });
|
|
7200
7257
|
} catch {
|
|
7201
7258
|
}
|
|
7202
|
-
|
|
7259
|
+
resolve7({ ok: !err2, needsAdmin: !!err2 && NEEDS_ADMIN_RE.test(output) });
|
|
7203
7260
|
}
|
|
7204
7261
|
);
|
|
7205
7262
|
} catch {
|
|
7206
|
-
|
|
7263
|
+
resolve7({ ok: false, needsAdmin: false });
|
|
7207
7264
|
}
|
|
7208
7265
|
});
|
|
7209
7266
|
}
|
|
@@ -7224,13 +7281,13 @@ function ownNodeModules() {
|
|
|
7224
7281
|
return null;
|
|
7225
7282
|
}
|
|
7226
7283
|
function globalInstallCheck() {
|
|
7227
|
-
prefixCheck ??= new Promise((
|
|
7284
|
+
prefixCheck ??= new Promise((resolve7) => {
|
|
7228
7285
|
try {
|
|
7229
7286
|
const dir = ownNodeModules();
|
|
7230
|
-
if (!dir) return
|
|
7231
|
-
access(dir, FS.W_OK, (e) =>
|
|
7287
|
+
if (!dir) return resolve7({ writable: null, dir: null });
|
|
7288
|
+
access(dir, FS.W_OK, (e) => resolve7({ writable: !e, dir }));
|
|
7232
7289
|
} catch {
|
|
7233
|
-
|
|
7290
|
+
resolve7({ writable: null, dir: null });
|
|
7234
7291
|
}
|
|
7235
7292
|
});
|
|
7236
7293
|
return prefixCheck;
|
|
@@ -7647,7 +7704,7 @@ var init_components = __esm({
|
|
|
7647
7704
|
});
|
|
7648
7705
|
|
|
7649
7706
|
// src/tui/local-log.ts
|
|
7650
|
-
import { closeSync, existsSync as existsSync4, openSync as openSync3, readdirSync, readFileSync as readFileSync8, readSync, statSync, writeFileSync as writeFileSync6 } from "fs";
|
|
7707
|
+
import { closeSync, existsSync as existsSync4, openSync as openSync3, readdirSync as readdirSync2, readFileSync as readFileSync8, readSync, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
7651
7708
|
import { homedir as homedir6 } from "os";
|
|
7652
7709
|
import { createHash as createHash2 } from "crypto";
|
|
7653
7710
|
import { isAbsolute, join as join8 } from "path";
|
|
@@ -7676,11 +7733,11 @@ function localLogsSetting() {
|
|
|
7676
7733
|
}
|
|
7677
7734
|
function policyCachesNewestFirst() {
|
|
7678
7735
|
const sgDir = join8(homedir6(), ".solongate");
|
|
7679
|
-
return
|
|
7736
|
+
return readdirSync2(sgDir).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
|
|
7680
7737
|
const p = join8(sgDir, f);
|
|
7681
7738
|
let mtime = 0;
|
|
7682
7739
|
try {
|
|
7683
|
-
mtime =
|
|
7740
|
+
mtime = statSync2(p).mtimeMs;
|
|
7684
7741
|
} catch {
|
|
7685
7742
|
}
|
|
7686
7743
|
return { p, mtime };
|
|
@@ -7689,11 +7746,11 @@ function policyCachesNewestFirst() {
|
|
|
7689
7746
|
function localLogFile() {
|
|
7690
7747
|
try {
|
|
7691
7748
|
const sgDir = join8(homedir6(), ".solongate");
|
|
7692
|
-
const caches =
|
|
7749
|
+
const caches = readdirSync2(sgDir).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json")).map((f) => {
|
|
7693
7750
|
const p = join8(sgDir, f);
|
|
7694
7751
|
let mtime = 0;
|
|
7695
7752
|
try {
|
|
7696
|
-
mtime =
|
|
7753
|
+
mtime = statSync2(p).mtimeMs;
|
|
7697
7754
|
} catch {
|
|
7698
7755
|
}
|
|
7699
7756
|
return { p, mtime };
|
|
@@ -7737,7 +7794,7 @@ function ensureLocalLogOwner(activeApiKey) {
|
|
|
7737
7794
|
}
|
|
7738
7795
|
function tailLines(file, maxBytes = 131072) {
|
|
7739
7796
|
try {
|
|
7740
|
-
const size =
|
|
7797
|
+
const size = statSync2(file).size;
|
|
7741
7798
|
const start = Math.max(0, size - maxBytes);
|
|
7742
7799
|
const fd = openSync3(file, "r");
|
|
7743
7800
|
const buf = Buffer.alloc(size - start);
|
|
@@ -8536,7 +8593,7 @@ import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
|
8536
8593
|
import TextInput from "ink-text-input";
|
|
8537
8594
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
8538
8595
|
import { homedir as homedir8 } from "os";
|
|
8539
|
-
import { join as join10 } from "path";
|
|
8596
|
+
import { join as join10, resolve as resolve5 } from "path";
|
|
8540
8597
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
8541
8598
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
8542
8599
|
function extractTarget(detail) {
|
|
@@ -9511,7 +9568,7 @@ function LivePanel({ active: active2 }) {
|
|
|
9511
9568
|
] })
|
|
9512
9569
|
] });
|
|
9513
9570
|
}
|
|
9514
|
-
var CONFIG, SPIN, BG, DIM_FLOOR, RING, fmtUp, FILTERS, sessStatus, STATUS_STYLE, LIVE_HELP;
|
|
9571
|
+
var CONFIG, SPIN, BG, DIM_FLOOR, projectKey, RING, fmtUp, FILTERS, sessStatus, STATUS_STYLE, LIVE_HELP;
|
|
9515
9572
|
var init_Live = __esm({
|
|
9516
9573
|
"src/tui/panels/Live.tsx"() {
|
|
9517
9574
|
"use strict";
|
|
@@ -9526,7 +9583,15 @@ var init_Live = __esm({
|
|
|
9526
9583
|
SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
9527
9584
|
BG = "#12234f";
|
|
9528
9585
|
DIM_FLOOR = "#233457";
|
|
9529
|
-
|
|
9586
|
+
projectKey = (dir) => {
|
|
9587
|
+
let h = 2166136261;
|
|
9588
|
+
for (let i = 0; i < dir.length; i++) {
|
|
9589
|
+
h ^= dir.charCodeAt(i);
|
|
9590
|
+
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
9591
|
+
}
|
|
9592
|
+
return h.toString(16);
|
|
9593
|
+
};
|
|
9594
|
+
RING = join10(homedir8(), ".solongate", "projects", projectKey(resolve5(process.cwd())), ".eval-ring.jsonl");
|
|
9530
9595
|
fmtUp = (ms) => {
|
|
9531
9596
|
const s = Math.floor(ms / 1e3);
|
|
9532
9597
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -11563,7 +11628,7 @@ var init_args = __esm({
|
|
|
11563
11628
|
});
|
|
11564
11629
|
|
|
11565
11630
|
// src/commands/doctor.ts
|
|
11566
|
-
import { existsSync as existsSync6, readFileSync as readFileSync10, statSync as
|
|
11631
|
+
import { existsSync as existsSync6, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
|
|
11567
11632
|
import { homedir as homedir10 } from "os";
|
|
11568
11633
|
import { join as join12 } from "path";
|
|
11569
11634
|
async function collectChecks() {
|
|
@@ -11654,7 +11719,7 @@ async function collectChecks() {
|
|
|
11654
11719
|
}
|
|
11655
11720
|
const LOCAL_LOG2 = localLogFile();
|
|
11656
11721
|
if (existsSync6(LOCAL_LOG2)) {
|
|
11657
|
-
const st =
|
|
11722
|
+
const st = statSync3(LOCAL_LOG2);
|
|
11658
11723
|
const ageMin = (Date.now() - st.mtimeMs) / 6e4;
|
|
11659
11724
|
checks.push({ name: "local logs", ok: true, detail: `on \xB7 ${(st.size / 1024).toFixed(0)}KB \xB7 last write ${ageMin < 1 ? "just now" : Math.round(ageMin) + "m ago"}` });
|
|
11660
11725
|
} else {
|
|
@@ -13397,10 +13462,10 @@ var init_agents2 = __esm({
|
|
|
13397
13462
|
});
|
|
13398
13463
|
|
|
13399
13464
|
// src/commands/watch.ts
|
|
13400
|
-
import { closeSync as closeSync2, existsSync as existsSync7, openSync as openSync4, readSync as readSync2, statSync as
|
|
13465
|
+
import { closeSync as closeSync2, existsSync as existsSync7, openSync as openSync4, readSync as readSync2, statSync as statSync4 } from "fs";
|
|
13401
13466
|
function tailLocal(file, maxBytes = 131072) {
|
|
13402
13467
|
try {
|
|
13403
|
-
const size =
|
|
13468
|
+
const size = statSync4(file).size;
|
|
13404
13469
|
const start = Math.max(0, size - maxBytes);
|
|
13405
13470
|
const fd = openSync4(file, "r");
|
|
13406
13471
|
const buf = Buffer.alloc(size - start);
|
|
@@ -13708,10 +13773,10 @@ __export(logs_server_exports, {
|
|
|
13708
13773
|
runLogsServer: () => runLogsServer
|
|
13709
13774
|
});
|
|
13710
13775
|
import { createServer } from "http";
|
|
13711
|
-
import { readFileSync as readFileSync12, statSync as
|
|
13712
|
-
import { resolve as
|
|
13776
|
+
import { readFileSync as readFileSync12, statSync as statSync5 } from "fs";
|
|
13777
|
+
import { resolve as resolve6, join as join14, isAbsolute as isAbsolute2 } from "path";
|
|
13713
13778
|
import { homedir as homedir12 } from "os";
|
|
13714
|
-
import { readdirSync as
|
|
13779
|
+
import { readdirSync as readdirSync3 } from "fs";
|
|
13715
13780
|
function allowedOrigins() {
|
|
13716
13781
|
const base = [
|
|
13717
13782
|
"https://dashboard.solongate.com",
|
|
@@ -13727,12 +13792,12 @@ function resolveLocalLogDir(rawPath) {
|
|
|
13727
13792
|
const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
|
|
13728
13793
|
if (!dir) return null;
|
|
13729
13794
|
if (isAbsolute2(dir)) return dir;
|
|
13730
|
-
return
|
|
13795
|
+
return resolve6(homedir12(), ".solongate", "local-logs");
|
|
13731
13796
|
}
|
|
13732
13797
|
async function findLogDir() {
|
|
13733
|
-
const base =
|
|
13798
|
+
const base = resolve6(homedir12(), ".solongate");
|
|
13734
13799
|
try {
|
|
13735
|
-
const files =
|
|
13800
|
+
const files = readdirSync3(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
|
|
13736
13801
|
for (const f of files) {
|
|
13737
13802
|
try {
|
|
13738
13803
|
const c2 = JSON.parse(readFileSync12(join14(base, f), "utf-8"));
|
|
@@ -13775,7 +13840,7 @@ function fileInfo(dir) {
|
|
|
13775
13840
|
if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
|
|
13776
13841
|
const file = join14(dir, LOG_FILENAME);
|
|
13777
13842
|
try {
|
|
13778
|
-
const st =
|
|
13843
|
+
const st = statSync5(file);
|
|
13779
13844
|
return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
|
|
13780
13845
|
} catch {
|
|
13781
13846
|
return { file, exists: false, size: 0, mtimeMs: 0 };
|
|
@@ -16687,7 +16752,7 @@ var Mutex = class {
|
|
|
16687
16752
|
this.locked = true;
|
|
16688
16753
|
return;
|
|
16689
16754
|
}
|
|
16690
|
-
return new Promise((
|
|
16755
|
+
return new Promise((resolve7, reject) => {
|
|
16691
16756
|
const timer = setTimeout(() => {
|
|
16692
16757
|
const idx = this.queue.indexOf(onReady);
|
|
16693
16758
|
if (idx !== -1) this.queue.splice(idx, 1);
|
|
@@ -16695,7 +16760,7 @@ var Mutex = class {
|
|
|
16695
16760
|
}, timeoutMs);
|
|
16696
16761
|
const onReady = () => {
|
|
16697
16762
|
clearTimeout(timer);
|
|
16698
|
-
|
|
16763
|
+
resolve7();
|
|
16699
16764
|
};
|
|
16700
16765
|
this.queue.push(onReady);
|
|
16701
16766
|
});
|
package/dist/tui/index.js
CHANGED
|
@@ -9,7 +9,7 @@ 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, chmodSync } from "fs";
|
|
12
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync, rmSync, rmdirSync, readdirSync as readdirSync2, statSync as statSync2, chmodSync } from "fs";
|
|
13
13
|
import { resolve, join as join3, dirname } from "path";
|
|
14
14
|
import { homedir as homedir3 } from "os";
|
|
15
15
|
import { fileURLToPath } from "url";
|
|
@@ -392,6 +392,56 @@ function opencodeDetected() {
|
|
|
392
392
|
return false;
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
|
+
function sweepStrayScratchDirs(root = homedir3(), maxDepth = 6, budget = 4e4) {
|
|
396
|
+
let removed = 0;
|
|
397
|
+
let visited = 0;
|
|
398
|
+
const skip = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "build"]);
|
|
399
|
+
const store = join3(homedir3(), ".solongate");
|
|
400
|
+
const walk = (dir, depth) => {
|
|
401
|
+
if (depth > maxDepth || visited++ > budget) return;
|
|
402
|
+
let entries;
|
|
403
|
+
try {
|
|
404
|
+
entries = readdirSync2(dir);
|
|
405
|
+
} catch {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
for (const name of entries) {
|
|
409
|
+
if (skip.has(name)) continue;
|
|
410
|
+
const full = join3(dir, name);
|
|
411
|
+
if (full === store) continue;
|
|
412
|
+
let isDir = false;
|
|
413
|
+
try {
|
|
414
|
+
isDir = statSync2(full).isDirectory();
|
|
415
|
+
} catch {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (!isDir) continue;
|
|
419
|
+
if (name === ".solongate") {
|
|
420
|
+
try {
|
|
421
|
+
const left = [];
|
|
422
|
+
for (const f of readdirSync2(full)) {
|
|
423
|
+
if (SCRATCH_FILES.has(f)) {
|
|
424
|
+
try {
|
|
425
|
+
rmSync(join3(full, f), { force: true });
|
|
426
|
+
} catch {
|
|
427
|
+
left.push(f);
|
|
428
|
+
}
|
|
429
|
+
} else left.push(f);
|
|
430
|
+
}
|
|
431
|
+
if (left.length === 0) {
|
|
432
|
+
rmdirSync(full);
|
|
433
|
+
removed++;
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
walk(full, depth + 1);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
walk(root, 0);
|
|
443
|
+
return removed;
|
|
444
|
+
}
|
|
395
445
|
function repairQuiet() {
|
|
396
446
|
const p = globalPaths();
|
|
397
447
|
const has = (f) => existsSync2(f);
|
|
@@ -415,6 +465,11 @@ function repairQuiet() {
|
|
|
415
465
|
line("OpenCode hooks", isOpencodeGuardInstalled(), "guard registered", "NOT registered")
|
|
416
466
|
];
|
|
417
467
|
const notes = [];
|
|
468
|
+
try {
|
|
469
|
+
const swept = sweepStrayScratchDirs();
|
|
470
|
+
if (swept > 0) notes.push(`Removed ${swept} leftover .solongate scratch folder(s) from earlier versions; per-call flags now live under ~/.solongate/projects.`);
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
418
473
|
const cx = codexHooksStatus();
|
|
419
474
|
if (cx.registered && !cx.trusted) {
|
|
420
475
|
notes.push("Codex only: run `/hooks` inside Codex once and trust the SolonGate hooks (Codex skips any hook it has not been told to trust).");
|
|
@@ -579,7 +634,7 @@ function removeClaudeShim() {
|
|
|
579
634
|
}
|
|
580
635
|
}
|
|
581
636
|
}
|
|
582
|
-
var __dirname, HOOKS_DIR, ANTIGRAVITY_GROUP, CODEX_EVENTS, CODEX_TIMEOUT_SEC, SHIM_BEGIN, SHIM_END;
|
|
637
|
+
var __dirname, HOOKS_DIR, ANTIGRAVITY_GROUP, CODEX_EVENTS, CODEX_TIMEOUT_SEC, SCRATCH_FILES, SHIM_BEGIN, SHIM_END;
|
|
583
638
|
var init_global_install = __esm({
|
|
584
639
|
"src/global-install.ts"() {
|
|
585
640
|
"use strict";
|
|
@@ -588,6 +643,7 @@ var init_global_install = __esm({
|
|
|
588
643
|
ANTIGRAVITY_GROUP = "solongate-guard";
|
|
589
644
|
CODEX_EVENTS = ["PreToolUse", "PostToolUse", "Stop"];
|
|
590
645
|
CODEX_TIMEOUT_SEC = 30;
|
|
646
|
+
SCRATCH_FILES = /* @__PURE__ */ new Set([".eval-ring.jsonl", ".last-eval", ".last-deny", ".last-tool-call", ".debug-guard-log"]);
|
|
591
647
|
SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
|
|
592
648
|
SHIM_END = "# <<< SolonGate shield <<<";
|
|
593
649
|
}
|
|
@@ -798,7 +854,7 @@ import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
|
798
854
|
import TextInput from "ink-text-input";
|
|
799
855
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
800
856
|
import { homedir as homedir5 } from "os";
|
|
801
|
-
import { join as join5 } from "path";
|
|
857
|
+
import { join as join5, resolve as resolve3 } from "path";
|
|
802
858
|
|
|
803
859
|
// src/tui/local-log.ts
|
|
804
860
|
import { closeSync, existsSync, openSync, readdirSync, readFileSync as readFileSync2, readSync, statSync, writeFileSync } from "fs";
|
|
@@ -1594,7 +1650,15 @@ var CONFIG = loadConfig();
|
|
|
1594
1650
|
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1595
1651
|
var BG = "#12234f";
|
|
1596
1652
|
var DIM_FLOOR = "#233457";
|
|
1597
|
-
var
|
|
1653
|
+
var projectKey = (dir) => {
|
|
1654
|
+
let h = 2166136261;
|
|
1655
|
+
for (let i = 0; i < dir.length; i++) {
|
|
1656
|
+
h ^= dir.charCodeAt(i);
|
|
1657
|
+
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
1658
|
+
}
|
|
1659
|
+
return h.toString(16);
|
|
1660
|
+
};
|
|
1661
|
+
var RING = join5(homedir5(), ".solongate", "projects", projectKey(resolve3(process.cwd())), ".eval-ring.jsonl");
|
|
1598
1662
|
var fmtUp = (ms) => {
|
|
1599
1663
|
const s = Math.floor(ms / 1e3);
|
|
1600
1664
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -4431,7 +4495,7 @@ import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } fro
|
|
|
4431
4495
|
init_global_install();
|
|
4432
4496
|
|
|
4433
4497
|
// src/commands/doctor.ts
|
|
4434
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as
|
|
4498
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
4435
4499
|
import { homedir as homedir7 } from "os";
|
|
4436
4500
|
import { join as join7 } from "path";
|
|
4437
4501
|
init_global_install();
|
|
@@ -4523,7 +4587,7 @@ async function collectChecks() {
|
|
|
4523
4587
|
}
|
|
4524
4588
|
const LOCAL_LOG2 = localLogFile();
|
|
4525
4589
|
if (existsSync4(LOCAL_LOG2)) {
|
|
4526
|
-
const st =
|
|
4590
|
+
const st = statSync3(LOCAL_LOG2);
|
|
4527
4591
|
const ageMin = (Date.now() - st.mtimeMs) / 6e4;
|
|
4528
4592
|
checks.push({ name: "local logs", ok: true, detail: `on \xB7 ${(st.size / 1024).toFixed(0)}KB \xB7 last write ${ageMin < 1 ? "just now" : Math.round(ageMin) + "m ago"}` });
|
|
4529
4593
|
} else {
|
|
@@ -4688,7 +4752,7 @@ async function fetchLatest() {
|
|
|
4688
4752
|
}
|
|
4689
4753
|
}
|
|
4690
4754
|
function runGlobalInstall(version) {
|
|
4691
|
-
return new Promise((
|
|
4755
|
+
return new Promise((resolve4) => {
|
|
4692
4756
|
try {
|
|
4693
4757
|
mkdirSync6(join9(homedir9(), ".solongate"), { recursive: true });
|
|
4694
4758
|
execFile(
|
|
@@ -4704,11 +4768,11 @@ ${output}
|
|
|
4704
4768
|
`, { flag: "a" });
|
|
4705
4769
|
} catch {
|
|
4706
4770
|
}
|
|
4707
|
-
|
|
4771
|
+
resolve4({ ok: !err2, needsAdmin: !!err2 && NEEDS_ADMIN_RE.test(output) });
|
|
4708
4772
|
}
|
|
4709
4773
|
);
|
|
4710
4774
|
} catch {
|
|
4711
|
-
|
|
4775
|
+
resolve4({ ok: false, needsAdmin: false });
|
|
4712
4776
|
}
|
|
4713
4777
|
});
|
|
4714
4778
|
}
|
|
@@ -4727,13 +4791,13 @@ function ownNodeModules() {
|
|
|
4727
4791
|
}
|
|
4728
4792
|
var prefixCheck = null;
|
|
4729
4793
|
function globalInstallCheck() {
|
|
4730
|
-
prefixCheck ??= new Promise((
|
|
4794
|
+
prefixCheck ??= new Promise((resolve4) => {
|
|
4731
4795
|
try {
|
|
4732
4796
|
const dir = ownNodeModules();
|
|
4733
|
-
if (!dir) return
|
|
4734
|
-
access(dir, FS.W_OK, (e) =>
|
|
4797
|
+
if (!dir) return resolve4({ writable: null, dir: null });
|
|
4798
|
+
access(dir, FS.W_OK, (e) => resolve4({ writable: !e, dir }));
|
|
4735
4799
|
} catch {
|
|
4736
|
-
|
|
4800
|
+
resolve4({ writable: null, dir: null });
|
|
4737
4801
|
}
|
|
4738
4802
|
});
|
|
4739
4803
|
return prefixCheck;
|
package/hooks/audit.mjs
CHANGED
|
@@ -8,10 +8,39 @@ import { readFileSync, existsSync, writeFileSync, mkdirSync, appendFileSync } fr
|
|
|
8
8
|
import { resolve, join, isAbsolute } from 'node:path';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
|
|
11
|
+
// ── Per-project scratch, kept OUT of the project ─────────────────────────────
|
|
12
|
+
// The guard and the audit hook pass a few small flags to each other (the eval
|
|
13
|
+
// ring, the last-eval measurement, the deny flag, the tool-call marker). These
|
|
14
|
+
// used to be written to `./.solongate/` — relative to whatever directory the
|
|
15
|
+
// agent happened to run in — which scattered a dot-folder through every folder
|
|
16
|
+
// an agent ever touched, config directories included, and put it inside repos
|
|
17
|
+
// that had no ignore rule for it. It also read as "local logging is on" to
|
|
18
|
+
// anyone who found one, which is a different setting entirely.
|
|
19
|
+
//
|
|
20
|
+
// They now live under the user's own ~/.solongate, in a directory named by a
|
|
21
|
+
// hash of the project path, so the per-project separation the dataroom relies
|
|
22
|
+
// on survives while nothing is written where the user works. The hash is
|
|
23
|
+
// duplicated in guard.mjs, audit.mjs and the dataroom: all three must agree for
|
|
24
|
+
// a call's flags to be found, so keep them identical if any one is touched.
|
|
25
|
+
function projectKey(dir) {
|
|
26
|
+
let h = 0x811c9dc5;
|
|
27
|
+
const s = String(dir || '');
|
|
28
|
+
for (let i = 0; i < s.length; i++) {
|
|
29
|
+
h ^= s.charCodeAt(i);
|
|
30
|
+
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
|
|
31
|
+
}
|
|
32
|
+
return h.toString(16);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function projectFlagDir() {
|
|
36
|
+
return join(resolve(homedir(), '.solongate'), 'projects', projectKey(resolve(process.cwd())));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
11
40
|
// Bump on every audit hook change. The cloud serves the newest version; the guard
|
|
12
41
|
// hook installs it on its next run (no re-login needed). See guard.mjs
|
|
13
42
|
// fetchAndInstallHook / maybeSelfUpdate.
|
|
14
|
-
const HOOK_VERSION =
|
|
43
|
+
const HOOK_VERSION = 27;
|
|
15
44
|
|
|
16
45
|
function loadEnvKey(dir) {
|
|
17
46
|
try {
|
|
@@ -52,7 +81,7 @@ function readLastEvalMs(toolName, sessionId) {
|
|
|
52
81
|
// .last-eval flag would be overwritten / half-read under that concurrency). A
|
|
53
82
|
// lone sequential call has only itself in the window → it reports its own time.
|
|
54
83
|
try {
|
|
55
|
-
const ring =
|
|
84
|
+
const ring = join(projectFlagDir(), '.eval-ring.jsonl');
|
|
56
85
|
if (existsSync(ring)) {
|
|
57
86
|
const now = Date.now();
|
|
58
87
|
const recent = [];
|
|
@@ -69,7 +98,7 @@ function readLastEvalMs(toolName, sessionId) {
|
|
|
69
98
|
} catch {}
|
|
70
99
|
// Fallback: the single-value flag (original behavior).
|
|
71
100
|
try {
|
|
72
|
-
const p =
|
|
101
|
+
const p = join(projectFlagDir(), '.last-eval');
|
|
73
102
|
if (!existsSync(p)) return null;
|
|
74
103
|
const c = JSON.parse(readFileSync(p, 'utf-8'));
|
|
75
104
|
if (!c || typeof c.ms !== 'number' || typeof c.ts !== 'number') return null;
|
|
@@ -587,7 +616,7 @@ try { input += readFileSync(0, 'utf-8'); } catch {}
|
|
|
587
616
|
// for a flag written by an older guard (no id, no fp).
|
|
588
617
|
let guardDenied = false;
|
|
589
618
|
try {
|
|
590
|
-
const denyFlagPath =
|
|
619
|
+
const denyFlagPath = join(projectFlagDir(), '.last-deny');
|
|
591
620
|
if (existsSync(denyFlagPath)) {
|
|
592
621
|
const flag = JSON.parse(readFileSync(denyFlagPath, 'utf-8'));
|
|
593
622
|
const fresh = flag.ts && Date.now() - flag.ts < 10000 && flag.tool === toolName;
|
|
@@ -697,7 +726,7 @@ try { input += readFileSync(0, 'utf-8'); } catch {}
|
|
|
697
726
|
|
|
698
727
|
// Write flag so stop.mjs knows tool calls happened (skip text-only ALLOW)
|
|
699
728
|
try {
|
|
700
|
-
const flagDir =
|
|
729
|
+
const flagDir = projectFlagDir();
|
|
701
730
|
mkdirSync(flagDir, { recursive: true });
|
|
702
731
|
writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
|
|
703
732
|
} catch {}
|
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -6530,13 +6530,55 @@ 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, openSync, readSync, closeSync } from "node:fs";
|
|
6533
|
+
import { readFileSync, existsSync, statSync, readdirSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync, rmSync, rmdirSync, openSync, readSync, closeSync } from "node:fs";
|
|
6534
6534
|
import { spawn } from "node:child_process";
|
|
6535
6535
|
import { resolve, join, dirname, isAbsolute } from "node:path";
|
|
6536
6536
|
import { homedir } from "node:os";
|
|
6537
6537
|
import { gunzipSync } from "node:zlib";
|
|
6538
6538
|
import { createHash } from "node:crypto";
|
|
6539
|
-
|
|
6539
|
+
function projectKey(dir) {
|
|
6540
|
+
let h = 2166136261;
|
|
6541
|
+
const s = String(dir || "");
|
|
6542
|
+
for (let i = 0; i < s.length; i++) {
|
|
6543
|
+
h ^= s.charCodeAt(i);
|
|
6544
|
+
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
6545
|
+
}
|
|
6546
|
+
return h.toString(16);
|
|
6547
|
+
}
|
|
6548
|
+
function projectFlagDir() {
|
|
6549
|
+
return join(resolve(homedir(), ".solongate"), "projects", projectKey(resolve(process.cwd())));
|
|
6550
|
+
}
|
|
6551
|
+
var _legacySwept = false;
|
|
6552
|
+
function sweepLegacyFlagDir() {
|
|
6553
|
+
if (_legacySwept)
|
|
6554
|
+
return;
|
|
6555
|
+
_legacySwept = true;
|
|
6556
|
+
try {
|
|
6557
|
+
const dir = resolve(process.cwd(), ".solongate");
|
|
6558
|
+
if (!existsSync(dir))
|
|
6559
|
+
return;
|
|
6560
|
+
const ours = /* @__PURE__ */ new Set([".eval-ring.jsonl", ".last-eval", ".last-deny", ".last-tool-call", ".debug-guard-log"]);
|
|
6561
|
+
const left = [];
|
|
6562
|
+
for (const f of readdirSync(dir)) {
|
|
6563
|
+
if (ours.has(f)) {
|
|
6564
|
+
try {
|
|
6565
|
+
rmSync(join(dir, f), { force: true });
|
|
6566
|
+
} catch {
|
|
6567
|
+
left.push(f);
|
|
6568
|
+
}
|
|
6569
|
+
} else
|
|
6570
|
+
left.push(f);
|
|
6571
|
+
}
|
|
6572
|
+
if (left.length === 0) {
|
|
6573
|
+
try {
|
|
6574
|
+
rmdirSync(dir);
|
|
6575
|
+
} catch {
|
|
6576
|
+
}
|
|
6577
|
+
}
|
|
6578
|
+
} catch {
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
var HOOK_VERSION = 78;
|
|
6540
6582
|
function localLogsOnly(security) {
|
|
6541
6583
|
if (security !== void 0) {
|
|
6542
6584
|
const l = security && security.localLogs;
|
|
@@ -7052,7 +7094,7 @@ function callFingerprint(s) {
|
|
|
7052
7094
|
}
|
|
7053
7095
|
function writeDenyFlag(toolName) {
|
|
7054
7096
|
try {
|
|
7055
|
-
const flagDir =
|
|
7097
|
+
const flagDir = projectFlagDir();
|
|
7056
7098
|
mkdirSync(flagDir, { recursive: true });
|
|
7057
7099
|
writeFileSync(join(flagDir, ".last-tool-call"), Date.now().toString());
|
|
7058
7100
|
writeFileSync(join(flagDir, ".last-deny"), JSON.stringify({
|
|
@@ -8653,8 +8695,9 @@ if (!REFRESH_MODE) {
|
|
|
8653
8695
|
process.stderr.write(`[SolonGate ROUTE] ${opaRoute.toUpperCase()} (${reason ? "block" : "allow"})
|
|
8654
8696
|
`);
|
|
8655
8697
|
try {
|
|
8656
|
-
const _fd =
|
|
8698
|
+
const _fd = projectFlagDir();
|
|
8657
8699
|
mkdirSync(_fd, { recursive: true });
|
|
8700
|
+
sweepLegacyFlagDir();
|
|
8658
8701
|
const _rec = { ms: Date.now() - _evalStart, ts: Date.now(), tool: toolName, session: call.sessionId };
|
|
8659
8702
|
writeFileSync(join(_fd, ".last-eval"), JSON.stringify(_rec));
|
|
8660
8703
|
try {
|
package/hooks/guard.mjs
CHANGED
|
@@ -21,18 +21,67 @@
|
|
|
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, openSync, readSync, closeSync } from 'node:fs';
|
|
24
|
+
import { readFileSync, existsSync, statSync, readdirSync, writeFileSync, mkdirSync, chmodSync, renameSync, appendFileSync, rmSync, rmdirSync, openSync, readSync, closeSync } from 'node:fs';
|
|
25
25
|
import { spawn } from 'node:child_process';
|
|
26
26
|
import { resolve, join, dirname, isAbsolute } from 'node:path';
|
|
27
27
|
import { homedir } from 'node:os';
|
|
28
28
|
import { gunzipSync } from 'node:zlib';
|
|
29
|
+
|
|
30
|
+
// ── Per-project scratch, kept OUT of the project ─────────────────────────────
|
|
31
|
+
// The guard and the audit hook pass a few small flags to each other (the eval
|
|
32
|
+
// ring, the last-eval measurement, the deny flag, the tool-call marker). These
|
|
33
|
+
// used to be written to `./.solongate/` — relative to whatever directory the
|
|
34
|
+
// agent happened to run in — which scattered a dot-folder through every folder
|
|
35
|
+
// an agent ever touched, config directories included, and put it inside repos
|
|
36
|
+
// that had no ignore rule for it. It also read as "local logging is on" to
|
|
37
|
+
// anyone who found one, which is a different setting entirely.
|
|
38
|
+
//
|
|
39
|
+
// They now live under the user's own ~/.solongate, in a directory named by a
|
|
40
|
+
// hash of the project path, so the per-project separation the dataroom relies
|
|
41
|
+
// on survives while nothing is written where the user works. The hash is
|
|
42
|
+
// duplicated in guard.mjs, audit.mjs and the dataroom: all three must agree for
|
|
43
|
+
// a call's flags to be found, so keep them identical if any one is touched.
|
|
44
|
+
function projectKey(dir) {
|
|
45
|
+
let h = 0x811c9dc5;
|
|
46
|
+
const s = String(dir || '');
|
|
47
|
+
for (let i = 0; i < s.length; i++) {
|
|
48
|
+
h ^= s.charCodeAt(i);
|
|
49
|
+
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
|
|
50
|
+
}
|
|
51
|
+
return h.toString(16);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function projectFlagDir() {
|
|
55
|
+
return join(resolve(homedir(), '.solongate'), 'projects', projectKey(resolve(process.cwd())));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Remove the dot-folder older versions left in this working directory. Only our
|
|
59
|
+
// own files go, and the directory only if that empties it — a folder someone
|
|
60
|
+
// else put there, or one with anything unexpected in it, is left alone.
|
|
61
|
+
let _legacySwept = false;
|
|
62
|
+
function sweepLegacyFlagDir() {
|
|
63
|
+
if (_legacySwept) return;
|
|
64
|
+
_legacySwept = true;
|
|
65
|
+
try {
|
|
66
|
+
const dir = resolve(process.cwd(), '.solongate');
|
|
67
|
+
if (!existsSync(dir)) return;
|
|
68
|
+
const ours = new Set(['.eval-ring.jsonl', '.last-eval', '.last-deny', '.last-tool-call', '.debug-guard-log']);
|
|
69
|
+
const left = [];
|
|
70
|
+
for (const f of readdirSync(dir)) {
|
|
71
|
+
if (ours.has(f)) { try { rmSync(join(dir, f), { force: true }); } catch { left.push(f); } }
|
|
72
|
+
else left.push(f);
|
|
73
|
+
}
|
|
74
|
+
if (left.length === 0) { try { rmdirSync(dir); } catch { /* not empty after all */ } }
|
|
75
|
+
} catch { /* never let cleanup disturb a tool call */ }
|
|
76
|
+
}
|
|
77
|
+
|
|
29
78
|
import { createHash } from 'node:crypto';
|
|
30
79
|
|
|
31
80
|
// Bump on every guard.mjs change. The cloud serves the newest bundle + version;
|
|
32
81
|
// the installed hook self-updates when the cloud version is higher (see
|
|
33
82
|
// maybeSelfUpdate). This is what makes guard fixes propagate without a manual
|
|
34
83
|
// reinstall — the same trust model as the OPA WASM this hook already runs.
|
|
35
|
-
const HOOK_VERSION =
|
|
84
|
+
const HOOK_VERSION = 78;
|
|
36
85
|
|
|
37
86
|
// True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
|
|
38
87
|
// nothing is sent to the cloud audit log — local and cloud are one or the
|
|
@@ -780,7 +829,7 @@ function callFingerprint(s) {
|
|
|
780
829
|
// Write flag file so stop.mjs knows a tool call (DENY) happened and doesn't log extra ALLOW
|
|
781
830
|
function writeDenyFlag(toolName) {
|
|
782
831
|
try {
|
|
783
|
-
const flagDir =
|
|
832
|
+
const flagDir = projectFlagDir();
|
|
784
833
|
mkdirSync(flagDir, { recursive: true });
|
|
785
834
|
writeFileSync(join(flagDir, '.last-tool-call'), Date.now().toString());
|
|
786
835
|
// Deny-specific flag: audit.mjs (PostToolUse) reads it to avoid logging a
|
|
@@ -2737,7 +2786,8 @@ if (!REFRESH_MODE) { try { input += readFileSync(0, 'utf-8'); } catch {} }
|
|
|
2737
2786
|
// Keyed by tool + session so the audit hook can match THIS invocation even
|
|
2738
2787
|
// when the tool itself runs for minutes (a bare timestamp TTL lost those).
|
|
2739
2788
|
try {
|
|
2740
|
-
const _fd =
|
|
2789
|
+
const _fd = projectFlagDir(); mkdirSync(_fd, { recursive: true });
|
|
2790
|
+
sweepLegacyFlagDir(); // every call, not just denials
|
|
2741
2791
|
const _rec = { ms: Date.now() - _evalStart, ts: Date.now(), tool: toolName, session: call.sessionId };
|
|
2742
2792
|
writeFileSync(join(_fd, '.last-eval'), JSON.stringify(_rec));
|
|
2743
2793
|
// Also append to a short ring. When many tool calls run in PARALLEL they all
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.83.
|
|
3
|
+
"version": "0.83.16",
|
|
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": {
|