@solongate/proxy 0.81.86 → 0.81.88

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/index.js CHANGED
@@ -9087,16 +9087,8 @@ var init_Policies = __esm({
9087
9087
  import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
9088
9088
  import { useEffect as useEffect4, useState as useState4 } from "react";
9089
9089
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
9090
- function ColoredSpark({ values, limit, width: width2 = 60 }) {
9091
- const v = values.length > width2 ? values.slice(values.length - width2) : values;
9092
- const max = Math.max(1, ...v);
9093
- return /* @__PURE__ */ jsx4(Text4, { children: v.map((n, i) => {
9094
- const ch = BLOCKS[Math.min(BLOCKS.length - 1, Math.round(n / max * (BLOCKS.length - 1)))];
9095
- const over = limit > 0 && n > limit;
9096
- return /* @__PURE__ */ jsx4(Text4, { color: over ? theme.bad : theme.accent, children: ch }, i);
9097
- }) });
9098
- }
9099
9090
  function RateLimitPanel({ focused }) {
9091
+ const { cols, rows } = usePanelSize();
9100
9092
  const layersQ = useLoader(() => api.settings.getSecurityLayers());
9101
9093
  const historyQ = useLoader(() => api.settings.getRateLimitHistory());
9102
9094
  const insightsQ = useLoader(() => api.stats.securityInsights(7));
@@ -9110,12 +9102,8 @@ function RateLimitPanel({ focused }) {
9110
9102
  const adjust = (dir, step) => {
9111
9103
  if (!draft) return;
9112
9104
  const field = FIELDS2[fi];
9113
- if (field === "mode") {
9114
- const idx = (MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length;
9115
- setDraft({ ...draft, mode: MODES[idx] });
9116
- } else {
9117
- setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
9118
- }
9105
+ if (field === "mode") setDraft({ ...draft, mode: MODES[(MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length] });
9106
+ else setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
9119
9107
  setDirty(true);
9120
9108
  setStatus(null);
9121
9109
  };
@@ -9128,6 +9116,7 @@ function RateLimitPanel({ focused }) {
9128
9116
  setDirty(false);
9129
9117
  setStatus("\u2713 Saved");
9130
9118
  historyQ.reload();
9119
+ insightsQ.reload();
9131
9120
  } catch (e) {
9132
9121
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
9133
9122
  }
@@ -9144,86 +9133,75 @@ function RateLimitPanel({ focused }) {
9144
9133
  { isActive: focused }
9145
9134
  );
9146
9135
  const history = (historyQ.data?.history ?? []).slice().sort((a, b) => b.ts - a.ts);
9147
- const activity = insightsQ.data?.["activity"] ?? {};
9148
- const minuteSeries = (activity.minute ?? []).map((b) => b.count);
9149
- const hourSeries = (activity.hour ?? []).map((b) => b.count);
9150
- const bursts = minuteSeries.filter((c2) => draft && draft.perMinute > 0 && c2 > draft.perMinute).length;
9151
- return /* @__PURE__ */ jsx4(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
9152
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
9153
- /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
9154
- /* @__PURE__ */ jsx4(FieldRow, { label: "Mode", active: focused && fi === 0, children: /* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), children: draft.mode }) }),
9155
- /* @__PURE__ */ jsx4(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perMinute }) }),
9156
- /* @__PURE__ */ jsx4(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
9157
- /* @__PURE__ */ jsx4(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
9158
- ] }),
9159
- draft.perMinute > 0 ? (() => {
9160
- const now = minuteSeries.slice(-1)[0] ?? 0;
9161
- const pct = Math.min(100, Math.round(now / draft.perMinute * 100));
9162
- const width2 = 30;
9163
- const filled = Math.min(width2, Math.round(now / draft.perMinute * width2));
9164
- const col = pct >= 100 ? theme.bad : pct >= 80 ? theme.warn : theme.ok;
9165
- return /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
9166
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "load " }),
9167
- /* @__PURE__ */ jsx4(Text4, { color: col, children: "\u2588".repeat(filled) }),
9168
- /* @__PURE__ */ jsx4(Text4, { color: "#233457", children: "\u2591".repeat(Math.max(0, width2 - filled)) }),
9169
- /* @__PURE__ */ jsx4(Text4, { color: col, children: ` ${now}/${draft.perMinute} ` }),
9170
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: `(${pct}% this min)` })
9171
- ] });
9172
- })() : null,
9173
- dirty || status ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
9174
- dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
9175
- status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
9176
- ] }) : null,
9177
- /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
9178
- /* @__PURE__ */ jsxs4(Box4, { children: [
9179
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Activity " }),
9180
- bursts > 0 ? /* @__PURE__ */ jsxs4(Text4, { color: theme.bad, children: [
9181
- bursts,
9182
- " burst min"
9183
- ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no bursts" }),
9184
- insightsQ.loading && !insightsQ.data ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " \u2026" }) : null
9136
+ const lastChange = history[0];
9137
+ const peaks = insightsQ.data?.["peaks"] ?? { minute: 0, hour: 0, day: 0 };
9138
+ const anomalies2 = (insightsQ.data?.["anomalies"] ?? []).slice().sort((a, b) => new Date(b.minute).getTime() - new Date(a.minute).getTime());
9139
+ return /* @__PURE__ */ jsx4(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? (() => {
9140
+ const lim = draft.perMinute;
9141
+ const now = insightsQ.data ? insightsQ.data["activity"]?.minute?.slice(-1)[0]?.count ?? 0 : 0;
9142
+ const loadPct = lim > 0 ? Math.min(100, Math.round(now / lim * 100)) : 0;
9143
+ const peakFlag = (peak) => lim > 0 ? peak > lim ? ` OVER ${lim}` : peak === lim ? ` at ${lim}` : ` / ${lim}` : "";
9144
+ const hasLoad = lim > 0;
9145
+ const fixed = 1 + 4 + (hasLoad ? 1 : 0) + 1 + 1 + 1 + 1 + (dirty || status ? 1 : 0) + 1;
9146
+ const burstBudget = Math.max(1, rows - fixed);
9147
+ const shown = anomalies2.slice(0, burstBudget);
9148
+ const bw = Math.max(10, cols - 30);
9149
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height: rows, overflow: "hidden", children: [
9150
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
9151
+ /* @__PURE__ */ jsxs4(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
9152
+ /* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
9153
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
9185
9154
  ] }),
9186
- /* @__PURE__ */ jsxs4(Box4, { children: [
9187
- /* @__PURE__ */ jsx4(Text4, { children: "/min " }),
9188
- minuteSeries.length ? /* @__PURE__ */ jsx4(ColoredSpark, { values: minuteSeries, limit: draft.perMinute }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no data" })
9155
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perMinute || "off" }) }),
9156
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perHour || "off" }) }),
9157
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perDay || "off" }) }),
9158
+ hasLoad ? /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
9159
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "load".padEnd(13) }),
9160
+ /* @__PURE__ */ jsx4(Text4, { color: loadPct >= 100 ? theme.bad : loadPct >= 80 ? theme.warn : theme.ok, children: "\u2588".repeat(Math.min(20, Math.round(now / lim * 20))) }),
9161
+ /* @__PURE__ */ jsx4(Text4, { color: "#233457", children: "\u2591".repeat(Math.max(0, 20 - Math.round(now / lim * 20))) }),
9162
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` ${now}/${lim} this minute (${loadPct}%)` })
9163
+ ] }) : null,
9164
+ /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
9165
+ /* @__PURE__ */ jsx4(Text4, { color: theme.accentBright, bold: true, children: "Busiest 7d " }),
9166
+ /* @__PURE__ */ jsx4(Text4, { color: peaks.minute > lim && lim > 0 ? theme.bad : theme.dim, children: `min ${peaks.minute}${peakFlag(peaks.minute)}` }),
9167
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` \xB7 hr ${peaks.hour} \xB7 day ${peaks.day}` })
9189
9168
  ] }),
