@solongate/proxy 0.83.33 → 0.83.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export declare function run(argv: string[]): Promise<number>;
@@ -1008,19 +1008,104 @@ async function run3(argv) {
1008
1008
  }
1009
1009
  }
1010
1010
 
1011
+ // src/commands/ghost.ts
1012
+ var USAGE4 = usage("solongate ghost", "hidden paths", [
1013
+ ["ghost show", "current mode + routes"],
1014
+ ["ghost on", "start hiding the routes"],
1015
+ ["ghost off", "stop hiding them (the routes are kept)"],
1016
+ ["ghost add <glob>", "hide one more path"],
1017
+ ["ghost remove <glob>", "stop hiding one path"]
1018
+ ]);
1019
+ var modeColor3 = (m) => m === "on" ? green(m) : dim("off");
1020
+ var isBlanketGlob = (glob) => glob.replaceAll("*", "").trim() === "";
1021
+ async function run4(argv) {
1022
+ const { positionals, flags } = parse(argv);
1023
+ const sub = positionals[0] ?? "show";
1024
+ const json = flagBool(flags, "json");
1025
+ if (sub === "help") return err(USAGE4), 0;
1026
+ const { layers } = await api.settings.getSecurityLayers();
1027
+ const save = async (next) => (await api.settings.setSecurityLayers(next)).layers;
1028
+ switch (sub) {
1029
+ case "show": {
1030
+ if (json) return printJson(layers.ghost), 0;
1031
+ err("");
1032
+ err(` Ghost mode: ${modeColor3(layers.ghost.mode)}`);
1033
+ if (!layers.ghost.patterns.length) {
1034
+ err(dim("\n No routes. `solongate ghost add <glob>` to hide a path."));
1035
+ return 0;
1036
+ }
1037
+ table(["", "ROUTE"], layers.ghost.patterns.map((r) => [cyan("\u2022"), r]));
1038
+ if (layers.ghost.mode !== "on") {
1039
+ err(dim("\n Ghost is off: these routes are stored but nothing is hidden."));
1040
+ }
1041
+ return 0;
1042
+ }
1043
+ case "on":
1044
+ case "off": {
1045
+ const saved = await save({ ...layers, ghost: { ...layers.ghost, mode: sub } });
1046
+ if (json) return printJson(saved.ghost), 0;
1047
+ err(green(` \u2713 Ghost \u2192 ${saved.ghost.mode}`));
1048
+ if (sub === "on" && !saved.ghost.patterns.length) {
1049
+ err(dim(" No routes yet, so nothing is hidden. `solongate ghost add <glob>`."));
1050
+ }
1051
+ return 0;
1052
+ }
1053
+ case "add": {
1054
+ const glob = positionals.slice(1).join(" ");
1055
+ if (!glob) return err(" Usage: ghost add <glob>"), 1;
1056
+ if (isBlanketGlob(glob)) {
1057
+ err(`${red(" \u2717 ")}"${glob}" would hide every path.`);
1058
+ err(dim(" Ghost routes are globs: `*` is any run of non-whitespace."));
1059
+ err(dim(" Anchor it on something, e.g. *payroll.csv or *internal/*.pem"));
1060
+ return 1;
1061
+ }
1062
+ if (layers.ghost.patterns.includes(glob)) {
1063
+ if (json) return printJson(layers.ghost), 0;
1064
+ return err(green(` \u2713 Route "${glob}" is already hidden`)), 0;
1065
+ }
1066
+ const patterns = [...layers.ghost.patterns, glob];
1067
+ const saved = await save({ ...layers, ghost: { ...layers.ghost, patterns } });
1068
+ if (json) return printJson(saved.ghost), 0;
1069
+ err(green(` \u2713 Hiding "${glob}"`) + dim(` (${saved.ghost.patterns.length} route(s))`));
1070
+ if (saved.ghost.mode !== "on") {
1071
+ err(dim(" Ghost is off. `solongate ghost on` to start hiding."));
1072
+ }
1073
+ return 0;
1074
+ }
1075
+ case "remove": {
1076
+ const glob = positionals.slice(1).join(" ");
1077
+ if (!glob) return err(" Usage: ghost remove <glob>"), 1;
1078
+ if (!layers.ghost.patterns.includes(glob)) {
1079
+ err(` No such route: "${glob}"`);
1080
+ if (layers.ghost.patterns.length) {
1081
+ err(dim(" Current:"));
1082
+ for (const r of layers.ghost.patterns) err(` ${dim("\u2022")} ${r}`);
1083
+ }
1084
+ return 1;
1085
+ }
1086
+ const patterns = layers.ghost.patterns.filter((r) => r !== glob);
1087
+ const saved = await save({ ...layers, ghost: { ...layers.ghost, patterns } });
1088
+ if (json) return printJson(saved.ghost), 0;
1089
+ return err(green(` \u2713 Stopped hiding "${glob}"`) + dim(` (${saved.ghost.patterns.length} route(s) left)`)), 0;
1090
+ }
1091
+ default:
1092
+ return err(USAGE4), 1;
1093
+ }
1094
+ }
1095
+
1011
1096
  // src/commands/stats.ts
