@solongate/proxy 0.83.3 → 0.83.5
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/api-client/auth.d.ts +0 -8
- package/dist/api-client/client.d.ts +0 -14
- package/dist/commands/index.js +0 -33
- package/dist/global-install.js +10 -0
- package/dist/index.js +175 -157
- package/dist/self-update.d.ts +14 -1
- package/dist/tui/index.js +83 -92
- package/hooks/guard.bundled.mjs +1 -44
- package/hooks/guard.mjs +1 -53
- package/package.json +1 -1
package/dist/self-update.d.ts
CHANGED
|
@@ -23,6 +23,19 @@ export declare function latestVersion(): Promise<string | null>;
|
|
|
23
23
|
export declare function runUpdateCommand(): Promise<number>;
|
|
24
24
|
/** The exact command that fixes a root-owned global prefix. */
|
|
25
25
|
export declare function adminInstallCommand(): string;
|
|
26
|
+
/** Are we root — i.e. did someone run this under sudo? */
|
|
27
|
+
export declare function runningAsRoot(): boolean;
|
|
28
|
+
export declare function globalInstallCheck(): Promise<{
|
|
29
|
+
writable: boolean | null;
|
|
30
|
+
dir: string | null;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* The two commands that update SolonGate on a machine whose global npm folder
|
|
34
|
+
* belongs to root. `repair` is deliberately NOT under sudo: it writes the guard
|
|
35
|
+
* hooks into the invoking user's home, so as root it would arm root's home and
|
|
36
|
+
* leave the actual user unguarded.
|
|
37
|
+
*/
|
|
38
|
+
export declare function adminUpdateSteps(): string[];
|
|
26
39
|
/**
|
|
27
40
|
* Install the newest version NOW, whatever the auto-update setting says — the
|
|
28
41
|
* dataroom's UPDATES row and `solongate update` both run through here.
|
|
@@ -36,7 +49,7 @@ export declare function updateNow(): Promise<{
|
|
|
36
49
|
* Kept separate from `runUpdateCommand` so `solongate update` stays "update me
|
|
37
50
|
* now" and never changes a setting as a side effect.
|
|
38
51
|
*/
|
|
39
|
-
export declare function runAutoUpdateCommand(arg?: string): number
|
|
52
|
+
export declare function runAutoUpdateCommand(arg?: string): Promise<number>;
|
|
40
53
|
/** What the dataroom shows about the updater. */
|
|
41
54
|
export type UpdateStatus = {
|
|
42
55
|
kind: 'idle';
|
package/dist/tui/index.js
CHANGED
|
@@ -530,8 +530,8 @@ var init_global_install = __esm({
|
|
|
530
530
|
|
|
531
531
|
// src/tui/index.tsx
|
|
532
532
|
import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
|
|
533
|
-
import { homedir as
|
|
534
|
-
import { join as
|
|
533
|
+
import { homedir as homedir10 } from "os";
|
|
534
|
+
import { join as join10 } from "path";
|
|
535
535
|
import { render } from "ink";
|
|
536
536
|
|
|
537
537
|
// src/tui/App.tsx
|
|
@@ -935,16 +935,6 @@ function setViewCredentials(creds) {
|
|
|
935
935
|
viewOverride = creds;
|
|
936
936
|
cached2 = null;
|
|
937
937
|
}
|
|
938
|
-
var GUEST_EMAIL_SUFFIX = "@anonymous.solongate.invalid";
|
|
939
|
-
function isGuestAccount() {
|
|
940
|
-
try {
|
|
941
|
-
const active2 = loginCredentialFile().apiKey;
|
|
942
|
-
if (!active2) return false;
|
|
943
|
-
return listAccounts().some((a) => a.apiKey === active2 && (a.email ?? "").endsWith(GUEST_EMAIL_SUFFIX));
|
|
944
|
-
} catch {
|
|
945
|
-
return false;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
938
|
function enforcingKey() {
|
|
949
939
|
return loginCredentialFile().apiKey ?? null;
|
|
950
940
|
}
|
|
@@ -1000,12 +990,6 @@ var ApiError = class extends Error {
|
|
|
1000
990
|
this.code = code;
|
|
1001
991
|
}
|
|
1002
992
|
};
|
|
1003
|
-
var GuestReadOnlyError = class extends Error {
|
|
1004
|
-
constructor() {
|
|
1005
|
-
super("Guest account: sign up at auth.solongate.com to change anything. Your project, its policy and everything recorded carry over.");
|
|
1006
|
-
this.name = "GuestReadOnlyError";
|
|
1007
|
-
}
|
|
1008
|
-
};
|
|
1009
993
|
var NotAuthenticatedError = class extends Error {
|
|
1010
994
|
constructor() {
|
|
1011
995
|
super("Not logged in. Run `solongate` and log in from the Accounts panel.");
|
|
@@ -1070,9 +1054,6 @@ function buildUrl(base, path, query) {
|
|
|
1070
1054
|
return url.toString();
|
|
1071
1055
|
}
|
|
1072
1056
|
async function request(method, path, opts = {}) {
|
|
1073
|
-
if (method !== "GET" && isGuestAccount()) {
|
|
1074
|
-
throw new GuestReadOnlyError();
|
|
1075
|
-
}
|
|
1076
1057
|
const creds = resolveCredentials(opts.apiUrl);
|
|
1077
1058
|
const url = buildUrl(creds.apiUrl, path, opts.query);
|
|
1078
1059
|
const headers = {
|
|
@@ -2422,18 +2403,18 @@ function LivePanel({ active: active2 }) {
|
|
|
2422
2403
|
}
|
|
2423
2404
|
) : null,
|
|
2424
2405
|
activeAction ? /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", backgroundColor: activeAction.level === "bad" ? "#3d1220" : "#123d1f", color: activeAction.level === "bad" ? "#ff6b6b" : "#7bd88f", bold: true, children: ` ${truncate(activeAction.text, innerW - 4)} ` }) : null,
|
|
2425
|
-
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
2426
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
2406
|
+
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, overflow: "hidden", children: [
|
|
2407
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, height: 1 + chartH, marginRight: 2, overflow: "hidden", children: [
|
|
2427
2408
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
2428
2409
|
/* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
|
|
2429
2410
|
] }),
|
|
2430
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
|
|
2411
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, height: 1 + chartH, overflow: "hidden", children: [
|
|
2431
2412
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `now ${latNow}ms \xB7 med ${latMed}ms \xB7 amber >${Math.round(latHotAt)}ms`, width: rightW }),
|
|
2432
2413
|
/* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "white", hotColor: "#ffb454" })
|
|
2433
2414
|
] })
|
|
2434
2415
|
] }),
|
|
2435
|
-
/* @__PURE__ */ jsxs2(Box2, { height: colH, children: [
|
|
2436
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
2416
|
+
/* @__PURE__ */ jsxs2(Box2, { height: colH, overflow: "hidden", children: [
|
|
2417
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, height: colH, marginRight: 2, overflow: "hidden", children: [
|
|
2437
2418
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", extra: "l = inspect", width: colW }),
|
|
2438
2419
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
2439
2420
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
|
|
@@ -2446,7 +2427,11 @@ function LivePanel({ active: active2 }) {
|
|
|
2446
2427
|
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
|
|
2447
2428
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
|
|
2448
2429
|
] }),
|
|
2449
|
-
dlpBars.length ? dlpBars.map((d2) => /* @__PURE__ */ jsx2(HBar, { label: truncate(d2.pattern, 10), value: d2.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d2.pattern)) :
|
|
2430
|
+
dlpBars.length ? dlpBars.map((d2) => /* @__PURE__ */ jsx2(HBar, { label: truncate(d2.pattern, 10), value: d2.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d2.pattern)) : (
|
|
2431
|
+
// 36 chars against a colW that is ~31 at cols=100: without truncate
|
|
2432
|
+
// this wrapped onto a second line and pushed the column over colH.
|
|
2433
|
+
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: " no dlp hits in last 7 days" })
|
|
2434
|
+
),
|
|
2450
2435
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
2451
2436
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "GUARD".padEnd(10) }),
|
|
2452
2437
|
ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
|
|
@@ -2455,7 +2440,7 @@ function LivePanel({ active: active2 }) {
|
|
|
2455
2440
|
] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no local ring in cwd" })
|
|
2456
2441
|
] })
|
|
2457
2442
|
] }),
|
|
2458
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
2443
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, height: colH, marginRight: 2, overflow: "hidden", children: [
|
|
2459
2444
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
2460
2445
|
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
2461
2446
|
pickable.map((r, i) => {
|
|
@@ -2481,7 +2466,7 @@ function LivePanel({ active: active2 }) {
|
|
|
2481
2466
|
] }, r.id);
|
|
2482
2467
|
})
|
|
2483
2468
|
] }),
|
|
2484
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
|
|
2469
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, height: colH, overflow: "hidden", children: [
|
|
2485
2470
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "system heartbeat", width: colW }),
|
|
2486
2471
|
log.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
2487
2472
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
@@ -4495,9 +4480,9 @@ function stopLogsServerDaemon() {
|
|
|
4495
4480
|
|
|
4496
4481
|
// src/self-update.ts
|
|
4497
4482
|
import { execFile, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
|
|
4498
|
-
import { mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
4483
|
+
import { access, mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync7, constants as FS } from "fs";
|
|
4499
4484
|
import { homedir as homedir9 } from "os";
|
|
4500
|
-
import { dirname as dirname3, join as join9 } from "path";
|
|
4485
|
+
import { dirname as dirname3, join as join9, sep } from "path";
|
|
4501
4486
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4502
4487
|
var PKG = "@solongate/proxy";
|
|
4503
4488
|
var CHECK_EVERY_MS = 30 * 60 * 1e3;
|
|
@@ -4598,11 +4583,41 @@ ${output}
|
|
|
4598
4583
|
function adminInstallCommand() {
|
|
4599
4584
|
return process.platform === "win32" ? `npm i -g ${PKG}@latest (in an Administrator terminal)` : `sudo npm i -g ${PKG}@latest`;
|
|
4600
4585
|
}
|
|
4586
|
+
function ownNodeModules() {
|
|
4587
|
+
let dir = dirname3(fileURLToPath3(import.meta.url));
|
|
4588
|
+
for (let i = 0; i < 12; i++) {
|
|
4589
|
+
const parent = dirname3(dir);
|
|
4590
|
+
if (parent === dir) break;
|
|
4591
|
+
if (dir.endsWith(`${sep}node_modules`)) return dir;
|
|
4592
|
+
dir = parent;
|
|
4593
|
+
}
|
|
4594
|
+
return null;
|
|
4595
|
+
}
|
|
4596
|
+
var prefixCheck = null;
|
|
4597
|
+
function globalInstallCheck() {
|
|
4598
|
+
prefixCheck ??= new Promise((resolve3) => {
|
|
4599
|
+
try {
|
|
4600
|
+
const dir = ownNodeModules();
|
|
4601
|
+
if (!dir) return resolve3({ writable: null, dir: null });
|
|
4602
|
+
access(dir, FS.W_OK, (e) => resolve3({ writable: !e, dir }));
|
|
4603
|
+
} catch {
|
|
4604
|
+
resolve3({ writable: null, dir: null });
|
|
4605
|
+
}
|
|
4606
|
+
});
|
|
4607
|
+
return prefixCheck;
|
|
4608
|
+
}
|
|
4609
|
+
function adminUpdateSteps() {
|
|
4610
|
+
return [` ${adminInstallCommand()}`, ` solongate repair${process.platform === "win32" ? "" : " (this one WITHOUT sudo)"}`];
|
|
4611
|
+
}
|
|
4601
4612
|
async function updateNow() {
|
|
4602
4613
|
const cur = currentVersion();
|
|
4603
4614
|
const latest = await fetchLatest();
|
|
4604
4615
|
if (!latest) return { status: "unreachable", version: cur };
|
|
4605
4616
|
if (!newerThan(latest, cur)) return { status: "current", version: cur };
|
|
4617
|
+
if ((await globalInstallCheck()).writable === false) {
|
|
4618
|
+
writeState2({ ...readState2(), needsAdmin: latest });
|
|
4619
|
+
return { status: "needs-admin", version: latest };
|
|
4620
|
+
}
|
|
4606
4621
|
const r = await runGlobalInstall(latest);
|
|
4607
4622
|
if (r.ok) {
|
|
4608
4623
|
writeState2({ ...readState2(), installed: latest, needsAdmin: void 0 });
|
|
@@ -4647,26 +4662,7 @@ async function tuiUpdateFlow(onStatus) {
|
|
|
4647
4662
|
}
|
|
4648
4663
|
|
|
4649
4664
|
// src/tui/panels/Settings.tsx
|
|
4650
|
-
import { readFileSync as readFileSync8, readdirSync as readdirSync2 } from "fs";
|
|
4651
|
-
import { homedir as homedir10 } from "os";
|
|
4652
|
-
import { join as join10 } from "path";
|
|
4653
4665
|
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
4654
|
-
function guestAllowance() {
|
|
4655
|
-
try {
|
|
4656
|
-
const dir = join10(homedir10(), ".solongate");
|
|
4657
|
-
for (const f of readdirSync2(dir)) {
|
|
4658
|
-
if (!f.startsWith(".policy-cache-") || !f.endsWith(".json")) continue;
|
|
4659
|
-
try {
|
|
4660
|
-
const c2 = JSON.parse(readFileSync8(join10(dir, f), "utf-8"));
|
|
4661
|
-
const q = c2?.security?.guestQuota;
|
|
4662
|
-
if (q && typeof q.limit === "number") return { used: q.used ?? 0, limit: q.limit };
|
|
4663
|
-
} catch {
|
|
4664
|
-
}
|
|
4665
|
-
}
|
|
4666
|
-
} catch {
|
|
4667
|
-
}
|
|
4668
|
-
return null;
|
|
4669
|
-
}
|
|
4670
4666
|
var EVENTS = ["denials", "allowed", "all"];
|
|
4671
4667
|
var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4672
4668
|
var SIGNALS2 = ["any", "deny", "dlp", "ratelimit"];
|
|
@@ -4703,6 +4699,17 @@ function SettingsPanel({
|
|
|
4703
4699
|
const [diagBusy, setDiagBusy] = useState8(null);
|
|
4704
4700
|
const [autoUp, setAutoUp] = useState8(() => autoUpdateEnabled());
|
|
4705
4701
|
const [updBusy, setUpdBusy] = useState8(false);
|
|
4702
|
+
const [canInstall, setCanInstall] = useState8(null);
|
|
4703
|
+
useEffect7(() => {
|
|
4704
|
+
let live2 = true;
|
|
4705
|
+
globalInstallCheck().then((r) => {
|
|
4706
|
+
if (live2) setCanInstall(r.writable);
|
|
4707
|
+
}).catch(() => {
|
|
4708
|
+
});
|
|
4709
|
+
return () => {
|
|
4710
|
+
live2 = false;
|
|
4711
|
+
};
|
|
4712
|
+
}, []);
|
|
4706
4713
|
const [diag, setDiag] = useState8(null);
|
|
4707
4714
|
const [diagStep, setDiagStep] = useState8(0);
|
|
4708
4715
|
const DIAG_STEPS = {
|
|
@@ -4720,7 +4727,6 @@ function SettingsPanel({
|
|
|
4720
4727
|
const step = setInterval(() => setDiagStep((s) => Math.min(s + 1, total - 1)), 220);
|
|
4721
4728
|
return () => clearInterval(step);
|
|
4722
4729
|
}, [diagBusy]);
|
|
4723
|
-
void guestAllowance;
|
|
4724
4730
|
const ver = currentVersion();
|
|
4725
4731
|
useEffect7(() => {
|
|
4726
4732
|
let live2 = true;
|
|
@@ -4982,15 +4988,27 @@ function SettingsPanel({
|
|
|
4982
4988
|
if (updBusy) return;
|
|
4983
4989
|
setUpdBusy(true);
|
|
4984
4990
|
setMsg({ text: "updating\u2026", level: "ok" });
|
|
4991
|
+
setDiag(null);
|
|
4985
4992
|
void (async () => {
|
|
4986
4993
|
try {
|
|
4987
4994
|
const res = await updateNow();
|
|
4988
4995
|
if (res.status === "updated") setMsg({ text: `\u2713 v${res.version} installed \xB7 restart (q, then solongate) to apply`, level: "ok" });
|
|
4989
4996
|
else if (res.status === "current") setMsg({ text: `\u2713 already on the latest version (v${res.version})`, level: "ok" });
|
|
4990
|
-
else if (res.status === "needs-admin")
|
|
4991
|
-
|
|
4997
|
+
else if (res.status === "needs-admin") {
|
|
4998
|
+
setMsg({ text: `\u2717 v${res.version} needs admin rights \u2014 run the two commands below`, level: "bad" });
|
|
4999
|
+
setDiag({
|
|
5000
|
+
of: "cli-update",
|
|
5001
|
+
lines: [
|
|
5002
|
+
{ text: "npm\u2019s global folder belongs to root on this machine (the usual macOS", level: "warn" },
|
|
5003
|
+
{ text: "setup), so SolonGate cannot install there by itself. In a terminal:", level: "warn" },
|
|
5004
|
+
...adminUpdateSteps().map((s) => ({ text: s.trim(), level: "ok" })),
|
|
5005
|
+
{ text: "run repair WITHOUT sudo, or the guard hooks land in root\u2019s home", level: "dim" }
|
|
5006
|
+
]
|
|
5007
|
+
});
|
|
5008
|
+
} else if (res.status === "unreachable") setMsg({ text: "\u2717 could not reach the npm registry", level: "bad" });
|
|
4992
5009
|
else setMsg({ text: `\u2717 update to v${res.version} failed \u2014 see ~/.solongate/self-update.log`, level: "bad" });
|
|
4993
5010
|
setLatest(await latestVersion());
|
|
5011
|
+
setCanInstall((await globalInstallCheck()).writable);
|
|
4994
5012
|
} finally {
|
|
4995
5013
|
setUpdBusy(false);
|
|
4996
5014
|
}
|
|
@@ -5004,7 +5022,7 @@ function SettingsPanel({
|
|
|
5004
5022
|
setAutoUpdate(next);
|
|
5005
5023
|
setAutoUp(next);
|
|
5006
5024
|
setMsg(
|
|
5007
|
-
next ? { text: "\u2713 auto-update on \u2014 new versions install in the background", level: "ok" } : { text: "\u2713 auto-update off \u2014 update from this row or with: solongate update", level: "ok" }
|
|
5025
|
+
next ? canInstall === false ? { text: "\u2713 auto-update on \u2014 but npm needs sudo here, so it still cannot install on its own", level: "bad" } : { text: "\u2713 auto-update on \u2014 new versions install in the background", level: "ok" } : { text: "\u2713 auto-update off \u2014 update from this row or with: solongate update", level: "ok" }
|
|
5008
5026
|
);
|
|
5009
5027
|
} else if (r.kind === "ll-enabled") {
|
|
5010
5028
|
if (!local) return;
|
|
@@ -5273,15 +5291,6 @@ function SettingsPanel({
|
|
|
5273
5291
|
cursor(r),
|
|
5274
5292
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
|
|
5275
5293
|
] });
|
|
5276
|
-
case "allowance": {
|
|
5277
|
-
const left = Math.max(0, r.limit - r.used);
|
|
5278
|
-
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
5279
|
-
cursor(r),
|
|
5280
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "allowance".padEnd(11) }),
|
|
5281
|
-
/* @__PURE__ */ jsx8(Text8, { color: left === 0 ? theme.bad : left <= 20 ? theme.warn : theme.ok, bold: true, children: `${r.used}/${r.limit} tool calls` }),
|
|
5282
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: left === 0 ? " used up \u2014 every call is denied \xB7 create an account to carry on" : ` ${left} left on this guest account \xB7 create an account for no limit` })
|
|
5283
|
-
] });
|
|
5284
|
-
}
|
|
5285
5294
|
case "guard":
|
|
5286
5295
|
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
5287
5296
|
cursor(r),
|
|
@@ -5322,7 +5331,10 @@ function SettingsPanel({
|
|
|
5322
5331
|
cursor(r),
|
|
5323
5332
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "version".padEnd(11) }),
|
|
5324
5333
|
/* @__PURE__ */ jsx8(Text8, { color: behind ? theme.warn : theme.ok, children: `v${ver}` }),
|
|
5325
|
-
updBusy ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: ` ${spin} updating\u2026` }) : behind
|
|
5334
|
+
updBusy ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: ` ${spin} updating\u2026` }) : behind && canInstall === false ? (
|
|
5335
|
+
// Said before they press enter, not after a minute of npm noise.
|
|
5336
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` v${latest} on npm \xB7 needs sudo here \xB7 enter shows how` })
|
|
5337
|
+
) : behind ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` v${latest} on npm \xB7 enter updates now` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: latest ? " latest \xB7 enter checks again" : " enter checks npm and updates" })
|
|
5326
5338
|
] });
|
|
5327
5339
|
}
|
|
5328
5340
|
case "auto-update":
|
|
@@ -5330,7 +5342,7 @@ function SettingsPanel({
|
|
|
5330
5342
|
cursor(r),
|
|
5331
5343
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "auto".padEnd(11) }),
|
|
5332
5344
|
onOff(autoUp),
|
|
5333
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: autoUpdateForcedByEnv() ? " set by SOLONGATE_AUTO_UPDATE" : autoUp ? " installs new versions in the background \xB7 enter toggles" : " off: nothing installs on its own (npm -g may need sudo) \xB7 enter toggles" })
|
|
5345
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: autoUpdateForcedByEnv() ? " set by SOLONGATE_AUTO_UPDATE" : canInstall === false ? " cannot work here: npm needs sudo, so updates stay manual \xB7 enter toggles" : autoUp ? " installs new versions in the background \xB7 enter toggles" : " off: nothing installs on its own (npm -g may need sudo) \xB7 enter toggles" })
|
|
5334
5346
|
] });
|
|
5335
5347
|
case "ll-enabled":
|
|
5336
5348
|
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
@@ -5384,7 +5396,7 @@ function SettingsPanel({
|
|
|
5384
5396
|
] });
|
|
5385
5397
|
}
|
|
5386
5398
|
};
|
|
5387
|
-
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add"
|
|
5399
|
+
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" || r.kind === "doctor" || r.kind === "repair" ? "PROTECTION" : r.kind === "cli-update" || r.kind === "auto-update" ? "UPDATES" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
5388
5400
|
const SECTION_DESC = {
|
|
5389
5401
|
ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
|
|
5390
5402
|
PROTECTION: "guard hook: enter install/update \xB7 d remove \xB7 self-protection \xB7 doctor + repair",
|
|
@@ -5429,7 +5441,7 @@ function SettingsPanel({
|
|
|
5429
5441
|
lineKey.push("");
|
|
5430
5442
|
});
|
|
5431
5443
|
}
|
|
5432
|
-
if (diag && (r.kind === "doctor" && diag.of === "doctor" || r.kind === "repair" && diag.of === "repair")) {
|
|
5444
|
+
if (diag && (r.kind === "doctor" && diag.of === "doctor" || r.kind === "repair" && diag.of === "repair" || r.kind === "cli-update" && diag.of === "cli-update")) {
|
|
5433
5445
|
diag.lines.forEach((l, i) => {
|
|
5434
5446
|
lineEls.push(
|
|
5435
5447
|
/* @__PURE__ */ jsx8(
|
|
@@ -5554,23 +5566,7 @@ function App() {
|
|
|
5554
5566
|
const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
|
|
5555
5567
|
const cur = accounts[acctIdx];
|
|
5556
5568
|
const rawEmail = cur?.email ?? "";
|
|
5557
|
-
const
|
|
5558
|
-
const acctLabel2 = !cur ? "not logged in" : isGuestAccount2 ? "Anonymous" : rawEmail || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}`;
|
|
5559
|
-
const [guestSpent, setGuestSpent] = useState9(false);
|
|
5560
|
-
useEffect8(() => {
|
|
5561
|
-
if (!isGuestAccount2) {
|
|
5562
|
-
setGuestSpent(false);
|
|
5563
|
-
return;
|
|
5564
|
-
}
|
|
5565
|
-
let alive = true;
|
|
5566
|
-
api.auth.me().then((r) => {
|
|
5567
|
-
if (alive) setGuestSpent(r.guest?.exhausted === true);
|
|
5568
|
-
}).catch(() => {
|
|
5569
|
-
});
|
|
5570
|
-
return () => {
|
|
5571
|
-
alive = false;
|
|
5572
|
-
};
|
|
5573
|
-
}, [isGuestAccount2, cur?.apiKey]);
|
|
5569
|
+
const acctLabel2 = !cur ? "not logged in" : rawEmail || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}`;
|
|
5574
5570
|
useEffect8(() => {
|
|
5575
5571
|
if (!cur || cur.email) return;
|
|
5576
5572
|
let alive = true;
|
|
@@ -5656,12 +5652,7 @@ function App() {
|
|
|
5656
5652
|
/* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
5657
5653
|
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
|
|
5658
5654
|
/* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
|
|
5659
|
-
locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Settings to unlock the dataroom" }) :
|
|
5660
|
-
// Said on this line rather than on a new one: every panel budgets its
|
|
5661
|
-
// height against a fixed shell, so one more line up here silently clips
|
|
5662
|
-
// a row off whichever panel is open.
|
|
5663
|
-
/* @__PURE__ */ jsx9(Text9, { color: theme.bad, bold: true, children: " \xB7 allowance used up \xB7 every call denied \xB7 create an account to carry on" })
|
|
5664
|
-
) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Settings to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Settings to add another" }),
|
|
5655
|
+
locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Settings to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Settings to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Settings to add another" }),
|
|
5665
5656
|
update2.kind === "updating" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \xB7 restart (q, then solongate) to apply` }) : update2.kind === "available" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} out \xB7 Settings \u2192 UPDATES` }) : update2.kind === "needs-admin" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} needs admin rights \xB7 Settings \u2192 UPDATES` }) : null
|
|
5666
5657
|
] }),
|
|
5667
5658
|
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
|
|
@@ -5710,11 +5701,11 @@ async function launchTui() {
|
|
|
5710
5701
|
return;
|
|
5711
5702
|
}
|
|
5712
5703
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
5713
|
-
const debugLog =
|
|
5704
|
+
const debugLog = join10(homedir10(), ".solongate", "dataroom-debug.log");
|
|
5714
5705
|
const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
|
|
5715
5706
|
const toFile = (level) => (...args) => {
|
|
5716
5707
|
try {
|
|
5717
|
-
mkdirSync7(
|
|
5708
|
+
mkdirSync7(join10(homedir10(), ".solongate"), { recursive: true });
|
|
5718
5709
|
appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
5719
5710
|
`);
|
|
5720
5711
|
} catch {
|
package/hooks/guard.bundled.mjs
CHANGED
|
@@ -6536,7 +6536,7 @@ 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
|
-
var HOOK_VERSION =
|
|
6539
|
+
var HOOK_VERSION = 71;
|
|
6540
6540
|
function localLogsOnly(security) {
|
|
6541
6541
|
if (security && typeof security === "object") {
|
|
6542
6542
|
const l = security.localLogs;
|
|
@@ -7951,53 +7951,10 @@ function rateLimitCheck(agentKey, limits) {
|
|
|
7951
7951
|
return null;
|
|
7952
7952
|
}
|
|
7953
7953
|
}
|
|
7954
|
-
function quotaTally(used) {
|
|
7955
|
-
const dir = resolve(homedir(), ".solongate");
|
|
7956
|
-
const markFile = join(dir, ".quota-mark");
|
|
7957
|
-
const tallyFile = join(dir, ".quota-tally");
|
|
7958
|
-
let n = 0;
|
|
7959
|
-
try {
|
|
7960
|
-
let mark = null;
|
|
7961
|
-
if (existsSync(markFile)) {
|
|
7962
|
-
try {
|
|
7963
|
-
mark = readFileSync(markFile, "utf-8").trim();
|
|
7964
|
-
} catch {
|
|
7965
|
-
}
|
|
7966
|
-
}
|
|
7967
|
-
if (mark !== String(used)) {
|
|
7968
|
-
writeFileSync(markFile, String(used));
|
|
7969
|
-
writeFileSync(tallyFile, "");
|
|
7970
|
-
} else if (existsSync(tallyFile)) {
|
|
7971
|
-
try {
|
|
7972
|
-
n = statSync(tallyFile).size;
|
|
7973
|
-
} catch {
|
|
7974
|
-
}
|
|
7975
|
-
}
|
|
7976
|
-
} catch {
|
|
7977
|
-
}
|
|
7978
|
-
return {
|
|
7979
|
-
n,
|
|
7980
|
-
add() {
|
|
7981
|
-
try {
|
|
7982
|
-
appendFileSync(tallyFile, ".");
|
|
7983
|
-
} catch {
|
|
7984
|
-
}
|
|
7985
|
-
}
|
|
7986
|
-
};
|
|
7987
|
-
}
|
|
7988
7954
|
function securityLayerCheck(toolName, args, cfg, agentKey) {
|
|
7989
7955
|
if (!cfg)
|
|
7990
7956
|
return null;
|
|
7991
7957
|
try {
|
|
7992
|
-
const q = cfg.guestQuota;
|
|
7993
|
-
if (q) {
|
|
7994
|
-
const tally = quotaTally(q.used ?? 0);
|
|
7995
|
-
const spent = (q.used ?? 0) + tally.n;
|
|
7996
|
-
if (q.exhausted || spent >= q.limit) {
|
|
7997
|
-
return `Guest allowance used up (${q.limit} tool calls). Create an account at https://auth.solongate.com to carry on. Your project, its policy and everything recorded carry over, and this device keeps working. To stop guarding this machine instead: solongate, Settings, guard, then press d.`;
|
|
7998
|
-
}
|
|
7999
|
-
tally.add();
|
|
8000
|
-
}
|
|
8001
7958
|
if (cfg.dlpBlock) {
|
|
8002
7959
|
const hit = dlpScan(args, cfg.dlpBlock);
|
|
8003
7960
|
if (hit)
|
package/hooks/guard.mjs
CHANGED
|
@@ -32,7 +32,7 @@ import { createHash } from 'node:crypto';
|
|
|
32
32
|
// the installed hook self-updates when the cloud version is higher (see
|
|
33
33
|
// maybeSelfUpdate). This is what makes guard fixes propagate without a manual
|
|
34
34
|
// reinstall — the same trust model as the OPA WASM this hook already runs.
|
|
35
|
-
const HOOK_VERSION =
|
|
35
|
+
const HOOK_VERSION = 71;
|
|
36
36
|
|
|
37
37
|
// True when local log storage is ON. In that mode logs are kept LOCAL ONLY and
|
|
38
38
|
// nothing is sent to the cloud audit log.
|
|
@@ -1761,62 +1761,10 @@ function rateLimitCheck(agentKey, limits) {
|
|
|
1761
1761
|
}
|
|
1762
1762
|
}
|
|
1763
1763
|
|
|
1764
|
-
// The allowance figure the API sends is built from audit rows, and those are
|
|
1765
|
-
// written AFTER a call runs. Under a burst of parallel tool calls every hook
|
|
1766
|
-
// therefore reads the same pre-burst figure, and the allowance overshoots by
|
|
1767
|
-
// roughly a batch before the count catches up.
|
|
1768
|
-
//
|
|
1769
|
-
// The guard is the thing making the calls, so it can close that gap without
|
|
1770
|
-
// another round trip: it tallies what it has already let through since the
|
|
1771
|
-
// server's figure last moved, and adds it in. One byte is appended per call and
|
|
1772
|
-
// the file's LENGTH is the count, because many hooks run at once and a
|
|
1773
|
-
// read-modify-write would lose increments; an O_APPEND write of a single byte
|
|
1774
|
-
// does not interleave.
|
|
1775
|
-
function quotaTally(used) {
|
|
1776
|
-
const dir = resolve(homedir(), '.solongate');
|
|
1777
|
-
const markFile = join(dir, '.quota-mark');
|
|
1778
|
-
const tallyFile = join(dir, '.quota-tally');
|
|
1779
|
-
let n = 0;
|
|
1780
|
-
try {
|
|
1781
|
-
let mark = null;
|
|
1782
|
-
if (existsSync(markFile)) { try { mark = readFileSync(markFile, 'utf-8').trim(); } catch {} }
|
|
1783
|
-
// A figure that has moved means the audit rows caught up and absorbed the
|
|
1784
|
-
// local tally, so it starts again from there.
|
|
1785
|
-
if (mark !== String(used)) {
|
|
1786
|
-
writeFileSync(markFile, String(used));
|
|
1787
|
-
writeFileSync(tallyFile, '');
|
|
1788
|
-
} else if (existsSync(tallyFile)) {
|
|
1789
|
-
try { n = statSync(tallyFile).size; } catch {}
|
|
1790
|
-
}
|
|
1791
|
-
} catch { /* a missing tally only costs accuracy near the edge, never the gate */ }
|
|
1792
|
-
return {
|
|
1793
|
-
n,
|
|
1794
|
-
add() { try { appendFileSync(tallyFile, '.'); } catch {} },
|
|
1795
|
-
};
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
1764
|
// Runs all enabled enforcement layers; returns a deny reason or null (allow).
|
|
1799
1765
|
function securityLayerCheck(toolName, args, cfg, agentKey) {
|
|
1800
1766
|
if (!cfg) return null;
|
|
1801
1767
|
try {
|
|
1802
|
-
// Guest allowance. A guest account is a trial, so past its allowance every
|
|
1803
|
-
// call is denied without exception rather than quietly running unguarded:
|
|
1804
|
-
// an agent that believes it is protected and is not is worse off than one
|
|
1805
|
-
// that is stopped and told why. Checked first, so no other layer can let a
|
|
1806
|
-
// call through after the allowance is gone.
|
|
1807
|
-
const q = cfg.guestQuota;
|
|
1808
|
-
if (q) {
|
|
1809
|
-
// The server's figure plus what this device has run since, so a burst
|
|
1810
|
-
// cannot spend past the allowance while the audit rows are still in flight.
|
|
1811
|
-
const tally = quotaTally(q.used ?? 0);
|
|
1812
|
-
const spent = (q.used ?? 0) + tally.n;
|
|
1813
|
-
if (q.exhausted || spent >= q.limit) {
|
|
1814
|
-
return `Guest allowance used up (${q.limit} tool calls). Create an account at https://auth.solongate.com to carry on. Your project, its policy and everything recorded carry over, and this device keeps working. To stop guarding this machine instead: solongate, Settings, guard, then press d.`;
|
|
1815
|
-
}
|
|
1816
|
-
// Counted here rather than after the verdict, because a call the policy
|
|
1817
|
-
// goes on to deny is still recorded and still spends the allowance.
|
|
1818
|
-
tally.add();
|
|
1819
|
-
}
|
|
1820
1768
|
if (cfg.dlpBlock) {
|
|
1821
1769
|
const hit = dlpScan(args, cfg.dlpBlock);
|
|
1822
1770
|
if (hit) return 'Security layer (DLP): blocked - arguments contain a ' + hit +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.83.
|
|
3
|
+
"version": "0.83.5",
|
|
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": {
|