9190
- /* @__PURE__ */ jsxs4(Box4, { children: [
9191
- /* @__PURE__ */ jsx4(Text4, { children: "/hour " }),
9192
- hourSeries.length ? /* @__PURE__ */ jsx4(ColoredSpark, { values: hourSeries, limit: draft.perHour, width: 24 }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no data" })
9193
- ] })
9194
- ] }),
9195
- /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
9196
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
9197
- "Change history ",
9198
- history.length ? `(${history.length})` : ""
9169
+ /* @__PURE__ */ jsxs4(Text4, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
9170
+ `Recent bursts 7d${anomalies2.length ? ` (${anomalies2.length})` : ""}`,
9171
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " over the per-minute limit \xB7 Bypassed = detect (not blocked)" })
9199
9172
  ] }),
9200
- history.length ? /* @__PURE__ */ jsx4(
9173
+ anomalies2.length === 0 ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
9174
+ " ",
9175
+ insightsQ.loading ? "loading\u2026" : "no bursts in the last 7 days"
9176
+ ] }) : /* @__PURE__ */ jsx4(
9201
9177
  Table,
9202
9178
  {
9203
- columns: [
9204
- { header: "WHEN", width: 10 },
9205
- { header: "/MIN", width: 6 },
9206
- { header: "/HOUR", width: 7 },
9207
- { header: "/DAY", width: 6 }
9208
- ],
9209
- rows: history.slice(0, 4).map((h, i) => [
9210
- { value: ago(h.ts) + " ago", dim: true, bold: i === 0 },
9211
- { value: String(h.minute), bold: i === 0 },
9212
- { value: h.hour === 0 ? "off" : String(h.hour), dim: h.hour === 0 },
9213
- { value: h.day === 0 ? "off" : String(h.day), dim: h.day === 0 }
9179
+ columns: [{ header: "WHEN", width: 13 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 29) }],
9180
+ rows: shown.map((a) => [
9181
+ { value: when(new Date(a.minute).getTime()), dim: true },
9182
+ { value: a.blocked ? "Blocked" : "Bypassed", color: a.blocked ? theme.bad : theme.warn },
9183
+ { value: `${a.count}/${a.limit}`, color: theme.warn },
9184
+ { value: truncate2(a.agent, Math.max(8, bw - 29)), dim: true }
9214
9185
  ])
9215
9186
  }
9216
- ) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " no changes" })
9217
- ] })
9218
- ] }) : null });
9187
+ ),
9188
+ /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
9189
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "limit now " }),
9190
+ /* @__PURE__ */ jsx4(Text4, { children: lim > 0 ? `${lim}/min` : "no per-minute limit" }),
9191
+ lastChange ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` \xB7 changed ${when(lastChange.ts)}` }) : null,
9192
+ dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: " \u25CF unsaved (s)" }) : null,
9193
+ status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: " " + status }) : null
9194
+ ] })
9195
+ ] });
9196
+ })() : null });
9219
9197
  }
