@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 +69 -88
- package/dist/tui/index.js +64 -83
- package/hooks/guard.bundled.mjs +28 -3
- package/hooks/guard.mjs +30 -3
- package/package.json +1 -1
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
|
-
|
|
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
|
|
9148
|
-
const
|
|
9149
|
-
const
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
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__ */
|
|
9187
|
-
|
|
9188
|
-
|
|
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(
|
|
9191
|
-
|
|
9192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9205
|
-
{
|
|
9206
|
-
{
|
|
9207
|
-
{
|
|
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
|
-
)
|
|
9217
|
-
|
|
9218
|
-
|
|
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(
|
|
9222
|
-
/* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(
|
|
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,
|
|
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
|
-
|
|
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
|
|
11215
|
-
return values.map((v) =>
|
|
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,
|
|
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
|
-
|
|
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
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
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
|
-
|
|
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
|
|
2238
|
-
const
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
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__ */
|
|
2277
|
-
|
|
2278
|
-
|
|
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(
|
|
2281
|
-
|
|
2282
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2295
|
-
{
|
|
2296
|
-
{
|
|
2297
|
-
{
|
|
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
|
-
)
|
|
2307
|
-
|
|
2308
|
-
|
|
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(
|
|
2312
|
-
/* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(
|
|
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
|
}
|
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 = 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(
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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 {
|
|
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.
|
|
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": {
|