1012
- var USAGE4 = usage("solongate stats", "traffic & security stats", [
1097
+ var USAGE5 = usage("solongate stats", "traffic & security stats", [
1013
1098
  ["stats", "overview (totals, recent activity)"],
1014
1099
  ["stats timeseries [--period 24h|7d|30d|all]"],
1015
1100
  ["stats drift [--days N]", "denials rising/falling vs previous window"]
1016
1101
  ]);
1017
- async function run4(argv) {
1102
+ async function run5(argv) {
1018
1103
  const { positionals, flags } = parse(argv);
1019
1104
  const sub = positionals[0] ?? "overview";
1020
1105
  const json = flagBool(flags, "json");
1021
1106
  switch (sub) {
1022
1107
  case "help":
1023
- return err(USAGE4), 0;
1108
+ return err(USAGE5), 0;
1024
1109
  case "overview": {
1025
1110
  const s = await api.stats.get();
1026
1111
  if (json) return printJson(s), 0;
@@ -1073,21 +1158,21 @@ async function run4(argv) {
1073
1158
  return 0;
1074
1159
  }
1075
1160
  default:
1076
- return err(USAGE4), 1;
1161
+ return err(USAGE5), 1;
1077
1162
  }
1078
1163
  }
1079
1164
 
1080
1165
  // src/commands/audit.ts
1081
- var USAGE5 = usage("solongate audit", "audit log", [
1166
+ var USAGE6 = usage("solongate audit", "audit log", [
1082
1167
  ["audit [--filter ALLOW|DENY] [--tool <substr>] [--signal dlp|ratelimit]"],
1083
1168
  [" [--search <text>] [--agent-name <name>] [--limit N]"],
1084
1169
  ["audit whitelist <logId> [--scope exact|tool]", "turn a denied call into an ALLOW rule"],
1085
1170
  ["audit block <logId> [--scope exact|tool]", "turn a call into a DENY rule"]
1086
1171
  ]);
1087
- async function run5(argv) {
1172
+ async function run6(argv) {
1088
1173
  const { positionals, flags } = parse(argv);
1089
1174
  const json = flagBool(flags, "json");
1090
- if (positionals[0] === "help") return err(USAGE5), 0;
1175
+ if (positionals[0] === "help") return err(USAGE6), 0;
1091
1176
  if (positionals[0] === "whitelist") {
1092
1177
  const id = positionals[1];
1093
1178
  if (!id) return err(" Usage: audit whitelist <logId> [--scope exact|tool]"), 1;
@@ -1329,7 +1414,7 @@ async function collectChecks() {
1329
1414
  }
1330
1415
  return checks;
1331
1416
  }
1332
- async function run6(argv) {
1417
+ async function run7(argv) {
1333
1418
  const { flags } = parse(argv);
1334
1419
  const json = flagBool(flags, "json");
1335
1420
  const checks = await collectChecks();
@@ -1377,7 +1462,7 @@ function print(r, json) {
1377
1462
  `${c.dim}[${time(r.at)}]${c.reset} ${src}${c.reset} ${dec}${r.decision.padEnd(6)}${c.reset} ${c.cyan}${trunc(r.tool, 12).padEnd(13)}${c.reset}${c.dim}${r.permission.slice(0, 4).padEnd(5)}${c.reset}${r.dlp ? c.red + "DLP! " + c.reset : ""}${c.dim}${trunc(r.agent || "-", 12).padEnd(13)}${r.detail}${c.reset}`
1378
1463
  );
1379
1464
  }
1380
- async function run7(argv) {
1465
+ async function run8(argv) {
1381
1466
  const { flags } = parse(argv);
1382
1467
  const json = flagBool(flags, "json");
1383
1468
  const decisionFilter = (flagStr(flags, "filter") || "").toUpperCase();
@@ -1455,19 +1540,19 @@ async function run7(argv) {
1455
1540
  }
1456
1541
 
1457
1542
  // src/commands/alerts.ts
1458
- var USAGE6 = usage("solongate alerts", "spike alerts (Telegram / email)", [
1543
+ var USAGE7 = usage("solongate alerts", "spike alerts (Telegram / email)", [
1459
1544
  ["alerts list"],
1460
1545
  ["alerts add --signal deny|dlp|ratelimit|any --threshold N --window S"],
1461
1546
  [" (--email <a> | --telegram <chatId> | --slack <url>)"],
1462
1547
  ["alerts remove <id>"]
1463
1548
  ]);
1464
- async function run8(argv) {
1549
+ async function run9(argv) {
1465
1550
  const { positionals, flags } = parse(argv);
1466
1551
  const sub = positionals[0] ?? "list";
1467
1552
  const json = flagBool(flags, "json");
1468
1553
  switch (sub) {
1469
1554
  case "help":
1470
- return err(USAGE6), 0;
1555
+ return err(USAGE7), 0;
1471
1556
  case "list": {
1472
1557
  const { rules } = await api.settings.getAlerts();
1473
1558
  if (json) return printJson(rules), 0;
@@ -1509,23 +1594,23 @@ async function run8(argv) {
1509
1594
  return err(green(` \u2713 Removed ${id}`)), 0;
1510
1595
  }
1511
1596
  default:
1512
- return err(USAGE6), 1;
1597
+ return err(USAGE7), 1;
1513
1598
  }
1514
1599
  }
1515
1600
 
1516
1601
  // src/commands/webhooks.ts
1517
- var USAGE7 = usage("solongate webhooks", "event webhooks", [
1602
+ var USAGE8 = usage("solongate webhooks", "event webhooks", [
1518
1603
  ["webhooks list"],
1519
1604
  ["webhooks add --url <https://\u2026> [--events denials|allowed|all]"],
1520
1605
  ["webhooks remove <id>"]
1521
1606
  ]);
1522
- async function run9(argv) {
1607
+ async function run10(argv) {
1523
1608
  const { positionals, flags } = parse(argv);
1524
1609
  const sub = positionals[0] ?? "list";
1525
1610
  const json = flagBool(flags, "json");
1526
1611
  switch (sub) {
1527
1612
  case "help":
1528
- return err(USAGE7), 0;
1613
+ return err(USAGE8), 0;
1529
1614
  case "list": {
1530
1615
  const { webhooks } = await api.settings.getWebhooks();
1531
1616
  if (json) return printJson(webhooks), 0;
@@ -1551,7 +1636,7 @@ async function run9(argv) {
1551
1636
  return err(green(` \u2713 Removed ${id}`)), 0;
1552
1637
  }
1553
1638
  default:
1554
- return err(USAGE7), 1;
1639
+ return err(USAGE8), 1;
1555
1640
  }
1556
1641
  }
1557
1642
 
@@ -1564,22 +1649,24 @@ async function dispatch(command, argv) {
1564
1649
  return run2(argv);
1565
1650
  case "dlp":
1566
1651
  return run3(argv);
1567
- case "stats":
1652
+ case "ghost":
1568
1653
  return run4(argv);
1569
- case "audit":
1654
+ case "stats":
1570
1655
  return run5(argv);
1656
+ case "audit":
1657
+ return run6(argv);
1571
1658
  case "sessions":
1572
1659
  return runAgents(argv);
1573
1660
  case "session":
1574
1661
  return runAgent(argv);
1575
1662
  case "doctor":
1576
- return run6(argv);
1577
- case "watch":
1578
1663
  return run7(argv);
1579
- case "alerts":
1664
+ case "watch":
1580
1665
  return run8(argv);
1581
- case "webhooks":
1666
+ case "alerts":
1582
1667
  return run9(argv);
1668
+ case "webhooks":
1669
+ return run10(argv);
1583
1670
  default:
1584
1671
  err(` Unknown command: ${command}`);
1585
1672
  return 1;
package/dist/index.js CHANGED
@@ -9351,7 +9351,7 @@ function LivePanel({ active: active2 }) {
9351
9351
  ] });
9352
9352
  }
9353
9353
  if (mode === "layers") {
9354
- const modeColor4 = (m) => m === "block" || m === "on" ? theme.ok : m === "detect" ? theme.warn : theme.dim;
9354
+ const modeColor5 = (m) => m === "block" || m === "on" ? theme.ok : m === "detect" ? theme.warn : theme.dim;
9355
9355
  const barW = Math.max(10, Math.min(40, innerW - 30));
9356
9356
  const burstsInBuf = mergedAll.filter((e) => e.burst).length;
9357
9357
  const dlpInBuf = mergedAll.filter((e) => e.dlp).length;
@@ -9366,7 +9366,7 @@ function LivePanel({ active: active2 }) {
9366
9366
  "\u258E",
9367
9367
  label.padEnd(10)
9368
9368
  ] }),
9369
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: modeColor4(m), children: (m ?? "?").padEnd(8) }),
9369
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: modeColor5(m), children: (m ?? "?").padEnd(8) }),
9370
9370
  note ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: note }) : null
9371
9371
  ] })
9372
9372
  );
@@ -9755,7 +9755,7 @@ function DryRunPanel({ focused }) {
9755
9755
  const [off, setOff] = useState3(0);
9756
9756
  const { cols, rows } = usePanelSize();
9757
9757
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
9758
- function run10(policyIdx = pi, lim = limit) {
9758
+ function run11(policyIdx = pi, lim = limit) {
9759
9759
  const p = policies[policyIdx];
9760
9760
  if (!p) return;
9761
9761
  setRunning(true);
@@ -9773,7 +9773,7 @@ function DryRunPanel({ focused }) {
9773
9773
  pendingPolicyId = null;
9774
9774
  }
9775
9775
  setPi(idx);
9776
- run10(idx, limit);
9776
+ run11(idx, limit);
9777
9777
  }, [policies.length]);
9778
9778
  useInput2((input, key) => {
9779
9779
  if (!focused || editLogs !== null) return;
@@ -9782,10 +9782,10 @@ function DryRunPanel({ focused }) {
9782
9782
  else if (input === "p" || input === "P") {
9783
9783
  const n = policies.length ? (pi + 1) % policies.length : 0;
9784
9784
  setPi(n);
9785
- run10(n, limit);
9785
+ run11(n, limit);
9786
9786
  } else if (input === "n" || input === "N") {
9787
9787
  setEditLogs(String(limit));
9788
- } else if (input === "r" || input === "R") run10(pi, limit);
9788
+ } else if (input === "r" || input === "R") run11(pi, limit);
9789
9789
  });
9790
9790
  function commitLogs(raw) {
9791
9791
  const cap = Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT);
@@ -9793,7 +9793,7 @@ function DryRunPanel({ focused }) {
9793
9793
  const next = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, cap) : limit;
9794
9794
  setEditLogs(null);
9795
9795
  setLimit(next);
9796
- run10(pi, next);
9796
+ run11(pi, next);
9797
9797
  }
9798
9798
  const w = Math.max(40, cols);
9799
9799
  const lines = [];
@@ -11090,7 +11090,7 @@ function AuditPanel({ active: active2, focused }) {
11090
11090
  const sessLoading = source === "cloud" ? agentsQ.loading : localQ.loading;
11091
11091
  const doDelete = (kind) => {
11092
11092
  setMsg({ text: "deleting\u2026", level: "ok" });
11093
- const run10 = async () => {
11093
+ const run11 = async () => {
11094
11094
  if (source === "cloud") {
11095
11095
  if (kind === "one") {
11096
11096
  if (!current) throw new Error("nothing selected");
@@ -11106,14 +11106,14 @@ function AuditPanel({ active: active2, focused }) {
11106
11106
  localQ.reload();
11107
11107
  }
11108
11108
  };
11109
- run10().then(() => {
11109
+ run11().then(() => {
11110
11110
  setMsg({ text: kind === "one" ? "\u2713 entry deleted" : `\u2713 ALL ${source} logs deleted`, level: "ok" });
11111
11111
  toTop();
11112
11112
  }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
11113
11113
  };
11114
11114
  const doExport = (kind) => {
11115
11115
  setMsg({ text: "exporting\u2026", level: "ok" });
11116
- const run10 = async () => {
11116
+ const run11 = async () => {
11117
11117
  const dir = join11(homedir9(), ".solongate");
11118
11118
  const file = join11(dir, `audit-export-${source}.jsonl`);
11119
11119
  let rows2;
@@ -11126,7 +11126,7 @@ function AuditPanel({ active: active2, focused }) {
11126
11126
  writeFileSync9(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
11127
11127
  return { n: rows2.length, file };
11128
11128
  };
11129
- run10().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
11129
+ run11().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
11130
11130
  };
11131
11131
  useInput6(
11132
11132
  (input, key) => {
@@ -12008,7 +12008,7 @@ function SettingsPanel({
12008
12008
  guardQ.reload();
12009
12009
  selfQ.reload();
12010
12010
  };
12011
- const run10 = (label, fn, reload) => {
12011
+ const run11 = (label, fn, reload) => {
12012
12012
  if (busy) return;
12013
12013
  setBusy(true);
12014
12014
  setMsg({ text: label + "\u2026", level: "ok" });
@@ -12090,8 +12090,8 @@ function SettingsPanel({
12090
12090
  const body = { signal: editor.signal, threshold: editor.threshold, windowSeconds: editor.windowSeconds, enabled: editor.enabled, ...channels };
12091
12091
  const id = editor.id;
12092
12092
  setEditor(null);
12093
- if (id) run10("alert updated", () => api.settings.updateAlert(id, body), alertQ.reload);
12094
- else run10(`${editor.channel} alert added`, () => api.settings.createAlert({ name: "SolonGate alert", ...body }), alertQ.reload);
12093
+ if (id) run11("alert updated", () => api.settings.updateAlert(id, body), alertQ.reload);
12094
+ else run11(`${editor.channel} alert added`, () => api.settings.createAlert({ name: "SolonGate alert", ...body }), alertQ.reload);
12095
12095
  };
12096
12096
  const activate = (r) => {
12097
12097
  if (r.kind === "acct") {
@@ -12165,7 +12165,7 @@ function SettingsPanel({
12165
12165
  })();
12166
12166
  } else if (r.kind === "self") {
12167
12167
  if (!selfProt) return;
12168
- run10(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
12168
+ run11(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
12169
12169
  } else if (r.kind === "cli-update") {
12170
12170
  if (updBusy) return;
12171
12171
  setUpdBusy(true);
@@ -12212,7 +12212,7 @@ function SettingsPanel({
12212
12212
  setMsg({ text: "set a path first (\u2193 then enter)", level: "bad" });
12213
12213
  return;
12214
12214
  }
12215
- run10(local.enabled ? "local logs disabled" : "local logs enabled", () => api.settings.setLocalLogs({ enabled: !local.enabled, path: local.path }), localQ.reload);
12215
+ run11(local.enabled ? "local logs disabled" : "local logs enabled", () => api.settings.setLocalLogs({ enabled: !local.enabled, path: local.path }), localQ.reload);
12216
12216
  } else if (r.kind === "ll-path") {
12217
12217
  setInput(local?.path ?? "");
12218
12218
  setEditing("path");
@@ -12223,7 +12223,7 @@ function SettingsPanel({
12223
12223
  next.running ? { text: `\u2713 dashboard link running on 127.0.0.1:${next.port} \u2014 keeps running after you close the dataroom`, level: "ok" } : { text: "\u2713 dashboard link stopped (disabled until you start it again)", level: "ok" }
12224
12224
  );
12225
12225
  } else if (r.kind === "wh") {
12226
- run10(r.wh.enabled ? "webhook disabled" : "webhook enabled", () => api.settings.updateWebhook(r.wh.id, { enabled: !r.wh.enabled }), whQ.reload);
12226
+ run11(r.wh.enabled ? "webhook disabled" : "webhook enabled", () => api.settings.updateWebhook(r.wh.id, { enabled: !r.wh.enabled }), whQ.reload);
12227
12227
  } else if (r.kind === "wh-add") {
12228
12228
  setInput("");
12229
12229
  setEditing("wh-url");
@@ -12242,7 +12242,7 @@ function SettingsPanel({
12242
12242
  setEditing(null);
12243
12243
  if (which === "path") {
12244
12244
  const enabled = (local?.enabled ?? false) && v.length > 0;
12245
- run10(v ? `path saved${enabled ? "" : " (press enter on enabled to turn on)"}` : "path cleared (local logs off)", () => api.settings.setLocalLogs({ enabled, path: v }), localQ.reload);
12245
+ run11(v ? `path saved${enabled ? "" : " (press enter on enabled to turn on)"}` : "path cleared (local logs off)", () => api.settings.setLocalLogs({ enabled, path: v }), localQ.reload);
12246
12246
  } else if (which === "wh-url") {
12247
12247
  const url = v.replace(/^["'<]+|["'>]+$/g, "").trim();
12248
12248
  if (!url) return;
@@ -12250,7 +12250,7 @@ function SettingsPanel({
12250
12250
  setMsg({ text: "\u2717 webhook url must start with http:// or https://", level: "bad" });
12251
12251
  return;
12252
12252
  }
12253
- run10("webhook added", () => api.settings.createWebhook({ url, events: "denials" }), whQ.reload);
12253
+ run11("webhook added", () => api.settings.createWebhook({ url, events: "denials" }), whQ.reload);
12254
12254
  } else if (which === "alert-target" && editor) {
12255
12255
  setEditor({ ...editor, target: v, field: 1 });
12256
12256
  }
@@ -12305,7 +12305,7 @@ function SettingsPanel({
12305
12305
  setMsg(ok ? { text: `\u2713 ${acctLabel(cur.acc)} is now the ACTIVE key (guard + logging)`, level: "ok" } : { text: "\u2717 could not set active", level: "bad" });
12306
12306
  refreshAccounts();
12307
12307
  } else if (cur.kind === "wh" || cur.kind === "alert" || cur.kind === "self" || cur.kind === "auto-update" || cur.kind === "ll-enabled" || cur.kind === "ll-server") {
12308
- if (cur.kind === "alert") run10(cur.rule.enabled ? "alert disabled" : "alert enabled", () => api.settings.setAlertEnabled(cur.rule.id, !cur.rule.enabled), alertQ.reload);
12308
+ if (cur.kind === "alert") run11(cur.rule.enabled ? "alert disabled" : "alert enabled", () => api.settings.setAlertEnabled(cur.rule.id, !cur.rule.enabled), alertQ.reload);
12309
12309
  else activate(cur);
12310
12310
  }
12311
12311
  } else if (inp === "x" && cur.kind === "acct") {
@@ -12336,13 +12336,13 @@ function SettingsPanel({
12336
12336
  refreshAccounts();
12337
12337
  onAccountsChanged?.();
12338
12338
  } else if (inp === "t" && cur.kind === "wh") {
12339
- run10("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
12339
+ run11("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
12340
12340
  if (!r.delivered) throw new Error("endpoint rejected the test (non-2xx)");
12341
12341
  }), whQ.reload);
12342
12342
  } else if (inp === "e" && cur.kind === "ll-path") activate(cur);
12343
12343
  else if (inp === "e" && cur.kind === "wh") {
12344
12344
  const next = EVENTS[(EVENTS.indexOf(cur.wh.events) + 1) % EVENTS.length];
12345
- run10(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
12345
+ run11(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
12346
12346
  } else if (inp === "e" && cur.kind === "alert") {
12347
12347
  const ch = alertChannel(cur.rule);
12348
12348
  if (ch) openAlertEditor(ch, cur.rule);
@@ -12379,10 +12379,10 @@ function SettingsPanel({
12379
12379
  setConfirmDel(null);
12380
12380
  if (cur.kind === "wh") {
12381
12381
  const id = cur.wh.id;
12382
- run10("webhook deleted", () => api.settings.deleteWebhook(id).then(() => setHidden((h) => new Set(h).add("wh:" + id))), whQ.reload);
12382
+ run11("webhook deleted", () => api.settings.deleteWebhook(id).then(() => setHidden((h) => new Set(h).add("wh:" + id))), whQ.reload);
12383
12383
  } else {
12384
12384
  const id = cur.rule.id;
12385
- run10("alert deleted", () => api.settings.deleteAlert(id).then(() => setHidden((h) => new Set(h).add("alert:" + id))), alertQ.reload);
12385
+ run11("alert deleted", () => api.settings.deleteAlert(id).then(() => setHidden((h) => new Set(h).add("alert:" + id))), alertQ.reload);
12386
12386
  }
12387
12387
  } else if (inp === "r") reloadAll();
12388
12388
  },
@@ -12770,9 +12770,9 @@ function App() {
12770
12770
  const [update2, setUpdate] = useState9({ kind: "idle" });
12771
12771
  useEffect8(() => {
12772
12772
  let alive = true;
12773
- const run10 = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
12774
- run10();
12775
- const t = setInterval(run10, 30 * 6e4);
12773
+ const run11 = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
12774
+ run11();
12775
+ const t = setInterval(run11, 30 * 6e4);
12776
12776
  return () => {
12777
12777
  alive = false;
12778
12778
  clearInterval(t);
@@ -13341,14 +13341,108 @@ var init_dlp = __esm({
13341
13341
  }
13342
13342
  });
13343
13343
 
13344
- // src/commands/stats.ts
13344
+ // src/commands/ghost.ts
13345
13345
  async function run5(argv) {
13346
+ const { positionals, flags } = parse(argv);
13347
+ const sub = positionals[0] ?? "show";
13348
+ const json = flagBool(flags, "json");
13349
+ if (sub === "help") return err(USAGE4), 0;
13350
+ const { layers } = await api.settings.getSecurityLayers();
13351
+ const save = async (next) => (await api.settings.setSecurityLayers(next)).layers;
13352
+ switch (sub) {
13353
+ case "show": {
13354
+ if (json) return printJson(layers.ghost), 0;
13355
+ err("");
13356
+ err(` Ghost mode: ${modeColor4(layers.ghost.mode)}`);
13357
+ if (!layers.ghost.patterns.length) {
13358
+ err(dim("\n No routes. `solongate ghost add <glob>` to hide a path."));
13359
+ return 0;
13360
+ }
13361
+ table(["", "ROUTE"], layers.ghost.patterns.map((r) => [cyan("\u2022"), r]));
13362
+ if (layers.ghost.mode !== "on") {
13363
+ err(dim("\n Ghost is off: these routes are stored but nothing is hidden."));
13364
+ }
13365
+ return 0;
13366
+ }
13367
+ case "on":
13368
+ case "off": {
13369
+ const saved = await save({ ...layers, ghost: { ...layers.ghost, mode: sub } });
13370
+ if (json) return printJson(saved.ghost), 0;
13371
+ err(green(` \u2713 Ghost \u2192 ${saved.ghost.mode}`));
13372
+ if (sub === "on" && !saved.ghost.patterns.length) {
13373
+ err(dim(" No routes yet, so nothing is hidden. `solongate ghost add <glob>`."));
13374
+ }
13375
+ return 0;
13376
+ }
13377
+ case "add": {
13378
+ const glob = positionals.slice(1).join(" ");
13379
+ if (!glob) return err(" Usage: ghost add <glob>"), 1;
13380
+ if (isBlanketGlob(glob)) {
13381
+ err(`${red(" \u2717 ")}"${glob}" would hide every path.`);
13382
+ err(dim(" Ghost routes are globs: `*` is any run of non-whitespace."));
13383
+ err(dim(" Anchor it on something, e.g. *payroll.csv or *internal/*.pem"));
13384
+ return 1;
13385
+ }
13386
+ if (layers.ghost.patterns.includes(glob)) {
13387
+ if (json) return printJson(layers.ghost), 0;
13388
+ return err(green(` \u2713 Route "${glob}" is already hidden`)), 0;
13389
+ }
13390
+ const patterns = [...layers.ghost.patterns, glob];
13391
+ const saved = await save({ ...layers, ghost: { ...layers.ghost, patterns } });
13392
+ if (json) return printJson(saved.ghost), 0;
13393
+ err(green(` \u2713 Hiding "${glob}"`) + dim(` (${saved.ghost.patterns.length} route(s))`));
13394
+ if (saved.ghost.mode !== "on") {
13395
+ err(dim(" Ghost is off. `solongate ghost on` to start hiding."));
13396
+ }
13397
+ return 0;
13398
+ }
13399
+ case "remove": {
13400
+ const glob = positionals.slice(1).join(" ");
13401
+ if (!glob) return err(" Usage: ghost remove <glob>"), 1;
13402
+ if (!layers.ghost.patterns.includes(glob)) {
13403
+ err(` No such route: "${glob}"`);
13404
+ if (layers.ghost.patterns.length) {
13405
+ err(dim(" Current:"));
13406
+ for (const r of layers.ghost.patterns) err(` ${dim("\u2022")} ${r}`);
13407
+ }
13408
+ return 1;
13409
+ }
13410
+ const patterns = layers.ghost.patterns.filter((r) => r !== glob);
13411
+ const saved = await save({ ...layers, ghost: { ...layers.ghost, patterns } });
13412
+ if (json) return printJson(saved.ghost), 0;
13413
+ return err(green(` \u2713 Stopped hiding "${glob}"`) + dim(` (${saved.ghost.patterns.length} route(s) left)`)), 0;
13414
+ }
13415
+ default:
13416
+ return err(USAGE4), 1;
13417
+ }
13418
+ }
13419
+ var USAGE4, modeColor4, isBlanketGlob;
13420
+ var init_ghost = __esm({
13421
+ "src/commands/ghost.ts"() {
13422
+ "use strict";
13423
+ init_api_client();
13424
+ init_args();
13425
+ init_format();
13426
+ USAGE4 = usage("solongate ghost", "hidden paths", [
13427
+ ["ghost show", "current mode + routes"],
13428
+ ["ghost on", "start hiding the routes"],
13429
+ ["ghost off", "stop hiding them (the routes are kept)"],
13430
+ ["ghost add <glob>", "hide one more path"],
13431
+ ["ghost remove <glob>", "stop hiding one path"]
13432
+ ]);
13433
+ modeColor4 = (m) => m === "on" ? green(m) : dim("off");
13434
+ isBlanketGlob = (glob) => glob.replaceAll("*", "").trim() === "";
13435
+ }
13436
+ });
13437
+
13438
+ // src/commands/stats.ts
13439
+ async function run6(argv) {
13346
13440
  const { positionals, flags } = parse(argv);
13347
13441
  const sub = positionals[0] ?? "overview";
13348
13442
  const json = flagBool(flags, "json");
13349
13443
  switch (sub) {
13350
13444
  case "help":
13351
- return err(USAGE4), 0;
13445
+ return err(USAGE5), 0;
13352
13446
  case "overview": {
13353
13447
  const s = await api.stats.get();
13354
13448
  if (json) return printJson(s), 0;
@@ -13401,17 +13495,17 @@ async function run5(argv) {
13401
13495
  return 0;
13402
13496
  }
13403
13497
  default:
13404
- return err(USAGE4), 1;
13498
+ return err(USAGE5), 1;
13405
13499
  }
13406
13500
  }
13407
- var USAGE4;
13501
+ var USAGE5;
13408
13502
  var init_stats2 = __esm({
13409
13503
  "src/commands/stats.ts"() {
13410
13504
  "use strict";
13411
13505
  init_api_client();
13412
13506
  init_args();
13413
13507
  init_format();
13414
- USAGE4 = usage("solongate stats", "traffic & security stats", [
13508
+ USAGE5 = usage("solongate stats", "traffic & security stats", [
13415
13509
  ["stats", "overview (totals, recent activity)"],
13416
13510
  ["stats timeseries [--period 24h|7d|30d|all]"],
13417
13511
  ["stats drift [--days N]", "denials rising/falling vs previous window"]
@@ -13420,10 +13514,10 @@ var init_stats2 = __esm({
13420
13514
  });
13421
13515
 
13422
13516
  // src/commands/audit.ts
13423
- async function run6(argv) {
13517
+ async function run7(argv) {
13424
13518
  const { positionals, flags } = parse(argv);
13425
13519
  const json = flagBool(flags, "json");
13426
- if (positionals[0] === "help") return err(USAGE5), 0;
13520
+ if (positionals[0] === "help") return err(USAGE6), 0;
13427
13521
  if (positionals[0] === "whitelist") {
13428
13522
  const id = positionals[1];
13429
13523
  if (!id) return err(" Usage: audit whitelist <logId> [--scope exact|tool]"), 1;
@@ -13471,14 +13565,14 @@ async function run6(argv) {
13471
13565
  );
13472
13566
  return 0;
13473
13567
  }
13474
- var USAGE5;
13568
+ var USAGE6;
13475
13569
  var init_audit2 = __esm({
13476
13570
  "src/commands/audit.ts"() {
13477
13571
  "use strict";
13478
13572
  init_api_client();
13479
13573
  init_args();
13480
13574
  init_format();
13481
- USAGE5 = usage("solongate audit", "audit log", [
13575
+ USAGE6 = usage("solongate audit", "audit log", [
13482
13576
  ["audit [--filter ALLOW|DENY] [--tool <substr>] [--signal dlp|ratelimit]"],
13483
13577
  [" [--search <text>] [--agent-name <name>] [--limit N]"],
13484
13578
  ["audit whitelist <logId> [--scope exact|tool]", "turn a denied call into an ALLOW rule"],
@@ -13574,7 +13668,7 @@ function print(r, json) {
13574
13668
  `${c.dim}[${time(r.at)}]${c.reset} ${src}${c.reset} ${dec}${r.decision.padEnd(6)}${c.reset} ${c.cyan}${trunc(r.tool, 12).padEnd(13)}${c.reset}${c.dim}${r.permission.slice(0, 4).padEnd(5)}${c.reset}${r.dlp ? c.red + "DLP! " + c.reset : ""}${c.dim}${trunc(r.agent || "-", 12).padEnd(13)}${r.detail}${c.reset}`
13575
13669
  );
13576
13670
  }
13577
- async function run7(argv) {
13671
+ async function run8(argv) {
13578
13672
  const { flags } = parse(argv);
13579
13673
  const json = flagBool(flags, "json");
13580
13674
  const decisionFilter = (flagStr(flags, "filter") || "").toUpperCase();
@@ -13665,13 +13759,13 @@ var init_watch = __esm({
13665
13759
  });
13666
13760
 
13667
13761
  // src/commands/alerts.ts
13668
- async function run8(argv) {
13762
+ async function run9(argv) {
13669
13763
  const { positionals, flags } = parse(argv);
13670
13764
  const sub = positionals[0] ?? "list";
13671
13765
  const json = flagBool(flags, "json");
13672
13766
  switch (sub) {
13673
13767
  case "help":
13674
- return err(USAGE6), 0;
13768
+ return err(USAGE7), 0;
13675
13769
  case "list": {
13676
13770
  const { rules } = await api.settings.getAlerts();
13677
13771
  if (json) return printJson(rules), 0;
@@ -13713,17 +13807,17 @@ async function run8(argv) {
13713
13807
  return err(green(` \u2713 Removed ${id}`)), 0;
13714
13808
  }
13715
13809
  default:
13716
- return err(USAGE6), 1;
13810
+ return err(USAGE7), 1;
13717
13811
  }
13718
13812
  }
13719
- var USAGE6;
13813
+ var USAGE7;
13720
13814
  var init_alerts = __esm({
13721
13815
  "src/commands/alerts.ts"() {
13722
13816
  "use strict";
13723
13817
  init_api_client();
13724
13818
  init_args();
13725
13819
  init_format();
13726
- USAGE6 = usage("solongate alerts", "spike alerts (Telegram / email)", [
13820
+ USAGE7 = usage("solongate alerts", "spike alerts (Telegram / email)", [
13727
13821
  ["alerts list"],
13728
13822
  ["alerts add --signal deny|dlp|ratelimit|any --threshold N --window S"],
13729
13823
  [" (--email <a> | --telegram <chatId> | --slack <url>)"],
@@ -13733,13 +13827,13 @@ var init_alerts = __esm({
13733
13827
  });
13734
13828
 
13735
13829
  // src/commands/webhooks.ts
13736
- async function run9(argv) {
13830
+ async function run10(argv) {
13737
13831
  const { positionals, flags } = parse(argv);
13738
13832
  const sub = positionals[0] ?? "list";
13739
13833
  const json = flagBool(flags, "json");
13740
13834
  switch (sub) {
13741
13835
  case "help":
13742
- return err(USAGE7), 0;
13836
+ return err(USAGE8), 0;
13743
13837
  case "list": {
13744
13838
  const { webhooks } = await api.settings.getWebhooks();
13745
13839
  if (json) return printJson(webhooks), 0;
@@ -13765,17 +13859,17 @@ async function run9(argv) {
13765
13859
  return err(green(` \u2713 Removed ${id}`)), 0;
13766
13860
  }
13767
13861
  default:
13768
- return err(USAGE7), 1;
13862
+ return err(USAGE8), 1;
13769
13863
  }
13770
13864
  }
13771
- var USAGE7;
13865
+ var USAGE8;
13772
13866
  var init_webhooks = __esm({
13773
13867
  "src/commands/webhooks.ts"() {
13774
13868
  "use strict";
13775
13869
  init_api_client();
13776
13870
  init_args();
13777
13871
  init_format();
13778
- USAGE7 = usage("solongate webhooks", "event webhooks", [
13872
+ USAGE8 = usage("solongate webhooks", "event webhooks", [
13779
13873
  ["webhooks list"],
13780
13874
  ["webhooks add --url <https://\u2026> [--events denials|allowed|all]"],
13781
13875
  ["webhooks remove <id>"]
@@ -13797,10 +13891,12 @@ async function dispatch(command, argv) {
13797
13891
  return run3(argv);
13798
13892
  case "dlp":
13799
13893
  return run4(argv);
13800
- case "stats":
13894
+ case "ghost":
13801
13895
  return run5(argv);
13802
- case "audit":
13896
+ case "stats":
13803
13897
  return run6(argv);
13898
+ case "audit":
13899
+ return run7(argv);
13804
13900
  case "sessions":
13805
13901
  return runAgents(argv);
13806
13902
  case "session":
@@ -13808,11 +13904,11 @@ async function dispatch(command, argv) {
13808
13904
  case "doctor":
13809
13905
  return run(argv);
13810
13906
  case "watch":
13811
- return run7(argv);
13812
- case "alerts":
13813
13907
  return run8(argv);
13814
- case "webhooks":
13908
+ case "alerts":
13815
13909
  return run9(argv);
13910
+ case "webhooks":
13911
+ return run10(argv);
13816
13912
  default:
13817
13913
  err(` Unknown command: ${command}`);
13818
13914
  return 1;
@@ -13844,6 +13940,7 @@ var init_commands = __esm({
13844
13940
  init_policy();
13845
13941
  init_ratelimit();
13846
13942
  init_dlp();
13943
+ init_ghost();
13847
13944
  init_stats2();
13848
13945
  init_audit2();
13849
13946
  init_agents2();
@@ -17440,7 +17537,7 @@ ${msg.content.text}`;
17440
17537
 
17441
17538
  // src/index.ts
17442
17539
  init_cli_utils();
17443
- var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["update", "repair", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks", "dataroom"]);
17540
+ var CLI_SUBCOMMANDS = /* @__PURE__ */ new Set(["update", "repair", "logs-server", "local-logs", "policy", "ratelimit", "dlp", "ghost", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks", "dataroom"]);
17444
17541
  var CLI_INFO_ARGS = /* @__PURE__ */ new Set(["login", "help", "--help", "-h", "--version", "-v", "version"]);
17445
17542
  var IS_HUMAN_CLI = process.argv.length <= 2 || CLI_SUBCOMMANDS.has(process.argv[2] ?? "") || CLI_INFO_ARGS.has(process.argv[2] ?? "");
17446
17543
  if (!IS_HUMAN_CLI) {
@@ -17531,6 +17628,11 @@ function printHelp() {
17531
17628
  cmd("dlp disable <pattern>", "disable a built-in pattern");
17532
17629
  cmd("dlp add-custom --name X --re <regex>", "add a custom pattern");
17533
17630
  cmd("dlp remove-custom <name>", "remove a custom pattern");
17631
+ head("Ghost (hidden paths)");
17632
+ cmd("ghost show", "current mode + routes");
17633
+ cmd("ghost on | off", "start or stop hiding the routes");
17634
+ cmd("ghost add <glob>", "hide one more path");
17635
+ cmd("ghost remove <glob>", "stop hiding one path");
17534
17636
  head("Monitoring");
17535
17637
  cmd("audit [--filter ALLOW|DENY] [--tool <s>] [--signal dlp|ratelimit] [--limit N]", "browse the audit log");
17536
17638
  cmd("audit whitelist <logId> [--scope exact|tool]", "turn a denial into an ALLOW rule");
@@ -17629,7 +17731,7 @@ async function main() {
17629
17731
  await launchTui2();
17630
17732
  return;
17631
17733
  }
17632
- const MGMT_COMMANDS = /* @__PURE__ */ new Set(["policy", "ratelimit", "dlp", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks"]);
17734
+ const MGMT_COMMANDS = /* @__PURE__ */ new Set(["policy", "ratelimit", "dlp", "ghost", "stats", "audit", "sessions", "session", "doctor", "watch", "alerts", "webhooks"]);
17633
17735
  if (MGMT_COMMANDS.has(subcommand ?? "")) {
17634
17736
  const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
17635
17737
  const code = await runCommand2(subcommand, process.argv.slice(3));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.83.33",
3
+ "version": "0.83.34",
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": {
@@ -61,12 +61,12 @@
61
61
  "node": ">=20.0.0"
62
62
  },
63
63
  "optionalDependencies": {
64
- "@solongate/guard-linux-x64": "0.83.33",
65
- "@solongate/guard-linux-arm64": "0.83.33",
66
- "@solongate/guard-darwin-x64": "0.83.33",
67
- "@solongate/guard-darwin-arm64": "0.83.33",
68
- "@solongate/guard-win32-x64": "0.83.33",
69
- "@solongate/guard-win32-arm64": "0.83.33"
64
+ "@solongate/guard-linux-x64": "0.83.34",
65
+ "@solongate/guard-linux-arm64": "0.83.34",
66
+ "@solongate/guard-darwin-x64": "0.83.34",
67
+ "@solongate/guard-darwin-arm64": "0.83.34",
68
+ "@solongate/guard-win32-x64": "0.83.34",
69
+ "@solongate/guard-win32-arm64": "0.83.34"
70
70
  },
71
71
  "dependencies": {
72
72
  "@modelcontextprotocol/sdk": "^1.26.0",