9220
9198
  function FieldRow({ label, active: active2, children }) {
9221
- return /* @__PURE__ */ jsxs4(Box4, { children: [
9222
- /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(12) }),
9199
+ return /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
9200
+ /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(11) }),
9223
9201
  children
9224
9202
  ] });
9225
9203
  }
9226
- var MODES, FIELDS2, BLOCKS;
9204
+ var MODES, FIELDS2, when;
9227
9205
  var init_RateLimit = __esm({
9228
9206
  "src/tui/panels/RateLimit.tsx"() {
9229
9207
  "use strict";
@@ -9233,7 +9211,10 @@ var init_RateLimit = __esm({
9233
9211
  init_theme();
9234
9212
  MODES = ["off", "detect", "block"];
9235
9213
  FIELDS2 = ["mode", "perMinute", "perHour", "perDay"];
9236
- BLOCKS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
9214
+ when = (ms) => {
9215
+ const d = new Date(ms);
9216
+ return Number.isNaN(d.getTime()) ? "\u2014" : d.toLocaleDateString([], { month: "short", day: "numeric" }) + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
9217
+ };
9237
9218
  }
9238
9219
  });
9239
9220
 
@@ -11211,10 +11192,10 @@ function truncate3(s, n) {
11211
11192
  function sparkline(values) {
11212
11193
  if (values.length === 0) return "";
11213
11194
  const max = Math.max(...values, 0);
11214
- if (max === 0) return BLOCKS2[0].repeat(values.length);
11215
- return values.map((v) => BLOCKS2[Math.min(BLOCKS2.length - 1, Math.round(v / max * (BLOCKS2.length - 1)))]).join("");
11195
+ if (max === 0) return BLOCKS[0].repeat(values.length);
11196
+ return values.map((v) => BLOCKS[Math.min(BLOCKS.length - 1, Math.round(v / max * (BLOCKS.length - 1)))]).join("");
11216
11197
  }
11217
- var out, err, dim, bold, green, red, yellow, cyan, ANSI, width, BLOCKS2;
11198
+ var out, err, dim, bold, green, red, yellow, cyan, ANSI, width, BLOCKS;
11218
11199
  var init_format = __esm({
11219
11200
  "src/commands/format.ts"() {
11220
11201
  "use strict";
@@ -11229,7 +11210,7 @@ var init_format = __esm({
11229
11210
  cyan = (s) => `${c.cyan}${s}${c.reset}`;
11230
11211
  ANSI = /\x1b\[[0-9;]*m/g;
11231
11212
  width = (s) => s.replace(ANSI, "").length;
11232
- BLOCKS2 = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
11213
+ BLOCKS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
11233
11214
  }
11234
11215
  });
11235
11216
 
package/dist/tui/index.js CHANGED
@@ -2176,17 +2176,12 @@ import { useEffect as useEffect4, useState as useState4 } from "react";
2176
2176
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2177
2177
  var MODES = ["off", "detect", "block"];
2178
2178
  var FIELDS2 = ["mode", "perMinute", "perHour", "perDay"];
2179
- var BLOCKS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
2180
- function ColoredSpark({ values, limit, width = 60 }) {
2181
- const v = values.length > width ? values.slice(values.length - width) : values;
2182
- const max = Math.max(1, ...v);
2183
- return /* @__PURE__ */ jsx4(Text4, { children: v.map((n, i) => {
2184
- const ch = BLOCKS[Math.min(BLOCKS.length - 1, Math.round(n / max * (BLOCKS.length - 1)))];
2185
- const over = limit > 0 && n > limit;
2186
- return /* @__PURE__ */ jsx4(Text4, { color: over ? theme.bad : theme.accent, children: ch }, i);
2187
- }) });
2188
- }
2179
+ var when = (ms) => {
2180
+ const d = new Date(ms);
2181
+ return Number.isNaN(d.getTime()) ? "\u2014" : d.toLocaleDateString([], { month: "short", day: "numeric" }) + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
2182
+ };
2189
2183
  function RateLimitPanel({ focused }) {
2184
+ const { cols, rows } = usePanelSize();
2190
2185
  const layersQ = useLoader(() => api.settings.getSecurityLayers());
2191
2186
  const historyQ = useLoader(() => api.settings.getRateLimitHistory());
2192
2187
  const insightsQ = useLoader(() => api.stats.securityInsights(7));
@@ -2200,12 +2195,8 @@ function RateLimitPanel({ focused }) {
2200
2195
  const adjust = (dir, step) => {
2201
2196
  if (!draft) return;
2202
2197
  const field = FIELDS2[fi];
2203
- if (field === "mode") {
2204
- const idx = (MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length;
2205
- setDraft({ ...draft, mode: MODES[idx] });
2206
- } else {
2207
- setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
2208
- }
2198
+ if (field === "mode") setDraft({ ...draft, mode: MODES[(MODES.indexOf(draft.mode) + dir + MODES.length) % MODES.length] });
2199
+ else setDraft({ ...draft, [field]: Math.max(0, draft[field] + dir * step) });
2209
2200
  setDirty(true);
2210
2201
  setStatus(null);
2211
2202
  };
@@ -2218,6 +2209,7 @@ function RateLimitPanel({ focused }) {
2218
2209
  setDirty(false);
2219
2210
  setStatus("\u2713 Saved");
2220
2211
  historyQ.reload();
2212
+ insightsQ.reload();
2221
2213
  } catch (e) {
2222
2214
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
2223
2215
  }
@@ -2234,82 +2226,71 @@ function RateLimitPanel({ focused }) {
2234
2226
  { isActive: focused }
2235
2227
  );
2236
2228
  const history = (historyQ.data?.history ?? []).slice().sort((a, b) => b.ts - a.ts);
2237
- const activity = insightsQ.data?.["activity"] ?? {};
2238
- const minuteSeries = (activity.minute ?? []).map((b) => b.count);
2239
- const hourSeries = (activity.hour ?? []).map((b) => b.count);
2240
- const bursts = minuteSeries.filter((c2) => draft && draft.perMinute > 0 && c2 > draft.perMinute).length;
2241
- return /* @__PURE__ */ jsx4(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2242
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
2243
- /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
2244
- /* @__PURE__ */ jsx4(FieldRow, { label: "Mode", active: focused && fi === 0, children: /* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), children: draft.mode }) }),
2245
- /* @__PURE__ */ jsx4(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perMinute }) }),
2246
- /* @__PURE__ */ jsx4(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perHour === 0 ? "off" : draft.perHour }) }),
2247
- /* @__PURE__ */ jsx4(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perDay === 0 ? "off" : draft.perDay }) })
2248
- ] }),
2249
- draft.perMinute > 0 ? (() => {
2250
- const now = minuteSeries.slice(-1)[0] ?? 0;
2251
- const pct = Math.min(100, Math.round(now / draft.perMinute * 100));
2252
- const width = 30;
2253
- const filled = Math.min(width, Math.round(now / draft.perMinute * width));
2254
- const col = pct >= 100 ? theme.bad : pct >= 80 ? theme.warn : theme.ok;
2255
- return /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
2256
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "load " }),
2257
- /* @__PURE__ */ jsx4(Text4, { color: col, children: "\u2588".repeat(filled) }),
2258
- /* @__PURE__ */ jsx4(Text4, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
2259
- /* @__PURE__ */ jsx4(Text4, { color: col, children: ` ${now}/${draft.perMinute} ` }),
2260
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: `(${pct}% this min)` })
2261
- ] });
2262
- })() : null,
2263
- dirty || status ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
2264
- dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "\u25CF unsaved \u2014 press s to apply " }) : null,
2265
- status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
2266
- ] }) : null,
2267
- /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
2268
- /* @__PURE__ */ jsxs4(Box4, { children: [
2269
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Activity " }),
2270
- bursts > 0 ? /* @__PURE__ */ jsxs4(Text4, { color: theme.bad, children: [
2271
- bursts,
2272
- " burst min"
2273
- ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no bursts" }),
2274
- insightsQ.loading && !insightsQ.data ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " \u2026" }) : null
2229
+ const lastChange = history[0];
2230
+ const peaks = insightsQ.data?.["peaks"] ?? { minute: 0, hour: 0, day: 0 };
2231
+ const anomalies2 = (insightsQ.data?.["anomalies"] ?? []).slice().sort((a, b) => new Date(b.minute).getTime() - new Date(a.minute).getTime());
2232
+ return /* @__PURE__ */ jsx4(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? (() => {
2233
+ const lim = draft.perMinute;
2234
+ const now = insightsQ.data ? insightsQ.data["activity"]?.minute?.slice(-1)[0]?.count ?? 0 : 0;
2235
+ const loadPct = lim > 0 ? Math.min(100, Math.round(now / lim * 100)) : 0;
2236
+ const peakFlag = (peak) => lim > 0 ? peak > lim ? ` OVER ${lim}` : peak === lim ? ` at ${lim}` : ` / ${lim}` : "";
2237
+ const hasLoad = lim > 0;
2238
+ const fixed = 1 + 4 + (hasLoad ? 1 : 0) + 1 + 1 + 1 + 1 + (dirty || status ? 1 : 0) + 1;
2239
+ const burstBudget = Math.max(1, rows - fixed);
2240
+ const shown = anomalies2.slice(0, burstBudget);
2241
+ const bw = Math.max(10, cols - 30);
2242
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height: rows, overflow: "hidden", children: [
2243
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 field \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save" : "press \u2192 to edit" }),
2244
+ /* @__PURE__ */ jsxs4(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
2245
+ /* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
2246
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
2275
2247
  ] }),
2276
- /* @__PURE__ */ jsxs4(Box4, { children: [
2277
- /* @__PURE__ */ jsx4(Text4, { children: "/min " }),
2278
- minuteSeries.length ? /* @__PURE__ */ jsx4(ColoredSpark, { values: minuteSeries, limit: draft.perMinute }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no data" })
2248
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perMinute || "off" }) }),
2249
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perHour || "off" }) }),
2250
+ /* @__PURE__ */ jsx4(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perDay || "off" }) }),
2251
+ hasLoad ? /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
2252
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "load".padEnd(13) }),
2253
+ /* @__PURE__ */ jsx4(Text4, { color: loadPct >= 100 ? theme.bad : loadPct >= 80 ? theme.warn : theme.ok, children: "\u2588".repeat(Math.min(20, Math.round(now / lim * 20))) }),
2254
+ /* @__PURE__ */ jsx4(Text4, { color: "#233457", children: "\u2591".repeat(Math.max(0, 20 - Math.round(now / lim * 20))) }),
2255
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` ${now}/${lim} this minute (${loadPct}%)` })
2256
+ ] }) : null,
2257
+ /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
2258
+ /* @__PURE__ */ jsx4(Text4, { color: theme.accentBright, bold: true, children: "Busiest 7d " }),
2259
+ /* @__PURE__ */ jsx4(Text4, { color: peaks.minute > lim && lim > 0 ? theme.bad : theme.dim, children: `min ${peaks.minute}${peakFlag(peaks.minute)}` }),
2260
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` \xB7 hr ${peaks.hour} \xB7 day ${peaks.day}` })
2279
2261
  ] }),
