sovr-mcp-proxy 7.0.0 → 7.2.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.
@@ -0,0 +1,398 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/auditDashboard.ts
21
+ var auditDashboard_exports = {};
22
+ __export(auditDashboard_exports, {
23
+ AuditDashboard: () => AuditDashboard,
24
+ createAuditDashboard: () => createAuditDashboard,
25
+ createFreeTierDashboard: () => createFreeTierDashboard,
26
+ createProDashboard: () => createProDashboard
27
+ });
28
+ module.exports = __toCommonJS(auditDashboard_exports);
29
+ var AuditDashboard = class {
30
+ entries = [];
31
+ config;
32
+ saveTimer = null;
33
+ quotaCounter = 0;
34
+ quotaPeriodStart = Date.now();
35
+ constructor(config = {}) {
36
+ this.config = {
37
+ maxEntries: config.maxEntries ?? 5e4,
38
+ persistPath: config.persistPath,
39
+ autoSaveInterval: config.autoSaveInterval ?? 6e4,
40
+ quotaLimit: config.quotaLimit ?? 1e4,
41
+ quotaPeriod: config.quotaPeriod ?? 30 * 24 * 60 * 60 * 1e3,
42
+ // 30 days
43
+ redactPatterns: config.redactPatterns ?? [
44
+ /password/i,
45
+ /secret/i,
46
+ /token/i,
47
+ /api.?key/i,
48
+ /credential/i,
49
+ /authorization/i,
50
+ /cookie/i,
51
+ /session/i
52
+ ]
53
+ };
54
+ if (this.config.persistPath && this.config.autoSaveInterval > 0) {
55
+ this.saveTimer = setInterval(() => this.persist(), this.config.autoSaveInterval);
56
+ }
57
+ }
58
+ // ─── Core Operations ───────────────────────────────────────────────────
59
+ /** Record a new audit entry */
60
+ record(entry) {
61
+ const now = Date.now();
62
+ const fullEntry = {
63
+ ...entry,
64
+ id: `ae_${now}_${Math.random().toString(36).slice(2, 8)}`,
65
+ timestamp: new Date(now).toISOString(),
66
+ epochMs: now,
67
+ arguments: this.redactSensitive(entry.arguments)
68
+ };
69
+ this.entries.push(fullEntry);
70
+ this.quotaCounter++;
71
+ if (this.entries.length > this.config.maxEntries) {
72
+ this.entries = this.entries.slice(-this.config.maxEntries);
73
+ }
74
+ return fullEntry;
75
+ }
76
+ /** Get entries with optional filters */
77
+ query(filters = {}) {
78
+ let filtered = this.entries;
79
+ if (filters.from) filtered = filtered.filter((e) => e.epochMs >= filters.from);
80
+ if (filters.to) filtered = filtered.filter((e) => e.epochMs <= filters.to);
81
+ if (filters.toolName) filtered = filtered.filter((e) => e.toolName === filters.toolName);
82
+ if (filters.decision) filtered = filtered.filter((e) => e.decision === filters.decision);
83
+ if (filters.riskLevel) filtered = filtered.filter((e) => e.riskLevel === filters.riskLevel);
84
+ if (filters.sessionId) filtered = filtered.filter((e) => e.sessionId === filters.sessionId);
85
+ if (filters.agentId) filtered = filtered.filter((e) => e.agentId === filters.agentId);
86
+ const total = filtered.length;
87
+ const offset = filters.offset ?? 0;
88
+ const limit = filters.limit ?? 100;
89
+ const paged = filtered.slice(offset, offset + limit);
90
+ return { entries: paged, total };
91
+ }
92
+ /** Get a single entry by ID */
93
+ getById(id) {
94
+ return this.entries.find((e) => e.id === id);
95
+ }
96
+ // ─── Statistics ────────────────────────────────────────────────────────
97
+ /** Get dashboard statistics for a time range */
98
+ getStats(from, to) {
99
+ const now = Date.now();
100
+ const rangeFrom = from ?? now - 24 * 60 * 60 * 1e3;
101
+ const rangeTo = to ?? now;
102
+ const filtered = this.entries.filter((e) => e.epochMs >= rangeFrom && e.epochMs <= rangeTo);
103
+ const byDecision = {};
104
+ const byRiskLevel = {};
105
+ const byTool = {};
106
+ const byType = {};
107
+ const policyCount = {};
108
+ const blockedTools = {};
109
+ const latencies = [];
110
+ for (const entry of filtered) {
111
+ byDecision[entry.decision] = (byDecision[entry.decision] || 0) + 1;
112
+ byRiskLevel[entry.riskLevel] = (byRiskLevel[entry.riskLevel] || 0) + 1;
113
+ byTool[entry.toolName] = (byTool[entry.toolName] || 0) + 1;
114
+ byType[entry.type] = (byType[entry.type] || 0) + 1;
115
+ latencies.push(entry.durationMs);
116
+ if (entry.policyId) {
117
+ policyCount[entry.policyId] = (policyCount[entry.policyId] || 0) + 1;
118
+ }
119
+ if (entry.decision === "block") {
120
+ blockedTools[entry.toolName] = (blockedTools[entry.toolName] || 0) + 1;
121
+ }
122
+ }
123
+ const sortedLatencies = latencies.sort((a, b) => a - b);
124
+ const p95Index = Math.floor(sortedLatencies.length * 0.95);
125
+ const p99Index = Math.floor(sortedLatencies.length * 0.99);
126
+ const topBlocked = Object.entries(blockedTools).sort(([, a], [, b]) => b - a).slice(0, 10).map(([tool, count]) => ({ tool, count }));
127
+ const topPolicies = Object.entries(policyCount).sort(([, a], [, b]) => b - a).slice(0, 10).map(([policyId, count]) => ({ policyId, count }));
128
+ const riskHistogram = [
129
+ { range: "0-20", count: filtered.filter((e) => e.riskScore < 20).length },
130
+ { range: "20-40", count: filtered.filter((e) => e.riskScore >= 20 && e.riskScore < 40).length },
131
+ { range: "40-60", count: filtered.filter((e) => e.riskScore >= 40 && e.riskScore < 60).length },
132
+ { range: "60-80", count: filtered.filter((e) => e.riskScore >= 60 && e.riskScore < 80).length },
133
+ { range: "80-100", count: filtered.filter((e) => e.riskScore >= 80).length }
134
+ ];
135
+ const durationMinutes = Math.max(1, (rangeTo - rangeFrom) / 6e4);
136
+ const allowed = byDecision["allow"] || 0;
137
+ return {
138
+ timeRange: { from: new Date(rangeFrom).toISOString(), to: new Date(rangeTo).toISOString() },
139
+ totalEvents: filtered.length,
140
+ byDecision,
141
+ byRiskLevel,
142
+ byTool,
143
+ byType,
144
+ successRate: filtered.length > 0 ? Math.round(allowed / filtered.length * 1e4) / 100 : 100,
145
+ avgLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : 0,
146
+ p95LatencyMs: sortedLatencies[p95Index] ?? 0,
147
+ p99LatencyMs: sortedLatencies[p99Index] ?? 0,
148
+ eventsPerMinute: Math.round(filtered.length / durationMinutes * 100) / 100,
149
+ topBlocked,
150
+ topPolicies,
151
+ riskHistogram
152
+ };
153
+ }
154
+ // ─── Trends ────────────────────────────────────────────────────────────
155
+ /** Get time-series trend data */
156
+ getTrends(granularity = "hourly", from, to) {
157
+ const now = Date.now();
158
+ const rangeFrom = from ?? now - 7 * 24 * 60 * 60 * 1e3;
159
+ const rangeTo = to ?? now;
160
+ const bucketSize = granularity === "hourly" ? 36e5 : granularity === "daily" ? 864e5 : 6048e5;
161
+ const filtered = this.entries.filter((e) => e.epochMs >= rangeFrom && e.epochMs <= rangeTo);
162
+ const buckets = /* @__PURE__ */ new Map();
163
+ for (const entry of filtered) {
164
+ const bucketStart = Math.floor(entry.epochMs / bucketSize) * bucketSize;
165
+ if (!buckets.has(bucketStart)) buckets.set(bucketStart, []);
166
+ buckets.get(bucketStart).push(entry);
167
+ }
168
+ const points = [];
169
+ let current = Math.floor(rangeFrom / bucketSize) * bucketSize;
170
+ while (current <= rangeTo) {
171
+ const entries = buckets.get(current) || [];
172
+ const allowed = entries.filter((e) => e.decision === "allow" || e.decision === "transform").length;
173
+ const blocked = entries.filter((e) => e.decision === "block" || e.decision === "rate-limited").length;
174
+ points.push({
175
+ label: this.formatBucketLabel(current, granularity),
176
+ epochMs: current,
177
+ total: entries.length,
178
+ allowed,
179
+ blocked,
180
+ avgRisk: entries.length > 0 ? Math.round(entries.reduce((sum, e) => sum + e.riskScore, 0) / entries.length) : 0,
181
+ avgLatency: entries.length > 0 ? Math.round(entries.reduce((sum, e) => sum + e.durationMs, 0) / entries.length) : 0
182
+ });
183
+ current += bucketSize;
184
+ }
185
+ return points;
186
+ }
187
+ // ─── Quota ─────────────────────────────────────────────────────────────
188
+ /** Get current quota status with prediction */
189
+ getQuotaStatus() {
190
+ const now = Date.now();
191
+ if (now - this.quotaPeriodStart > this.config.quotaPeriod) {
192
+ this.quotaCounter = 0;
193
+ this.quotaPeriodStart = now;
194
+ }
195
+ const elapsed = now - this.quotaPeriodStart;
196
+ const elapsedDays = Math.max(1, elapsed / (24 * 60 * 60 * 1e3));
197
+ const dailyAverage = Math.round(this.quotaCounter / elapsedDays);
198
+ const remaining = this.config.quotaLimit - this.quotaCounter;
199
+ const daysRemaining = dailyAverage > 0 ? Math.round(remaining / dailyAverage) : void 0;
200
+ let predictedExhaustion;
201
+ if (daysRemaining !== void 0 && daysRemaining > 0) {
202
+ predictedExhaustion = new Date(now + daysRemaining * 24 * 60 * 60 * 1e3).toISOString();
203
+ }
204
+ return {
205
+ used: this.quotaCounter,
206
+ limit: this.config.quotaLimit,
207
+ percentage: Math.round(this.quotaCounter / this.config.quotaLimit * 1e4) / 100,
208
+ predictedExhaustion,
209
+ dailyAverage,
210
+ daysRemaining
211
+ };
212
+ }
213
+ // ─── Export ────────────────────────────────────────────────────────────
214
+ /** Export audit log as CSV */
215
+ exportCSV(filters) {
216
+ const { entries } = this.query({ from: filters?.from, to: filters?.to, limit: this.config.maxEntries });
217
+ const headers = [
218
+ "id",
219
+ "timestamp",
220
+ "type",
221
+ "toolName",
222
+ "decision",
223
+ "riskScore",
224
+ "riskLevel",
225
+ "policyId",
226
+ "reason",
227
+ "durationMs",
228
+ "sessionId",
229
+ "agentId",
230
+ "error"
231
+ ];
232
+ const rows = entries.map((e) => [
233
+ e.id,
234
+ e.timestamp,
235
+ e.type,
236
+ e.toolName,
237
+ e.decision,
238
+ e.riskScore,
239
+ e.riskLevel,
240
+ e.policyId || "",
241
+ `"${(e.reason || "").replace(/"/g, '""')}"`,
242
+ e.durationMs,
243
+ e.sessionId || "",
244
+ e.agentId || "",
245
+ e.error ? `"${e.error.replace(/"/g, '""')}"` : ""
246
+ ].join(","));
247
+ return [headers.join(","), ...rows].join("\n");
248
+ }
249
+ /** Export audit log as JSON */
250
+ exportJSON(filters) {
251
+ const { entries } = this.query({ from: filters?.from, to: filters?.to, limit: this.config.maxEntries });
252
+ return JSON.stringify({
253
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
254
+ totalEntries: entries.length,
255
+ entries
256
+ }, null, 2);
257
+ }
258
+ /** Export Trust Bundle (evidence package) */
259
+ exportTrustBundle(sessionId) {
260
+ const { entries } = sessionId ? this.query({ sessionId, limit: this.config.maxEntries }) : this.query({ limit: 1e3 });
261
+ const stats = this.getStats();
262
+ const dataStr = JSON.stringify(entries);
263
+ let hash = 0;
264
+ for (let i = 0; i < dataStr.length; i++) {
265
+ const char = dataStr.charCodeAt(i);
266
+ hash = (hash << 5) - hash + char;
267
+ hash |= 0;
268
+ }
269
+ return {
270
+ bundleId: `tb_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
271
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
272
+ entries,
273
+ stats,
274
+ integrity: `sha256:${Math.abs(hash).toString(16).padStart(16, "0")}`
275
+ };
276
+ }
277
+ // ─── Session Management ────────────────────────────────────────────────
278
+ /** Start a new audit session */
279
+ startSession(agentId) {
280
+ const sessionId = `ses_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
281
+ this.record({
282
+ type: "session_start",
283
+ toolName: "__session__",
284
+ arguments: {},
285
+ decision: "allow",
286
+ riskScore: 0,
287
+ riskLevel: "safe",
288
+ reason: "Session started",
289
+ durationMs: 0,
290
+ sessionId,
291
+ agentId
292
+ });
293
+ return sessionId;
294
+ }
295
+ /** End an audit session */
296
+ endSession(sessionId) {
297
+ this.record({
298
+ type: "session_end",
299
+ toolName: "__session__",
300
+ arguments: {},
301
+ decision: "allow",
302
+ riskScore: 0,
303
+ riskLevel: "safe",
304
+ reason: "Session ended",
305
+ durationMs: 0,
306
+ sessionId
307
+ });
308
+ }
309
+ // ─── Lifecycle ─────────────────────────────────────────────────────────
310
+ /** Get total entry count */
311
+ get size() {
312
+ return this.entries.length;
313
+ }
314
+ /** Clear all entries */
315
+ clear() {
316
+ this.entries = [];
317
+ }
318
+ /** Destroy and cleanup */
319
+ destroy() {
320
+ if (this.saveTimer) {
321
+ clearInterval(this.saveTimer);
322
+ this.saveTimer = null;
323
+ }
324
+ this.persist();
325
+ }
326
+ // ─── Private ─────────────────────────────────────────────────────────────
327
+ redactSensitive(args) {
328
+ const result = JSON.parse(JSON.stringify(args));
329
+ const redact = (obj) => {
330
+ for (const key of Object.keys(obj)) {
331
+ if (this.config.redactPatterns.some((p) => p.test(key))) {
332
+ obj[key] = "[REDACTED]";
333
+ } else if (typeof obj[key] === "object" && obj[key] !== null) {
334
+ redact(obj[key]);
335
+ } else if (typeof obj[key] === "string") {
336
+ let val = obj[key];
337
+ for (const pattern of this.config.redactPatterns) {
338
+ if (pattern.test(val)) {
339
+ obj[key] = "[REDACTED]";
340
+ break;
341
+ }
342
+ }
343
+ }
344
+ }
345
+ };
346
+ redact(result);
347
+ return result;
348
+ }
349
+ formatBucketLabel(epochMs, granularity) {
350
+ const d = new Date(epochMs);
351
+ switch (granularity) {
352
+ case "hourly":
353
+ return `${d.getUTCMonth() + 1}/${d.getUTCDate()} ${d.getUTCHours().toString().padStart(2, "0")}:00`;
354
+ case "daily":
355
+ return `${d.getUTCFullYear()}-${(d.getUTCMonth() + 1).toString().padStart(2, "0")}-${d.getUTCDate().toString().padStart(2, "0")}`;
356
+ case "weekly":
357
+ return `Week of ${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
358
+ default:
359
+ return d.toISOString();
360
+ }
361
+ }
362
+ persist() {
363
+ if (!this.config.persistPath) return;
364
+ try {
365
+ const data = JSON.stringify({
366
+ entries: this.entries.slice(-this.config.maxEntries),
367
+ quotaCounter: this.quotaCounter,
368
+ quotaPeriodStart: this.quotaPeriodStart
369
+ });
370
+ void data;
371
+ } catch {
372
+ }
373
+ }
374
+ };
375
+ function createAuditDashboard(overrides = {}) {
376
+ return new AuditDashboard(overrides);
377
+ }
378
+ function createFreeTierDashboard() {
379
+ return new AuditDashboard({
380
+ maxEntries: 1e3,
381
+ quotaLimit: 500,
382
+ quotaPeriod: 30 * 24 * 60 * 60 * 1e3
383
+ });
384
+ }
385
+ function createProDashboard() {
386
+ return new AuditDashboard({
387
+ maxEntries: 5e4,
388
+ quotaLimit: 5e4,
389
+ quotaPeriod: 30 * 24 * 60 * 60 * 1e3
390
+ });
391
+ }
392
+ // Annotate the CommonJS export names for ESM import in node:
393
+ 0 && (module.exports = {
394
+ AuditDashboard,
395
+ createAuditDashboard,
396
+ createFreeTierDashboard,
397
+ createProDashboard
398
+ });