@solongate/proxy 0.59.4 → 0.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client/agents.d.ts +10 -0
- package/dist/api-client/audit.d.ts +22 -0
- package/dist/api-client/client.d.ts +39 -0
- package/dist/api-client/index.d.ts +18 -0
- package/dist/api-client/policies.d.ts +82 -0
- package/dist/api-client/settings.d.ts +22 -0
- package/dist/api-client/stats.d.ts +9 -0
- package/dist/api-client/types.d.ts +224 -0
- package/dist/audit/index.js +0 -1
- package/dist/commands/agents.d.ts +4 -0
- package/dist/commands/args.d.ts +15 -0
- package/dist/commands/audit.d.ts +1 -0
- package/dist/commands/dlp.d.ts +1 -0
- package/dist/commands/format.d.ts +22 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.js +938 -0
- package/dist/commands/policy.d.ts +1 -0
- package/dist/commands/ratelimit.d.ts +1 -0
- package/dist/commands/stats.d.ts +1 -0
- package/dist/index.js +2059 -348
- package/dist/inject.js +4 -4
- package/dist/lib.js +3 -31
- package/dist/proxy.d.ts +0 -7
- package/dist/pull-push.js +2 -1
- package/dist/tui/App.d.ts +1 -0
- package/dist/tui/components.d.ts +40 -0
- package/dist/tui/hooks.d.ts +13 -0
- package/dist/tui/index.d.ts +1 -0
- package/dist/tui/index.js +881 -0
- package/dist/tui/panels/Agents.d.ts +3 -0
- package/dist/tui/panels/Audit.d.ts +4 -0
- package/dist/tui/panels/Dlp.d.ts +4 -0
- package/dist/tui/panels/Policies.d.ts +4 -0
- package/dist/tui/panels/RateLimit.d.ts +4 -0
- package/dist/tui/panels/Stats.d.ts +3 -0
- package/dist/tui/theme.d.ts +16 -0
- package/hooks/audit.mjs +23 -4
- package/hooks/guard.bundled.mjs +46 -35
- package/hooks/guard.mjs +16 -2
- package/package.json +7 -2
|
@@ -0,0 +1,938 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/api-client/client.ts
|
|
8
|
+
import { readFileSync, existsSync } from "fs";
|
|
9
|
+
import { resolve, join } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
12
|
+
var ApiError = class extends Error {
|
|
13
|
+
status;
|
|
14
|
+
code;
|
|
15
|
+
constructor(status, code, message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "ApiError";
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var NotAuthenticatedError = class extends Error {
|
|
23
|
+
constructor() {
|
|
24
|
+
super("Not logged in. Run `solongate login` first.");
|
|
25
|
+
this.name = "NotAuthenticatedError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function loginCredentialFile() {
|
|
29
|
+
try {
|
|
30
|
+
const p = join(homedir(), ".solongate", "cloud-guard.json");
|
|
31
|
+
if (!existsSync(p)) return {};
|
|
32
|
+
const c2 = JSON.parse(readFileSync(p, "utf-8"));
|
|
33
|
+
return c2 && typeof c2 === "object" ? c2 : {};
|
|
34
|
+
} catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function dotenvApiKey() {
|
|
39
|
+
try {
|
|
40
|
+
const envPath = resolve(".env");
|
|
41
|
+
if (!existsSync(envPath)) return void 0;
|
|
42
|
+
for (const line of readFileSync(envPath, "utf-8").split("\n")) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
45
|
+
const eq = trimmed.indexOf("=");
|
|
46
|
+
if (eq === -1) continue;
|
|
47
|
+
const key = trimmed.slice(0, eq).trim();
|
|
48
|
+
if (key !== "SOLONGATE_API_KEY") continue;
|
|
49
|
+
return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
var cached = null;
|
|
56
|
+
function resolveCredentials(apiUrlOverride) {
|
|
57
|
+
if (cached && !apiUrlOverride) return cached;
|
|
58
|
+
const file = loginCredentialFile();
|
|
59
|
+
const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
|
|
60
|
+
if (!apiKey) throw new NotAuthenticatedError();
|
|
61
|
+
const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL;
|
|
62
|
+
const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
|
|
63
|
+
if (!apiUrlOverride) cached = creds;
|
|
64
|
+
return creds;
|
|
65
|
+
}
|
|
66
|
+
function buildUrl(base, path, query) {
|
|
67
|
+
const url = new URL(`${base}/api/v1${path}`);
|
|
68
|
+
if (query) {
|
|
69
|
+
for (const [k, v] of Object.entries(query)) {
|
|
70
|
+
if (v === void 0) continue;
|
|
71
|
+
url.searchParams.set(k, String(v));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return url.toString();
|
|
75
|
+
}
|
|
76
|
+
async function request(method, path, opts = {}) {
|
|
77
|
+
const creds = resolveCredentials(opts.apiUrl);
|
|
78
|
+
const url = buildUrl(creds.apiUrl, path, opts.query);
|
|
79
|
+
const headers = {
|
|
80
|
+
Authorization: `Bearer ${creds.apiKey}`
|
|
81
|
+
};
|
|
82
|
+
let bodyInit;
|
|
83
|
+
if (opts.body !== void 0) {
|
|
84
|
+
headers["Content-Type"] = "application/json";
|
|
85
|
+
bodyInit = JSON.stringify(opts.body);
|
|
86
|
+
}
|
|
87
|
+
let res;
|
|
88
|
+
try {
|
|
89
|
+
res = await fetch(url, {
|
|
90
|
+
method,
|
|
91
|
+
headers,
|
|
92
|
+
body: bodyInit,
|
|
93
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
94
|
+
});
|
|
95
|
+
} catch (err2) {
|
|
96
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
97
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
98
|
+
}
|
|
99
|
+
const text = await res.text().catch(() => "");
|
|
100
|
+
let json = void 0;
|
|
101
|
+
if (text) {
|
|
102
|
+
try {
|
|
103
|
+
json = JSON.parse(text);
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
const envelope = json?.error;
|
|
109
|
+
if (envelope && typeof envelope === "object") {
|
|
110
|
+
throw new ApiError(res.status, envelope.code || "ERROR", envelope.message || res.statusText);
|
|
111
|
+
}
|
|
112
|
+
if (typeof envelope === "string") {
|
|
113
|
+
throw new ApiError(res.status, "ERROR", envelope);
|
|
114
|
+
}
|
|
115
|
+
if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate login`.");
|
|
116
|
+
if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
|
|
117
|
+
throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
|
|
118
|
+
}
|
|
119
|
+
return json;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/api-client/policies.ts
|
|
123
|
+
var policies_exports = {};
|
|
124
|
+
__export(policies_exports, {
|
|
125
|
+
active: () => active,
|
|
126
|
+
addRule: () => addRule,
|
|
127
|
+
create: () => create,
|
|
128
|
+
dryRun: () => dryRun,
|
|
129
|
+
get: () => get,
|
|
130
|
+
list: () => list,
|
|
131
|
+
remove: () => remove,
|
|
132
|
+
revokeRule: () => revokeRule,
|
|
133
|
+
rollback: () => rollback,
|
|
134
|
+
setActive: () => setActive,
|
|
135
|
+
update: () => update,
|
|
136
|
+
versions: () => versions
|
|
137
|
+
});
|
|
138
|
+
function list() {
|
|
139
|
+
return request("GET", "/policies");
|
|
140
|
+
}
|
|
141
|
+
function get(id, version) {
|
|
142
|
+
return request("GET", `/policies/${encodeURIComponent(id)}`, {
|
|
143
|
+
query: version !== void 0 ? { version } : void 0
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function create(policy) {
|
|
147
|
+
return request("POST", "/policies", { body: policy });
|
|
148
|
+
}
|
|
149
|
+
function update(id, policy) {
|
|
150
|
+
return request("PUT", `/policies/${encodeURIComponent(id)}`, { body: policy });
|
|
151
|
+
}
|
|
152
|
+
function remove(id) {
|
|
153
|
+
return request("DELETE", `/policies/${encodeURIComponent(id)}`);
|
|
154
|
+
}
|
|
155
|
+
function addRule(id, spec) {
|
|
156
|
+
return request("POST", `/policies/${encodeURIComponent(id)}/rules`, { body: spec });
|
|
157
|
+
}
|
|
158
|
+
function revokeRule(id, ruleId) {
|
|
159
|
+
return request("DELETE", `/policies/${encodeURIComponent(id)}/rules/${encodeURIComponent(ruleId)}`);
|
|
160
|
+
}
|
|
161
|
+
function versions(id, opts = {}) {
|
|
162
|
+
return request("GET", `/policies/${encodeURIComponent(id)}/versions`, { query: opts });
|
|
163
|
+
}
|
|
164
|
+
function rollback(id, version) {
|
|
165
|
+
return request("POST", `/policies/${encodeURIComponent(id)}/rollback`, { body: { version } });
|
|
166
|
+
}
|
|
167
|
+
function active(agentId) {
|
|
168
|
+
return request("GET", "/policies/active", { query: agentId ? { agent_id: agentId } : void 0 });
|
|
169
|
+
}
|
|
170
|
+
function setActive(policyId) {
|
|
171
|
+
return request("POST", "/policies/active", { body: { policyId: policyId ?? "" } });
|
|
172
|
+
}
|
|
173
|
+
function dryRun(body) {
|
|
174
|
+
return request("POST", "/policies/dry-run", { body });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/api-client/settings.ts
|
|
178
|
+
var settings_exports = {};
|
|
179
|
+
__export(settings_exports, {
|
|
180
|
+
clearRateLimitHistory: () => clearRateLimitHistory,
|
|
181
|
+
getGuardStatus: () => getGuardStatus,
|
|
182
|
+
getRateLimitHistory: () => getRateLimitHistory,
|
|
183
|
+
getSecurityLayers: () => getSecurityLayers,
|
|
184
|
+
setSecurityLayers: () => setSecurityLayers
|
|
185
|
+
});
|
|
186
|
+
function getSecurityLayers() {
|
|
187
|
+
return request("GET", "/settings/security-layers");
|
|
188
|
+
}
|
|
189
|
+
function setSecurityLayers(layers) {
|
|
190
|
+
return request("PUT", "/settings/security-layers", { body: { layers } });
|
|
191
|
+
}
|
|
192
|
+
function getRateLimitHistory() {
|
|
193
|
+
return request("GET", "/settings/rate-limit-history");
|
|
194
|
+
}
|
|
195
|
+
function clearRateLimitHistory() {
|
|
196
|
+
return request("DELETE", "/settings/rate-limit-history", { query: { all: 1 } });
|
|
197
|
+
}
|
|
198
|
+
function getGuardStatus() {
|
|
199
|
+
return request("GET", "/settings/guard-status");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/api-client/stats.ts
|
|
203
|
+
var stats_exports = {};
|
|
204
|
+
__export(stats_exports, {
|
|
205
|
+
drift: () => drift,
|
|
206
|
+
get: () => get2,
|
|
207
|
+
securityInsights: () => securityInsights,
|
|
208
|
+
timeseries: () => timeseries
|
|
209
|
+
});
|
|
210
|
+
function get2() {
|
|
211
|
+
return request("GET", "/stats");
|
|
212
|
+
}
|
|
213
|
+
function timeseries(opts = {}) {
|
|
214
|
+
return request("GET", "/stats/timeseries", { query: opts });
|
|
215
|
+
}
|
|
216
|
+
function drift(days) {
|
|
217
|
+
return request("GET", "/stats/drift", { query: days !== void 0 ? { days } : void 0 });
|
|
218
|
+
}
|
|
219
|
+
function securityInsights(days) {
|
|
220
|
+
return request("GET", "/stats/security-insights", { query: days !== void 0 ? { days } : void 0 });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/api-client/audit.ts
|
|
224
|
+
var audit_exports = {};
|
|
225
|
+
__export(audit_exports, {
|
|
226
|
+
list: () => list2,
|
|
227
|
+
whitelist: () => whitelist
|
|
228
|
+
});
|
|
229
|
+
function list2(query = {}) {
|
|
230
|
+
return request("GET", "/audit-logs", { query });
|
|
231
|
+
}
|
|
232
|
+
function whitelist(id, scope = "exact") {
|
|
233
|
+
return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/api-client/agents.ts
|
|
237
|
+
var agents_exports = {};
|
|
238
|
+
__export(agents_exports, {
|
|
239
|
+
anomalies: () => anomalies,
|
|
240
|
+
get: () => get3,
|
|
241
|
+
live: () => live
|
|
242
|
+
});
|
|
243
|
+
function live(opts = {}) {
|
|
244
|
+
return request("GET", "/agents/live", {
|
|
245
|
+
query: { limit: opts.limit, include_deactivated: opts.includeDeactivated ? 1 : void 0 }
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function get3(id, scan = false) {
|
|
249
|
+
return request("GET", `/agents/${encodeURIComponent(id)}`, { query: scan ? { scan: 1 } : void 0 });
|
|
250
|
+
}
|
|
251
|
+
function anomalies(id, limit) {
|
|
252
|
+
return request("GET", `/agents/${encodeURIComponent(id)}/anomalies`, {
|
|
253
|
+
query: limit !== void 0 ? { limit } : void 0
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/api-client/index.ts
|
|
258
|
+
var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports };
|
|
259
|
+
|
|
260
|
+
// src/cli-utils.ts
|
|
261
|
+
var c = {
|
|
262
|
+
reset: "\x1B[0m",
|
|
263
|
+
bold: "\x1B[1m",
|
|
264
|
+
dim: "\x1B[2m",
|
|
265
|
+
italic: "\x1B[3m",
|
|
266
|
+
white: "\x1B[97m",
|
|
267
|
+
gray: "\x1B[90m",
|
|
268
|
+
blue1: "\x1B[38;2;20;50;160m",
|
|
269
|
+
blue2: "\x1B[38;2;40;80;190m",
|
|
270
|
+
blue3: "\x1B[38;2;60;110;215m",
|
|
271
|
+
blue4: "\x1B[38;2;90;140;230m",
|
|
272
|
+
blue5: "\x1B[38;2;130;170;240m",
|
|
273
|
+
blue6: "\x1B[38;2;170;200;250m",
|
|
274
|
+
green: "\x1B[38;2;80;200;120m",
|
|
275
|
+
red: "\x1B[38;2;220;80;80m",
|
|
276
|
+
cyan: "\x1B[38;2;100;200;220m",
|
|
277
|
+
yellow: "\x1B[38;2;220;200;80m",
|
|
278
|
+
bgBlue: "\x1B[48;2;20;50;160m"
|
|
279
|
+
};
|
|
280
|
+
var BANNER_COLORS = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
|
|
281
|
+
|
|
282
|
+
// src/commands/format.ts
|
|
283
|
+
var out = (s = "") => void process.stdout.write(s + "\n");
|
|
284
|
+
var err = (s = "") => void process.stderr.write(s + "\n");
|
|
285
|
+
function printJson(value) {
|
|
286
|
+
out(JSON.stringify(value, null, 2));
|
|
287
|
+
}
|
|
288
|
+
var dim = (s) => `${c.dim}${s}${c.reset}`;
|
|
289
|
+
var bold = (s) => `${c.bold}${s}${c.reset}`;
|
|
290
|
+
var green = (s) => `${c.green}${s}${c.reset}`;
|
|
291
|
+
var red = (s) => `${c.red}${s}${c.reset}`;
|
|
292
|
+
var yellow = (s) => `${c.yellow}${s}${c.reset}`;
|
|
293
|
+
var cyan = (s) => `${c.cyan}${s}${c.reset}`;
|
|
294
|
+
function decisionColor(decision) {
|
|
295
|
+
const d = decision.toUpperCase();
|
|
296
|
+
if (d === "ALLOW") return green(d);
|
|
297
|
+
if (d === "DENY" || d === "DENIED") return red(d);
|
|
298
|
+
return dim(d);
|
|
299
|
+
}
|
|
300
|
+
var ANSI = /\x1b\[[0-9;]*m/g;
|
|
301
|
+
var width = (s) => s.replace(ANSI, "").length;
|
|
302
|
+
function table(headers, rows) {
|
|
303
|
+
const cols = headers.length;
|
|
304
|
+
const w = new Array(cols).fill(0);
|
|
305
|
+
for (let i = 0; i < cols; i++) w[i] = width(headers[i] ?? "");
|
|
306
|
+
for (const row of rows) {
|
|
307
|
+
for (let i = 0; i < cols; i++) w[i] = Math.max(w[i], width(row[i] ?? ""));
|
|
308
|
+
}
|
|
309
|
+
const pad = (s, i) => s + " ".repeat(Math.max(0, w[i] - width(s)));
|
|
310
|
+
err(" " + headers.map((h, i) => dim(pad(h, i))).join(" "));
|
|
311
|
+
for (const row of rows) {
|
|
312
|
+
err(" " + row.map((cell, i) => pad(cell ?? "", i)).join(" "));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function truncate(s, n) {
|
|
316
|
+
if (s.length <= n) return s;
|
|
317
|
+
return s.slice(0, Math.max(0, n - 1)) + "\u2026";
|
|
318
|
+
}
|
|
319
|
+
var BLOCKS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
320
|
+
function sparkline(values) {
|
|
321
|
+
if (values.length === 0) return "";
|
|
322
|
+
const max = Math.max(...values, 0);
|
|
323
|
+
if (max === 0) return BLOCKS[0].repeat(values.length);
|
|
324
|
+
return values.map((v) => BLOCKS[Math.min(BLOCKS.length - 1, Math.round(v / max * (BLOCKS.length - 1)))]).join("");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/commands/policy.ts
|
|
328
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
329
|
+
|
|
330
|
+
// src/commands/args.ts
|
|
331
|
+
function parse(argv) {
|
|
332
|
+
const positionals = [];
|
|
333
|
+
const flags = {};
|
|
334
|
+
for (let i = 0; i < argv.length; i++) {
|
|
335
|
+
const tok = argv[i];
|
|
336
|
+
if (tok.startsWith("--")) {
|
|
337
|
+
const body = tok.slice(2);
|
|
338
|
+
const eq = body.indexOf("=");
|
|
339
|
+
if (eq !== -1) {
|
|
340
|
+
flags[body.slice(0, eq)] = body.slice(eq + 1);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const next = argv[i + 1];
|
|
344
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
345
|
+
flags[body] = next;
|
|
346
|
+
i++;
|
|
347
|
+
} else {
|
|
348
|
+
flags[body] = true;
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
positionals.push(tok);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return { positionals, flags };
|
|
355
|
+
}
|
|
356
|
+
function flagStr(flags, name) {
|
|
357
|
+
const v = flags[name];
|
|
358
|
+
return typeof v === "string" ? v : void 0;
|
|
359
|
+
}
|
|
360
|
+
function flagNum(flags, name) {
|
|
361
|
+
const v = flagStr(flags, name);
|
|
362
|
+
if (v === void 0) return void 0;
|
|
363
|
+
const n = Number(v);
|
|
364
|
+
return Number.isFinite(n) ? n : void 0;
|
|
365
|
+
}
|
|
366
|
+
function flagBool(flags, name) {
|
|
367
|
+
return flags[name] === true || flags[name] === "true";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/commands/policy.ts
|
|
371
|
+
var USAGE = `${bold("solongate policy")} \u2014 manage cloud policies
|
|
372
|
+
|
|
373
|
+
policy list List all policies
|
|
374
|
+
policy show <id> [--version N] Show one policy (rules, mode)
|
|
375
|
+
policy rules <id> List a policy's rules
|
|
376
|
+
policy allow <id> --tool <p> [--command|--path|--url <val>]
|
|
377
|
+
Append an ALLOW rule
|
|
378
|
+
policy revoke <id> <ruleId> Remove a rule
|
|
379
|
+
policy versions <id> List version history
|
|
380
|
+
policy rollback <id> <version> Roll back to a version
|
|
381
|
+
policy active Show the resolved active policy
|
|
382
|
+
policy activate <id> | --clear Pin / unpin the active policy
|
|
383
|
+
policy dry-run <id|file.json> [--limit N] [--mode denylist|whitelist]
|
|
384
|
+
Replay recent traffic against a policy's rules
|
|
385
|
+
|
|
386
|
+
Add --json to any read command for machine-readable output.`;
|
|
387
|
+
async function run(argv) {
|
|
388
|
+
const { positionals, flags } = parse(argv);
|
|
389
|
+
const sub = positionals[0];
|
|
390
|
+
const json = flagBool(flags, "json");
|
|
391
|
+
switch (sub) {
|
|
392
|
+
case void 0:
|
|
393
|
+
case "help":
|
|
394
|
+
err(USAGE);
|
|
395
|
+
return sub ? 0 : 1;
|
|
396
|
+
case "list": {
|
|
397
|
+
const { policies } = await api.policies.list();
|
|
398
|
+
if (json) return printJson(policies), 0;
|
|
399
|
+
if (policies.length === 0) {
|
|
400
|
+
err(dim(" No policies. Create one at https://dashboard.solongate.com"));
|
|
401
|
+
return 0;
|
|
402
|
+
}
|
|
403
|
+
table(
|
|
404
|
+
["ID", "NAME", "MODE", "RULES", "VER", "UPDATED BY"],
|
|
405
|
+
policies.map((p) => [
|
|
406
|
+
cyan(p.id),
|
|
407
|
+
truncate(p.name, 28),
|
|
408
|
+
p.mode === "whitelist" ? green("whitelist") : "denylist",
|
|
409
|
+
String(p.rules?.length ?? 0),
|
|
410
|
+
`v${p.version}`,
|
|
411
|
+
dim(truncate(p.created_by || "\u2014", 20))
|
|
412
|
+
])
|
|
413
|
+
);
|
|
414
|
+
return 0;
|
|
415
|
+
}
|
|
416
|
+
case "show": {
|
|
417
|
+
const id = positionals[1];
|
|
418
|
+
if (!id) return err(" Usage: policy show <id> [--version N]"), 1;
|
|
419
|
+
const p = await api.policies.get(id, flagNum(flags, "version"));
|
|
420
|
+
if (json) return printJson(p), 0;
|
|
421
|
+
err("");
|
|
422
|
+
err(` ${bold(p.name)} ${dim(`(${p.id})`)} v${p._version}`);
|
|
423
|
+
if (p.description) err(` ${dim(p.description)}`);
|
|
424
|
+
err(` mode: ${p.mode === "whitelist" ? green("whitelist") : "denylist"} rules: ${p.rules.length}`);
|
|
425
|
+
err("");
|
|
426
|
+
printRules(p.rules);
|
|
427
|
+
return 0;
|
|
428
|
+
}
|
|
429
|
+
case "rules": {
|
|
430
|
+
const id = positionals[1];
|
|
431
|
+
if (!id) return err(" Usage: policy rules <id>"), 1;
|
|
432
|
+
const p = await api.policies.get(id);
|
|
433
|
+
if (json) return printJson(p.rules), 0;
|
|
434
|
+
printRules(p.rules);
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
case "allow": {
|
|
438
|
+
const id = positionals[1];
|
|
439
|
+
if (!id) return err(" Usage: policy allow <id> --tool <pattern> [--command|--path|--url <val>]"), 1;
|
|
440
|
+
const toolPattern = flagStr(flags, "tool") ?? "*";
|
|
441
|
+
let kind = "tool";
|
|
442
|
+
let value;
|
|
443
|
+
for (const k of ["command", "path", "url"]) {
|
|
444
|
+
const v = flagStr(flags, k);
|
|
445
|
+
if (v !== void 0) {
|
|
446
|
+
kind = k;
|
|
447
|
+
value = v;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const res = await api.policies.addRule(id, { toolPattern, kind, value });
|
|
451
|
+
if (json) return printJson(res), 0;
|
|
452
|
+
if (res.deduped) err(green(" \u2713 ") + dim("Equivalent ALLOW rule already present."));
|
|
453
|
+
else err(green(` \u2713 Rule added`) + dim(` (${res.rule?.id}) \u2192 v${res.policy_version}`));
|
|
454
|
+
return 0;
|
|
455
|
+
}
|
|
456
|
+
case "revoke": {
|
|
457
|
+
const id = positionals[1];
|
|
458
|
+
const ruleId = positionals[2];
|
|
459
|
+
if (!id || !ruleId) return err(" Usage: policy revoke <id> <ruleId>"), 1;
|
|
460
|
+
const res = await api.policies.revokeRule(id, ruleId);
|
|
461
|
+
if (json) return printJson(res), 0;
|
|
462
|
+
err(green(` \u2713 Revoked ${ruleId}`) + dim(` \u2192 v${res.policy_version}`));
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
case "versions": {
|
|
466
|
+
const id = positionals[1];
|
|
467
|
+
if (!id) return err(" Usage: policy versions <id>"), 1;
|
|
468
|
+
const { versions: versions2 } = await api.policies.versions(id, { limit: flagNum(flags, "limit") });
|
|
469
|
+
if (json) return printJson(versions2), 0;
|
|
470
|
+
table(
|
|
471
|
+
["VER", "RULES", "REASON", "BY", "WHEN"],
|
|
472
|
+
versions2.map((v) => [
|
|
473
|
+
`v${v.version}`,
|
|
474
|
+
String(v.rules_count),
|
|
475
|
+
truncate(v.reason || "\u2014", 40),
|
|
476
|
+
dim(truncate(v.created_by || "\u2014", 18)),
|
|
477
|
+
dim(v.created_at)
|
|
478
|
+
])
|
|
479
|
+
);
|
|
480
|
+
return 0;
|
|
481
|
+
}
|
|
482
|
+
case "rollback": {
|
|
483
|
+
const id = positionals[1];
|
|
484
|
+
const version = Number(positionals[2]);
|
|
485
|
+
if (!id || !Number.isFinite(version)) return err(" Usage: policy rollback <id> <version>"), 1;
|
|
486
|
+
const res = await api.policies.rollback(id, version);
|
|
487
|
+
if (json) return printJson(res), 0;
|
|
488
|
+
err(green(` \u2713 Rolled back ${res.policy_id} from v${res.rolled_back_from} \u2192 v${res.version}`));
|
|
489
|
+
return 0;
|
|
490
|
+
}
|
|
491
|
+
case "active": {
|
|
492
|
+
const a = await api.policies.active();
|
|
493
|
+
if (json) return printJson(a), 0;
|
|
494
|
+
if (!a.policy) {
|
|
495
|
+
err(dim(" No active policy resolves for this project."));
|
|
496
|
+
return 0;
|
|
497
|
+
}
|
|
498
|
+
err("");
|
|
499
|
+
err(` Active: ${bold(a.policy.name)} ${dim(`(${a.policy.id})`)} v${a.version}`);
|
|
500
|
+
err(` matched by: ${cyan(a.matched_by ?? "\u2014")} self-protection: ${a.self_protection_enabled ? green("on") : dim("off")}`);
|
|
501
|
+
const rl = a.security?.rateLimit;
|
|
502
|
+
if (rl) err(` rate limit: ${rl.perMinute}/min ${rl.perHour}/h ${rl.perDay}/day`);
|
|
503
|
+
if (a.security?.dlpBlock) err(` DLP block: ${a.security.dlpBlock.patterns.length} patterns`);
|
|
504
|
+
return 0;
|
|
505
|
+
}
|
|
506
|
+
case "activate": {
|
|
507
|
+
if (flagBool(flags, "clear")) {
|
|
508
|
+
const res2 = await api.policies.setActive(null);
|
|
509
|
+
if (json) return printJson(res2), 0;
|
|
510
|
+
return err(green(" \u2713 Cleared active-policy pin.")), 0;
|
|
511
|
+
}
|
|
512
|
+
const id = positionals[1];
|
|
513
|
+
if (!id) return err(" Usage: policy activate <id> | policy activate --clear"), 1;
|
|
514
|
+
const res = await api.policies.setActive(id);
|
|
515
|
+
if (json) return printJson(res), 0;
|
|
516
|
+
err(green(` \u2713 Pinned active policy \u2192 ${res.active}`));
|
|
517
|
+
return 0;
|
|
518
|
+
}
|
|
519
|
+
case "dry-run": {
|
|
520
|
+
const target = positionals[1];
|
|
521
|
+
if (!target) return err(" Usage: policy dry-run <id|file.json> [--limit N]"), 1;
|
|
522
|
+
const rules = await resolveRules(target);
|
|
523
|
+
const res = await api.policies.dryRun({
|
|
524
|
+
rules,
|
|
525
|
+
mode: flagStr(flags, "mode") ?? void 0,
|
|
526
|
+
limit: flagNum(flags, "limit")
|
|
527
|
+
});
|
|
528
|
+
if (json) return printJson(res), 0;
|
|
529
|
+
err("");
|
|
530
|
+
err(` Replayed ${bold(String(res.evaluated))} recent calls against ${rules.length} rule(s)`);
|
|
531
|
+
err(` would allow: ${green(String(res.would_allow))} would deny: ${decisionColor("DENY")} ${res.would_deny}`);
|
|
532
|
+
err(` ${green("newly allowed")}: ${res.newly_allowed} ${decisionColor("DENY")}${dim(" newly blocked")}: ${res.newly_blocked} unchanged: ${res.unchanged}`);
|
|
533
|
+
if (res.sample_newly_blocked.length) {
|
|
534
|
+
err(dim("\n Sample newly-blocked:"));
|
|
535
|
+
for (const s of res.sample_newly_blocked.slice(0, 8)) err(` ${decisionColor("DENY")} ${s.tool} ${dim(truncate(s.preview, 50))}`);
|
|
536
|
+
}
|
|
537
|
+
return 0;
|
|
538
|
+
}
|
|
539
|
+
default:
|
|
540
|
+
err(` Unknown: policy ${sub}
|
|
541
|
+
`);
|
|
542
|
+
err(USAGE);
|
|
543
|
+
return 1;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function printRules(rules) {
|
|
547
|
+
if (rules.length === 0) return void err(dim(" (no rules)"));
|
|
548
|
+
table(
|
|
549
|
+
["EFFECT", "PRIO", "TOOL", "ID", "DESCRIPTION"],
|
|
550
|
+
rules.map((r) => [
|
|
551
|
+
r.effect === "ALLOW" ? green("ALLOW") : decisionColor("DENY"),
|
|
552
|
+
String(r.priority),
|
|
553
|
+
cyan(truncate(r.toolPattern, 24)),
|
|
554
|
+
dim(truncate(r.id, 22)),
|
|
555
|
+
truncate(r.description || "\u2014", 40)
|
|
556
|
+
])
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
async function resolveRules(target) {
|
|
560
|
+
if (target.endsWith(".json")) {
|
|
561
|
+
const parsed = JSON.parse(readFileSync2(target, "utf-8"));
|
|
562
|
+
return parsed.rules ?? [];
|
|
563
|
+
}
|
|
564
|
+
const p = await api.policies.get(target);
|
|
565
|
+
return p.rules;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/commands/ratelimit.ts
|
|
569
|
+
var USAGE2 = `${bold("solongate ratelimit")} \u2014 request throttling
|
|
570
|
+
|
|
571
|
+
ratelimit show Current limits + change history
|
|
572
|
+
ratelimit set --minute N [--hour N] [--day N] [--mode off|detect|block]
|
|
573
|
+
ratelimit history Recent limit changes
|
|
574
|
+
|
|
575
|
+
Add --json for machine-readable output.`;
|
|
576
|
+
var modeColor = (m) => m === "block" ? green(m) : m === "detect" ? yellow(m) : dim(m);
|
|
577
|
+
async function run2(argv) {
|
|
578
|
+
const { positionals, flags } = parse(argv);
|
|
579
|
+
const sub = positionals[0] ?? "show";
|
|
580
|
+
const json = flagBool(flags, "json");
|
|
581
|
+
switch (sub) {
|
|
582
|
+
case "help":
|
|
583
|
+
return err(USAGE2), 0;
|
|
584
|
+
case "show": {
|
|
585
|
+
const [{ layers }, { history }] = await Promise.all([
|
|
586
|
+
api.settings.getSecurityLayers(),
|
|
587
|
+
api.settings.getRateLimitHistory()
|
|
588
|
+
]);
|
|
589
|
+
if (json) return printJson({ rateLimit: layers.rateLimit, history }), 0;
|
|
590
|
+
const rl = layers.rateLimit;
|
|
591
|
+
err("");
|
|
592
|
+
err(` Rate limit mode: ${modeColor(rl.mode)}`);
|
|
593
|
+
err(` ${bold(String(rl.perMinute))} ${dim("/min")} ${bold(String(rl.perHour))} ${dim("/hour")} ${bold(String(rl.perDay))} ${dim("/day")}`);
|
|
594
|
+
if (history.length) {
|
|
595
|
+
const spark = sparkline(history.map((h) => h.minute));
|
|
596
|
+
err(` history ${cyan(spark)} ${dim(`(${history.length} changes, per-min)`)}`);
|
|
597
|
+
}
|
|
598
|
+
return 0;
|
|
599
|
+
}
|
|
600
|
+
case "history": {
|
|
601
|
+
const { history } = await api.settings.getRateLimitHistory();
|
|
602
|
+
if (json) return printJson(history), 0;
|
|
603
|
+
if (!history.length) return err(dim(" No rate-limit changes recorded.")), 0;
|
|
604
|
+
table(
|
|
605
|
+
["WHEN", "MINUTE", "HOUR", "DAY"],
|
|
606
|
+
history.map((h) => [dim(new Date(h.ts).toISOString()), String(h.minute), String(h.hour), String(h.day)])
|
|
607
|
+
);
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
case "set": {
|
|
611
|
+
const minute = flagNum(flags, "minute");
|
|
612
|
+
const hour = flagNum(flags, "hour");
|
|
613
|
+
const day = flagNum(flags, "day");
|
|
614
|
+
const mode = flagStr(flags, "mode");
|
|
615
|
+
if (minute === void 0 && hour === void 0 && day === void 0 && !mode) {
|
|
616
|
+
return err(" Usage: ratelimit set --minute N [--hour N] [--day N] [--mode off|detect|block]"), 1;
|
|
617
|
+
}
|
|
618
|
+
const { layers } = await api.settings.getSecurityLayers();
|
|
619
|
+
const next = {
|
|
620
|
+
...layers,
|
|
621
|
+
rateLimit: {
|
|
622
|
+
mode: mode ?? layers.rateLimit.mode,
|
|
623
|
+
perMinute: minute ?? layers.rateLimit.perMinute,
|
|
624
|
+
perHour: hour ?? layers.rateLimit.perHour,
|
|
625
|
+
perDay: day ?? layers.rateLimit.perDay
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
const res = await api.settings.setSecurityLayers(next);
|
|
629
|
+
if (json) return printJson(res.layers.rateLimit), 0;
|
|
630
|
+
const r = res.layers.rateLimit;
|
|
631
|
+
err(green(" \u2713 Rate limit updated") + dim(` ${r.perMinute}/min ${r.perHour}/h ${r.perDay}/day (${r.mode})`));
|
|
632
|
+
return 0;
|
|
633
|
+
}
|
|
634
|
+
default:
|
|
635
|
+
return err(USAGE2), 1;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/commands/dlp.ts
|
|
640
|
+
var USAGE3 = `${bold("solongate dlp")} \u2014 data-loss prevention
|
|
641
|
+
|
|
642
|
+
dlp show Current mode + enabled patterns
|
|
643
|
+
dlp mode <off|detect|block> Set enforcement mode
|
|
644
|
+
dlp enable <pattern> Enable a built-in pattern
|
|
645
|
+
dlp disable <pattern> Disable a built-in pattern
|
|
646
|
+
dlp add-custom --name X --re <regex> Add a custom pattern
|
|
647
|
+
dlp remove-custom <name> Remove a custom pattern
|
|
648
|
+
|
|
649
|
+
Add --json for machine-readable output.`;
|
|
650
|
+
var modeColor2 = (m) => m === "block" ? green(m) : m === "detect" ? yellow(m) : dim(m);
|
|
651
|
+
async function run3(argv) {
|
|
652
|
+
const { positionals, flags } = parse(argv);
|
|
653
|
+
const sub = positionals[0] ?? "show";
|
|
654
|
+
const json = flagBool(flags, "json");
|
|
655
|
+
const { layers, availablePatterns } = await api.settings.getSecurityLayers();
|
|
656
|
+
const save = async (next) => (await api.settings.setSecurityLayers(next)).layers;
|
|
657
|
+
switch (sub) {
|
|
658
|
+
case "help":
|
|
659
|
+
return err(USAGE3), 0;
|
|
660
|
+
case "show": {
|
|
661
|
+
if (json) return printJson({ dlp: layers.dlp, availablePatterns }), 0;
|
|
662
|
+
err("");
|
|
663
|
+
err(` DLP mode: ${modeColor2(layers.dlp.mode)}`);
|
|
664
|
+
const enabled = new Set(layers.dlp.patterns);
|
|
665
|
+
table(
|
|
666
|
+
["", "PATTERN"],
|
|
667
|
+
availablePatterns.map((p) => [enabled.has(p) ? green("\u25CF") : dim("\u25CB"), enabled.has(p) ? p : dim(p)])
|
|
668
|
+
);
|
|
669
|
+
if (layers.dlp.custom.length) {
|
|
670
|
+
err(dim("\n Custom:"));
|
|
671
|
+
for (const c2 of layers.dlp.custom) err(` ${cyan(c2.name)} ${dim(c2.re)}`);
|
|
672
|
+
}
|
|
673
|
+
return 0;
|
|
674
|
+
}
|
|
675
|
+
case "mode": {
|
|
676
|
+
const mode = positionals[1];
|
|
677
|
+
if (!mode || !["off", "detect", "block"].includes(mode)) return err(" Usage: dlp mode <off|detect|block>"), 1;
|
|
678
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, mode } });
|
|
679
|
+
if (json) return printJson(saved.dlp), 0;
|
|
680
|
+
return err(green(` \u2713 DLP mode \u2192 ${saved.dlp.mode}`)), 0;
|
|
681
|
+
}
|
|
682
|
+
case "enable":
|
|
683
|
+
case "disable": {
|
|
684
|
+
const pattern = positionals.slice(1).join(" ");
|
|
685
|
+
if (!pattern) return err(` Usage: dlp ${sub} <pattern>`), 1;
|
|
686
|
+
if (!availablePatterns.includes(pattern)) {
|
|
687
|
+
err(` Unknown pattern: "${pattern}". Available:`);
|
|
688
|
+
for (const p of availablePatterns) err(` ${dim("\u2022")} ${p}`);
|
|
689
|
+
return 1;
|
|
690
|
+
}
|
|
691
|
+
const set = new Set(layers.dlp.patterns);
|
|
692
|
+
if (sub === "enable") set.add(pattern);
|
|
693
|
+
else set.delete(pattern);
|
|
694
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, patterns: [...set] } });
|
|
695
|
+
if (json) return printJson(saved.dlp), 0;
|
|
696
|
+
return err(green(` \u2713 ${sub}d "${pattern}"`) + dim(` (${saved.dlp.patterns.length} active)`)), 0;
|
|
697
|
+
}
|
|
698
|
+
case "add-custom": {
|
|
699
|
+
const name = flagStr(flags, "name");
|
|
700
|
+
const re = flagStr(flags, "re");
|
|
701
|
+
if (!name || !re) return err(" Usage: dlp add-custom --name <name> --re <regex>"), 1;
|
|
702
|
+
const custom = [...layers.dlp.custom.filter((c2) => c2.name !== name), { name, re }];
|
|
703
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, custom } });
|
|
704
|
+
if (json) return printJson(saved.dlp), 0;
|
|
705
|
+
return err(green(` \u2713 Custom pattern "${name}" added`)), 0;
|
|
706
|
+
}
|
|
707
|
+
case "remove-custom": {
|
|
708
|
+
const name = positionals.slice(1).join(" ");
|
|
709
|
+
if (!name) return err(" Usage: dlp remove-custom <name>"), 1;
|
|
710
|
+
const custom = layers.dlp.custom.filter((c2) => c2.name !== name);
|
|
711
|
+
const saved = await save({ ...layers, dlp: { ...layers.dlp, custom } });
|
|
712
|
+
if (json) return printJson(saved.dlp), 0;
|
|
713
|
+
return err(green(` \u2713 Removed custom pattern "${name}"`)), 0;
|
|
714
|
+
}
|
|
715
|
+
default:
|
|
716
|
+
return err(USAGE3), 1;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/commands/stats.ts
|
|
721
|
+
var USAGE4 = `${bold("solongate stats")} \u2014 traffic & security stats
|
|
722
|
+
|
|
723
|
+
stats Overview (totals, recent activity)
|
|
724
|
+
stats timeseries [--period 24h|7d|30d|all]
|
|
725
|
+
stats drift [--days N] Denials rising/falling vs previous window
|
|
726
|
+
|
|
727
|
+
Add --json for machine-readable output.`;
|
|
728
|
+
async function run4(argv) {
|
|
729
|
+
const { positionals, flags } = parse(argv);
|
|
730
|
+
const sub = positionals[0] ?? "overview";
|
|
731
|
+
const json = flagBool(flags, "json");
|
|
732
|
+
switch (sub) {
|
|
733
|
+
case "help":
|
|
734
|
+
return err(USAGE4), 0;
|
|
735
|
+
case "overview": {
|
|
736
|
+
const s = await api.stats.get();
|
|
737
|
+
if (json) return printJson(s), 0;
|
|
738
|
+
err("");
|
|
739
|
+
err(` ${bold(String(s.total_calls))} calls ${green(String(s.allowed))} allowed ${red(String(s.denied))} denied`);
|
|
740
|
+
err(` ${dim(`${s.active_policies} active policies \xB7 ${s.registered_tools} tools`)}`);
|
|
741
|
+
if (s.recent_activity.length) {
|
|
742
|
+
err(dim("\n Recent:"));
|
|
743
|
+
table(
|
|
744
|
+
["DECISION", "TOOL", "TRUST", "MS", "WHEN"],
|
|
745
|
+
s.recent_activity.map((a) => [
|
|
746
|
+
decisionColor(a.decision),
|
|
747
|
+
cyan(truncate(a.tool_name, 24)),
|
|
748
|
+
dim(a.trust_level),
|
|
749
|
+
String(a.evaluation_time_ms ?? "\u2014"),
|
|
750
|
+
dim(a.created_at)
|
|
751
|
+
])
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
return 0;
|
|
755
|
+
}
|
|
756
|
+
case "timeseries": {
|
|
757
|
+
const period = flagStr(flags, "period") ?? "24h";
|
|
758
|
+
const ts = await api.stats.timeseries({ period });
|
|
759
|
+
if (json) return printJson(ts), 0;
|
|
760
|
+
const pts = ts.timeseries;
|
|
761
|
+
err("");
|
|
762
|
+
err(` Timeseries ${dim(`(${ts.period}, per ${ts.granularity})`)}`);
|
|
763
|
+
err(` total ${cyan(sparkline(pts.map((p) => p.total)))} ${dim(`max ${Math.max(0, ...pts.map((p) => p.total))}`)}`);
|
|
764
|
+
err(` allowed ${green(sparkline(pts.map((p) => p.allowed)))}`);
|
|
765
|
+
err(` denied ${red(sparkline(pts.map((p) => p.denied)))}`);
|
|
766
|
+
return 0;
|
|
767
|
+
}
|
|
768
|
+
case "drift": {
|
|
769
|
+
const d = await api.stats.drift(flagNum(flags, "days"));
|
|
770
|
+
if (json) return printJson(d), 0;
|
|
771
|
+
err("");
|
|
772
|
+
err(` Denial drift ${dim(`(${d.days}d: ${d.total_current} now vs ${d.total_previous} prev)`)}`);
|
|
773
|
+
if (!d.rules.length) return err(dim(" No denials in window.")), 0;
|
|
774
|
+
table(
|
|
775
|
+
["NOW", "PREV", "\u0394", "RULE", "REASON"],
|
|
776
|
+
d.rules.slice(0, 20).map((r) => [
|
|
777
|
+
bold(String(r.current)),
|
|
778
|
+
dim(String(r.previous)),
|
|
779
|
+
r.is_new ? green("NEW") : r.spike ? red(`+${r.delta}`) : String(r.delta),
|
|
780
|
+
cyan(truncate(r.rule_id ?? "\u2014", 22)),
|
|
781
|
+
truncate(r.reason ?? r.last_tool ?? "\u2014", 36)
|
|
782
|
+
])
|
|
783
|
+
);
|
|
784
|
+
return 0;
|
|
785
|
+
}
|
|
786
|
+
default:
|
|
787
|
+
return err(USAGE4), 1;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/commands/audit.ts
|
|
792
|
+
var USAGE5 = `${bold("solongate audit")} \u2014 audit log
|
|
793
|
+
|
|
794
|
+
audit [--filter ALLOW|DENY] [--tool <substr>] [--signal dlp|ratelimit]
|
|
795
|
+
[--search <text>] [--agent-name <name>] [--limit N]
|
|
796
|
+
audit whitelist <logId> [--scope exact|tool]
|
|
797
|
+
Turn a denied call into an ALLOW rule
|
|
798
|
+
|
|
799
|
+
Add --json for machine-readable output.`;
|
|
800
|
+
async function run5(argv) {
|
|
801
|
+
const { positionals, flags } = parse(argv);
|
|
802
|
+
const json = flagBool(flags, "json");
|
|
803
|
+
if (positionals[0] === "help") return err(USAGE5), 0;
|
|
804
|
+
if (positionals[0] === "whitelist") {
|
|
805
|
+
const id = positionals[1];
|
|
806
|
+
if (!id) return err(" Usage: audit whitelist <logId> [--scope exact|tool]"), 1;
|
|
807
|
+
const scope = flagStr(flags, "scope") ?? "exact";
|
|
808
|
+
const res2 = await api.audit.whitelist(id, scope);
|
|
809
|
+
if (json) return printJson(res2), 0;
|
|
810
|
+
if (res2.deduped) err(green(" \u2713 ") + dim("Equivalent ALLOW already present."));
|
|
811
|
+
else err(green(` \u2713 Whitelisted (${res2.scope})`) + dim(` \u2192 ${res2.policy_id} v${res2.policy_version}`));
|
|
812
|
+
return 0;
|
|
813
|
+
}
|
|
814
|
+
const query = {
|
|
815
|
+
filter: flagStr(flags, "filter"),
|
|
816
|
+
tool: flagStr(flags, "tool"),
|
|
817
|
+
signal: flagStr(flags, "signal"),
|
|
818
|
+
search: flagStr(flags, "search"),
|
|
819
|
+
agent_name: flagStr(flags, "agent-name"),
|
|
820
|
+
limit: flagNum(flags, "limit") ?? 30
|
|
821
|
+
};
|
|
822
|
+
const res = await api.audit.list(query);
|
|
823
|
+
if (json) return printJson(res), 0;
|
|
824
|
+
err("");
|
|
825
|
+
err(` ${bold(String(res.total))} matching entries ${dim(`(showing ${res.entries.length})`)}`);
|
|
826
|
+
if (!res.entries.length) return 0;
|
|
827
|
+
table(
|
|
828
|
+
["DECISION", "TOOL", "AGENT", "REASON", "DLP", "WHEN", "ID"],
|
|
829
|
+
res.entries.map((e) => [
|
|
830
|
+
decisionColor(e.decision),
|
|
831
|
+
cyan(truncate(e.tool_name, 22)),
|
|
832
|
+
dim(truncate(e.agent_name ?? "\u2014", 16)),
|
|
833
|
+
truncate(e.reason ?? "\u2014", 30),
|
|
834
|
+
e.dlp_matches?.length ? red(String(e.dlp_matches.length)) : dim("0"),
|
|
835
|
+
dim(e.created_at),
|
|
836
|
+
dim(truncate(e.id, 10))
|
|
837
|
+
])
|
|
838
|
+
);
|
|
839
|
+
return 0;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// src/commands/agents.ts
|
|
843
|
+
var statusColor = (s) => s === "active" ? green(s) : s === "idle" ? yellow(s) : dim(s);
|
|
844
|
+
async function runAgents(argv) {
|
|
845
|
+
const { flags } = parse(argv);
|
|
846
|
+
const json = flagBool(flags, "json");
|
|
847
|
+
const res = await api.agents.live({ limit: flagNum(flags, "limit"), includeDeactivated: flagBool(flags, "all") });
|
|
848
|
+
if (json) return printJson(res), 0;
|
|
849
|
+
err("");
|
|
850
|
+
err(` Agents ${green(String(res.counts.active))} active ${yellow(String(res.counts.idle))} idle ${dim(String(res.counts.deactivated) + " off")}`);
|
|
851
|
+
if (!res.agents.length) return err(dim(" No agent sessions.")), 0;
|
|
852
|
+
table(
|
|
853
|
+
["STATUS", "AGENT", "CALLS", "DENY", "DLP", "TRUST", "CHARACTER"],
|
|
854
|
+
res.agents.map((a) => [
|
|
855
|
+
statusColor(a.status),
|
|
856
|
+
cyan(truncate(a.agent_name ?? a.agent_id ?? a.session_id, 20)),
|
|
857
|
+
String(a.total_calls),
|
|
858
|
+
a.denied_calls ? red(String(a.denied_calls)) : dim("0"),
|
|
859
|
+
a.dlp_events ? red(String(a.dlp_events)) : dim("0"),
|
|
860
|
+
`${a.trust_score}`,
|
|
861
|
+
dim(truncate(a.character || "\u2014", 22))
|
|
862
|
+
])
|
|
863
|
+
);
|
|
864
|
+
return 0;
|
|
865
|
+
}
|
|
866
|
+
async function runAgent(argv) {
|
|
867
|
+
const { positionals, flags } = parse(argv);
|
|
868
|
+
const json = flagBool(flags, "json");
|
|
869
|
+
const id = positionals[0];
|
|
870
|
+
if (!id) return err(" Usage: solongate agent <agent_id> [--json]"), 1;
|
|
871
|
+
const a = await api.agents.get(id);
|
|
872
|
+
if (json) return printJson(a), 0;
|
|
873
|
+
err("");
|
|
874
|
+
err(` ${bold(a.agent_id)} status: ${statusColor(String(a.status))}`);
|
|
875
|
+
const base = a.baseline;
|
|
876
|
+
if (base) {
|
|
877
|
+
err(` ${dim(base.character ?? "")} trust ${bold(String(base.trustScore ?? "\u2014"))}/100 deny-rate ${(Number(base.denyRate ?? 0) * 100).toFixed(0)}%`);
|
|
878
|
+
}
|
|
879
|
+
const feed = a.recent_feed ?? [];
|
|
880
|
+
if (feed.length) {
|
|
881
|
+
err(dim("\n Recent:"));
|
|
882
|
+
table(
|
|
883
|
+
["DECISION", "TOOL", "REASON", "WHEN"],
|
|
884
|
+
feed.slice(0, 15).map((f) => [
|
|
885
|
+
f.decision === "ALLOW" ? green("ALLOW") : red(String(f.decision)),
|
|
886
|
+
cyan(truncate(String(f.tool), 22)),
|
|
887
|
+
truncate(String(f.reason ?? "\u2014"), 30),
|
|
888
|
+
dim(String(f.created_at))
|
|
889
|
+
])
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
return 0;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/commands/index.ts
|
|
896
|
+
async function dispatch(command, argv) {
|
|
897
|
+
switch (command) {
|
|
898
|
+
case "policy":
|
|
899
|
+
return run(argv);
|
|
900
|
+
case "ratelimit":
|
|
901
|
+
return run2(argv);
|
|
902
|
+
case "dlp":
|
|
903
|
+
return run3(argv);
|
|
904
|
+
case "stats":
|
|
905
|
+
return run4(argv);
|
|
906
|
+
case "audit":
|
|
907
|
+
return run5(argv);
|
|
908
|
+
case "agents":
|
|
909
|
+
return runAgents(argv);
|
|
910
|
+
case "agent":
|
|
911
|
+
return runAgent(argv);
|
|
912
|
+
default:
|
|
913
|
+
err(` Unknown command: ${command}`);
|
|
914
|
+
return 1;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
async function runCommand(command, argv) {
|
|
918
|
+
try {
|
|
919
|
+
return await dispatch(command, argv);
|
|
920
|
+
} catch (e) {
|
|
921
|
+
if (e instanceof NotAuthenticatedError) {
|
|
922
|
+
err(red(" \u2717 ") + e.message);
|
|
923
|
+
return 1;
|
|
924
|
+
}
|
|
925
|
+
if (e instanceof ApiError) {
|
|
926
|
+
err(red(" \u2717 ") + `${e.message}` + (e.status ? ` (${e.status})` : ""));
|
|
927
|
+
return 1;
|
|
928
|
+
}
|
|
929
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
930
|
+
err(red(" \u2717 ") + msg);
|
|
931
|
+
return 1;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
var COMMAND_NAMES = ["policy", "ratelimit", "dlp", "stats", "audit", "agents", "agent"];
|
|
935
|
+
export {
|
|
936
|
+
COMMAND_NAMES,
|
|
937
|
+
runCommand
|
|
938
|
+
};
|