2280
- /* @__PURE__ */ jsxs4(Box4, { children: [
2281
- /* @__PURE__ */ jsx4(Text4, { children: "/hour " }),
2282
- hourSeries.length ? /* @__PURE__ */ jsx4(ColoredSpark, { values: hourSeries, limit: draft.perHour, width: 24 }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "no data" })
2283
- ] })
2284
- ] }),
2285
- /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
2286
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2287
- "Change history ",
2288
- history.length ? `(${history.length})` : ""
2262
+ /* @__PURE__ */ jsxs4(Text4, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
2263
+ `Recent bursts 7d${anomalies2.length ? ` (${anomalies2.length})` : ""}`,
2264
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " over the per-minute limit \xB7 Bypassed = detect (not blocked)" })
2289
2265
  ] }),
2290
- history.length ? /* @__PURE__ */ jsx4(
2266
+ anomalies2.length === 0 ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2267
+ " ",
2268
+ insightsQ.loading ? "loading\u2026" : "no bursts in the last 7 days"
2269
+ ] }) : /* @__PURE__ */ jsx4(
2291
2270
  Table,
2292
2271
  {
2293
- columns: [
2294
- { header: "WHEN", width: 10 },
2295
- { header: "/MIN", width: 6 },
2296
- { header: "/HOUR", width: 7 },
2297
- { header: "/DAY", width: 6 }
2298
- ],
2299
- rows: history.slice(0, 4).map((h, i) => [
2300
- { value: ago(h.ts) + " ago", dim: true, bold: i === 0 },
2301
- { value: String(h.minute), bold: i === 0 },
2302
- { value: h.hour === 0 ? "off" : String(h.hour), dim: h.hour === 0 },
2303
- { value: h.day === 0 ? "off" : String(h.day), dim: h.day === 0 }
2272
+ columns: [{ header: "WHEN", width: 13 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 29) }],
2273
+ rows: shown.map((a) => [
2274
+ { value: when(new Date(a.minute).getTime()), dim: true },
2275
+ { value: a.blocked ? "Blocked" : "Bypassed", color: a.blocked ? theme.bad : theme.warn },
2276
+ { value: `${a.count}/${a.limit}`, color: theme.warn },
2277
+ { value: truncate(a.agent, Math.max(8, bw - 29)), dim: true }
2304
2278
  ])
2305
2279
  }
2306
- ) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " no changes" })
2307
- ] })
2308
- ] }) : null });
2280
+ ),
2281
+ /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
2282
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "limit now " }),
2283
+ /* @__PURE__ */ jsx4(Text4, { children: lim > 0 ? `${lim}/min` : "no per-minute limit" }),
2284
+ lastChange ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` \xB7 changed ${when(lastChange.ts)}` }) : null,
2285
+ dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: " \u25CF unsaved (s)" }) : null,
2286
+ status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: " " + status }) : null
2287
+ ] })
2288
+ ] });
2289
+ })() : null });
2309
2290
  }
2310
2291
  function FieldRow({ label, active: active2, children }) {
2311
- return /* @__PURE__ */ jsxs4(Box4, { children: [
2312
- /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(12) }),
2292
+ return /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
2293
+ /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(11) }),
2313
2294
  children
2314
2295
  ] });
2315
2296
  }
@@ -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 = 42;
6539
+ var HOOK_VERSION = 43;
6540
6540
  function localLogsOnly(security) {
6541
6541
  if (security && typeof security === "object") {
6542
6542
  const l = security.localLogs;
@@ -7180,6 +7180,29 @@ function dlpGlobToRe(glob) {
7180
7180
  }
7181
7181
  return new RegExp(re, "i");
7182
7182
  }
7183
+ function dlpViews(text) {
7184
+ const views = [text];
7185
+ try {
7186
+ const dequoted = text.replace(/[`'"\\]/g, "");
7187
+ if (dequoted !== text)
7188
+ views.push(dequoted);
7189
+ const src = dequoted !== text ? text + "\n" + dequoted : text;
7190
+ const toks = src.match(/[A-Za-z0-9+/]{16,}={0,2}/g) || [];
7191
+ let decoded = "";
7192
+ for (const t of toks.slice(0, 60)) {
7193
+ try {
7194
+ const d = Buffer.from(t, "base64").toString("latin1");
7195
+ if (/[ -~]{8,}/.test(d))
7196
+ decoded += d + "\n";
7197
+ } catch {
7198
+ }
7199
+ }
7200
+ if (decoded)
7201
+ views.push(decoded);
7202
+ } catch {
7203
+ }
7204
+ return views;
7205
+ }
7183
7206
  function dlpScan(args, cfg) {
7184
7207
  if (!cfg)
7185
7208
  return null;
@@ -7189,14 +7212,16 @@ function dlpScan(args, cfg) {
7189
7212
  } catch {
7190
7213
  return null;
7191
7214
  }
7215
+ const views = dlpViews(text);
7192
7216
  const allow = new Set(Array.isArray(cfg.patterns) ? cfg.patterns : []);
7193
7217
  for (const p of DLP_PATTERNS) {
7194
- if (allow.has(p.name) && p.re.test(text))
7218
+ if (allow.has(p.name) && views.some((v) => p.re.test(v)))
7195
7219
  return p.name;
7196
7220
  }
7197
7221
  for (const c of Array.isArray(cfg.custom) ? cfg.custom : []) {
7198
7222
  try {
7199
- if (dlpGlobToRe(c.re).test(text))
7223
+ const re = dlpGlobToRe(c.re);
7224
+ if (views.some((v) => re.test(v)))
7200
7225
  return c.name || "custom pattern";
7201
7226
  } catch {
7202
7227
  }
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 = 42;
35
+ const HOOK_VERSION = 43;
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.
@@ -900,18 +900,45 @@ function dlpGlobToRe(glob) {
900
900
  }
901
901
  return new RegExp(re, 'i');
902
902
  }
903
+ // De-obfuscated VIEWS of the scanned text, so an agent can't smuggle a secret
904
+ // past the literal patterns by SPLITTING it (printf "AKIA""3XZ9…") or ENCODING it
905
+ // (echo <base64> | base64 -d). We scan every view. Not exhaustive — pattern DLP
906
+ // can never be — but it closes the two obvious bypasses.
907
+ function dlpViews(text) {
908
+ const views = [text];
909
+ try {
910
+ // 1) Drop shell quotes + backslashes so a split secret collapses back to a
911
+ // contiguous run: "AKIA""3XZ9…" / 'AKIA'\''…' / AKIA\3XZ9 → AKIA3XZ9…
912
+ const dequoted = text.replace(/[`'"\\]/g, '');
913
+ if (dequoted !== text) views.push(dequoted);
914
+ // 2) Decode base64-looking tokens (from both the raw and the dequoted view —
915
+ // the payload itself may be split too) and scan the decoded bytes.
916
+ const src = dequoted !== text ? text + '\n' + dequoted : text;
917
+ const toks = src.match(/[A-Za-z0-9+/]{16,}={0,2}/g) || [];
918
+ let decoded = '';
919
+ for (const t of toks.slice(0, 60)) {
920
+ try {
921
+ const d = Buffer.from(t, 'base64').toString('latin1');
922
+ if (/[ -~]{8,}/.test(d)) decoded += d + '\n'; // keep printable decodes only
923
+ } catch { /* not base64 */ }
924
+ }
925
+ if (decoded) views.push(decoded);
926
+ } catch { /* fall back to the raw view */ }
927
+ return views;
928
+ }
903
929
  // Scan args against the enabled built-in patterns + any user custom patterns.
904
930
  // `cfg` = { patterns: string[], custom: {name,re}[] }.
905
931
  function dlpScan(args, cfg) {
906
932
  if (!cfg) return null;
907
933
  let text = '';
908
934
  try { text = JSON.stringify(args || {}); } catch { return null; }
935
+ const views = dlpViews(text);
909
936
  const allow = new Set(Array.isArray(cfg.patterns) ? cfg.patterns : []);
910
937
  for (const p of DLP_PATTERNS) {
911
- if (allow.has(p.name) && p.re.test(text)) return p.name;
938
+ if (allow.has(p.name) && views.some((v) => p.re.test(v))) return p.name;
912
939
  }
913
940
  for (const c of Array.isArray(cfg.custom) ? cfg.custom : []) {
914
- try { if (dlpGlobToRe(c.re).test(text)) return c.name || 'custom pattern'; } catch { /* skip invalid */ }
941
+ try { const re = dlpGlobToRe(c.re); if (views.some((v) => re.test(v))) return c.name || 'custom pattern'; } catch { /* skip invalid */ }
915
942
  }
916
943
  return null;
917
944
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.86",
3
+ "version": "0.81.88",
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": {