@revealui/security 0.2.4 → 0.2.5

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
@@ -1,394 +1,227 @@
1
- // src/audit.ts
2
- var AuditSystem = class {
3
- storage;
4
- filters = [];
5
- constructor(storage) {
6
- this.storage = storage;
1
+ import {
2
+ AuditReportGenerator,
3
+ AuditSystem,
4
+ AuditTrail,
5
+ InMemoryAuditStorage,
6
+ audit,
7
+ createAuditMiddleware,
8
+ signAuditEntry,
9
+ verifyAuditEntry
10
+ } from "./chunk-Q5KAPSST.js";
11
+
12
+ // src/logger.ts
13
+ var securityLogger = console;
14
+ function configureSecurityLogger(logger) {
15
+ securityLogger = logger;
16
+ }
17
+ function getSecurityLogger() {
18
+ return securityLogger;
19
+ }
20
+
21
+ // src/alerting.ts
22
+ var DEFAULT_THRESHOLDS = {
23
+ failedLogins: {
24
+ maxCount: 10,
25
+ windowMs: 15 * 60 * 1e3,
26
+ severity: "high",
27
+ messageTemplate: "Excessive failed logins detected: {count} attempts in 15 minutes"
28
+ },
29
+ privilegeEscalation: {
30
+ maxCount: 1,
31
+ windowMs: 60 * 60 * 1e3,
32
+ severity: "critical",
33
+ messageTemplate: "Privilege escalation detected: role changed to admin"
34
+ },
35
+ massDataExport: {
36
+ maxCount: 100,
37
+ windowMs: 60 * 60 * 1e3,
38
+ severity: "high",
39
+ messageTemplate: "Mass data export detected: {count} exports in 1 hour"
40
+ },
41
+ accountLockout: {
42
+ maxCount: 1,
43
+ windowMs: 60 * 60 * 1e3,
44
+ severity: "high",
45
+ messageTemplate: "Account lockout triggered"
46
+ },
47
+ mfaDisabled: {
48
+ maxCount: 1,
49
+ windowMs: 60 * 60 * 1e3,
50
+ severity: "critical",
51
+ messageTemplate: "MFA disabled on account"
7
52
  }
8
- /**
9
- * Log audit event
10
- */
11
- async log(event) {
12
- const fullEvent = {
13
- ...event,
14
- id: crypto.randomUUID(),
15
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
16
- };
17
- const shouldLog = this.filters.every((filter) => filter(fullEvent));
18
- if (!shouldLog) {
19
- return;
53
+ };
54
+ var LogAlertHandler = class {
55
+ /** Write alert details to the configured security logger. */
56
+ async handle(alert) {
57
+ const logger = getSecurityLogger();
58
+ const prefix = `[SecurityAlert:${alert.type}]`;
59
+ if (alert.severity === "critical") {
60
+ logger.error(`${prefix} ${alert.message}`, alert.context);
61
+ } else {
62
+ logger.warn(`${prefix} ${alert.message}`, alert.context);
20
63
  }
21
- await this.storage.write(fullEvent);
22
- }
23
- /**
24
- * Log authentication event
25
- */
26
- async logAuth(type, actorId, result, metadata) {
27
- await this.log({
28
- type,
29
- severity: result === "failure" ? "medium" : "low",
30
- actor: {
31
- id: actorId,
32
- type: "user"
33
- },
34
- action: type.replace("auth.", ""),
35
- result,
36
- metadata
37
- });
38
- }
39
- /**
40
- * Log data access event
41
- */
42
- async logDataAccess(action, actorId, resourceType, resourceId, result, changes) {
43
- await this.log({
44
- type: `data.${action}`,
45
- severity: action === "delete" ? "high" : "medium",
46
- actor: {
47
- id: actorId,
48
- type: "user"
49
- },
50
- resource: {
51
- type: resourceType,
52
- id: resourceId
53
- },
54
- action,
55
- result,
56
- changes
57
- });
58
- }
59
- /**
60
- * Log permission change
61
- */
62
- async logPermissionChange(action, actorId, targetUserId, permission, result) {
63
- await this.log({
64
- type: `permission.${action}`,
65
- severity: "high",
66
- actor: {
67
- id: actorId,
68
- type: "user"
69
- },
70
- resource: {
71
- type: "user",
72
- id: targetUserId
73
- },
74
- action,
75
- result,
76
- metadata: {
77
- permission
78
- }
79
- });
80
- }
81
- /**
82
- * Log security event
83
- */
84
- async logSecurityEvent(type, severity, actorId, message, metadata) {
85
- await this.log({
86
- type: `security.${type}`,
87
- severity,
88
- actor: {
89
- id: actorId,
90
- type: "user"
91
- },
92
- action: type,
93
- result: "failure",
94
- message,
95
- metadata
96
- });
97
- }
98
- /**
99
- * Log GDPR event
100
- */
101
- async logGDPREvent(type, actorId, result, metadata) {
102
- await this.log({
103
- type: `gdpr.${type}`,
104
- severity: "high",
105
- actor: {
106
- id: actorId,
107
- type: "user"
108
- },
109
- action: type,
110
- result,
111
- metadata
112
- });
113
64
  }
114
- /**
115
- * Query audit logs
116
- */
117
- async query(query) {
118
- return this.storage.query(query);
119
- }
120
- /**
121
- * Count audit logs
122
- */
123
- async count(query) {
124
- return this.storage.count(query);
65
+ };
66
+ var AuditAlertHandler = class {
67
+ /** Record the alert in the audit trail with severity 'critical'. */
68
+ async handle(alert) {
69
+ try {
70
+ const { audit: audit2 } = await import("./audit-UF7PIYBU.js");
71
+ await audit2.logSecurityEvent(
72
+ "alert",
73
+ "critical",
74
+ alert.context.actorId ?? "system",
75
+ alert.message,
76
+ { alertType: alert.type, ...alert.context }
77
+ );
78
+ } catch {
79
+ }
125
80
  }
81
+ };
82
+ var WebhookAlertHandler = class {
83
+ url;
84
+ headers;
126
85
  /**
127
- * Add filter
86
+ * Create a webhook alert handler.
87
+ *
88
+ * @param url - The webhook endpoint URL
89
+ * @param headers - Additional HTTP headers (e.g. authorization)
128
90
  */
129
- addFilter(filter) {
130
- this.filters.push(filter);
91
+ constructor(url, headers = {}) {
92
+ this.url = url;
93
+ this.headers = headers;
131
94
  }
132
- /**
133
- * Remove filter
134
- */
135
- removeFilter(filter) {
136
- const index = this.filters.indexOf(filter);
137
- if (index > -1) {
138
- this.filters.splice(index, 1);
95
+ /** POST the alert payload to the configured webhook URL. */
96
+ async handle(alert) {
97
+ try {
98
+ await fetch(this.url, {
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/json",
102
+ ...this.headers
103
+ },
104
+ body: JSON.stringify(alert)
105
+ });
106
+ } catch {
107
+ const logger = getSecurityLogger();
108
+ logger.error(`[WebhookAlertHandler] Failed to POST alert to ${this.url}`);
139
109
  }
140
110
  }
141
111
  };
142
- var InMemoryAuditStorage = class {
143
- events = [];
144
- maxEvents;
145
- constructor(maxEvents = 1e4) {
146
- this.maxEvents = maxEvents;
147
- }
148
- async write(event) {
149
- this.events.push(event);
150
- if (this.events.length > this.maxEvents) {
151
- this.events.shift();
152
- }
112
+ function mapEventToRule(event) {
113
+ if (event.type === "auth.failed_login") {
114
+ return "failedLogins";
153
115
  }
154
- async query(query) {
155
- let results = [...this.events];
156
- if (query.types && query.types.length > 0) {
157
- results = results.filter((e) => query.types?.includes(e.type));
116
+ if (event.type === "role.assign") {
117
+ const newRole = event.changes?.after?.role ?? event.metadata?.role;
118
+ if (newRole === "admin") {
119
+ return "privilegeEscalation";
158
120
  }
159
- if (query.actorId) {
160
- results = results.filter((e) => e.actor.id === query.actorId);
161
- }
162
- if (query.resourceType) {
163
- results = results.filter((e) => e.resource?.type === query.resourceType);
164
- }
165
- if (query.resourceId) {
166
- results = results.filter((e) => e.resource?.id === query.resourceId);
167
- }
168
- if (query.startDate) {
169
- const startDate = query.startDate;
170
- results = results.filter((e) => new Date(e.timestamp) >= startDate);
171
- }
172
- if (query.endDate) {
173
- const endDate = query.endDate;
174
- results = results.filter((e) => new Date(e.timestamp) <= endDate);
175
- }
176
- if (query.severity && query.severity.length > 0) {
177
- results = results.filter((e) => query.severity?.includes(e.severity));
178
- }
179
- if (query.result && query.result.length > 0) {
180
- results = results.filter((e) => query.result?.includes(e.result));
181
- }
182
- results.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
183
- const offset = query.offset || 0;
184
- const limit = query.limit || 100;
185
- return results.slice(offset, offset + limit);
186
121
  }
187
- async count(query) {
188
- const results = await this.query({ ...query, limit: void 0, offset: void 0 });
189
- return results.length;
122
+ if (event.type === "data.export") {
123
+ return "massDataExport";
190
124
  }
191
- /**
192
- * Clear all events
193
- */
194
- clear() {
195
- this.events = [];
125
+ if (event.action === "account_locked") {
126
+ return "accountLockout";
196
127
  }
197
- /**
198
- * Get all events
199
- */
200
- getAll() {
201
- return [...this.events];
128
+ if (event.type === "auth.mfa_disabled") {
129
+ return "mfaDisabled";
202
130
  }
203
- };
204
- function AuditTrail(type, action, options) {
205
- return (_target, _propertyKey, descriptor) => {
206
- const originalMethod = descriptor.value;
207
- descriptor.value = async function(...args) {
208
- const actorId = this.user?.id || "system";
209
- const before = options?.captureChanges ? args[0] : void 0;
210
- let result = "success";
211
- let error;
212
- try {
213
- const returnValue = await originalMethod.apply(this, args);
214
- if (this.audit) {
215
- await this.audit.log({
216
- type,
217
- severity: options?.severity || "medium",
218
- actor: {
219
- id: actorId,
220
- type: "user"
221
- },
222
- resource: options?.resourceType ? {
223
- type: options.resourceType,
224
- id: args[0]?.id || "unknown"
225
- } : void 0,
226
- action,
227
- result,
228
- changes: options?.captureChanges ? {
229
- before,
230
- after: returnValue
231
- } : void 0
232
- });
233
- }
234
- return returnValue;
235
- } catch (err) {
236
- result = "failure";
237
- error = err;
238
- if (this.audit) {
239
- await this.audit.log({
240
- type,
241
- severity: "high",
242
- actor: {
243
- id: actorId,
244
- type: "user"
245
- },
246
- resource: options?.resourceType ? {
247
- type: options.resourceType,
248
- id: args[0]?.id || "unknown"
249
- } : void 0,
250
- action,
251
- result,
252
- message: error.message
253
- });
254
- }
255
- throw error;
256
- }
257
- };
258
- return descriptor;
259
- };
131
+ return null;
260
132
  }
261
- function createAuditMiddleware(audit2, getUser) {
262
- return async (request, next) => {
263
- const user = getUser(request);
264
- const startTime = Date.now();
265
- try {
266
- const response = await next();
267
- await audit2.log({
268
- type: "data.read",
269
- severity: "low",
270
- actor: {
271
- id: user.id,
272
- type: "user",
273
- ip: user.ip,
274
- userAgent: user.userAgent
275
- },
276
- action: request.method,
277
- result: "success",
278
- metadata: {
279
- path: request.url,
280
- duration: Date.now() - startTime,
281
- status: response.status
282
- }
283
- });
284
- return response;
285
- } catch (error) {
286
- await audit2.log({
287
- type: "data.read",
288
- severity: "medium",
289
- actor: {
290
- id: user.id,
291
- type: "user",
292
- ip: user.ip,
293
- userAgent: user.userAgent
294
- },
295
- action: request.method,
296
- result: "failure",
297
- message: error instanceof Error ? error.message : "Unknown error",
298
- metadata: {
299
- path: request.url,
300
- duration: Date.now() - startTime
301
- }
302
- });
303
- throw error;
304
- }
305
- };
306
- }
307
- var AuditReportGenerator = class {
308
- constructor(audit2) {
309
- this.audit = audit2;
133
+ function getGroupKey(event, ruleName) {
134
+ if (ruleName === "failedLogins") {
135
+ return `${ruleName}:${event.actor.id}`;
310
136
  }
137
+ if (ruleName === "massDataExport") {
138
+ return ruleName;
139
+ }
140
+ return `${ruleName}:${event.actor.id}`;
141
+ }
142
+ var SecurityAlertService = class {
143
+ config;
144
+ windows = /* @__PURE__ */ new Map();
311
145
  /**
312
- * Generate security report
146
+ * Create a new SecurityAlertService.
147
+ *
148
+ * @param config - Alerting configuration with thresholds and handlers
313
149
  */
314
- async generateSecurityReport(startDate, endDate) {
315
- const allEvents = await this.audit.query({
316
- startDate,
317
- endDate
318
- });
319
- const securityViolations = allEvents.filter((e) => e.type.startsWith("security.")).length;
320
- const failedLogins = allEvents.filter((e) => e.type === "auth.failed_login").length;
321
- const permissionChanges = allEvents.filter((e) => e.type.startsWith("permission.")).length;
322
- const dataExports = allEvents.filter((e) => e.type === "data.export").length;
323
- const criticalEvents = allEvents.filter((e) => e.severity === "critical");
324
- return {
325
- totalEvents: allEvents.length,
326
- securityViolations,
327
- failedLogins,
328
- permissionChanges,
329
- dataExports,
330
- criticalEvents
331
- };
150
+ constructor(config) {
151
+ this.config = config;
332
152
  }
333
153
  /**
334
- * Generate user activity report
154
+ * Evaluate a single audit event against all threshold rules.
155
+ * If a threshold is breached, dispatches alerts to all handlers.
156
+ *
157
+ * @param event - The audit event to evaluate
158
+ * @returns The alert that was dispatched, or null if no threshold was breached
335
159
  */
336
- async generateUserActivityReport(userId, startDate, endDate) {
337
- const events = await this.audit.query({
338
- actorId: userId,
339
- startDate,
340
- endDate
341
- });
342
- const actionsByType = events.reduce(
343
- (acc, event) => {
344
- acc[event.type] = (acc[event.type] || 0) + 1;
345
- return acc;
160
+ async evaluateEvent(event) {
161
+ const ruleName = mapEventToRule(event);
162
+ if (!ruleName) {
163
+ return null;
164
+ }
165
+ const rule = this.config.thresholds[ruleName];
166
+ if (!rule) {
167
+ return null;
168
+ }
169
+ const groupKey = getGroupKey(event, ruleName);
170
+ const now = Date.now();
171
+ const cutoff = now - rule.windowMs;
172
+ let entry = this.windows.get(groupKey);
173
+ if (!entry) {
174
+ entry = { timestamps: [], lastAlertAt: 0 };
175
+ this.windows.set(groupKey, entry);
176
+ }
177
+ entry.timestamps.push(now);
178
+ entry.timestamps = entry.timestamps.filter((ts) => ts > cutoff);
179
+ if (entry.timestamps.length < rule.maxCount) {
180
+ return null;
181
+ }
182
+ if (entry.lastAlertAt > cutoff) {
183
+ return null;
184
+ }
185
+ entry.lastAlertAt = now;
186
+ const message = rule.messageTemplate.includes("{count}") ? rule.messageTemplate.split("{count}").join(String(entry.timestamps.length)) : rule.messageTemplate;
187
+ const alert = {
188
+ type: ruleName,
189
+ severity: rule.severity,
190
+ message,
191
+ context: {
192
+ actorId: event.actor.id,
193
+ eventType: event.type,
194
+ count: entry.timestamps.length,
195
+ windowMs: rule.windowMs
346
196
  },
347
- {}
348
- );
349
- const failedActions = events.filter((e) => e.result === "failure").length;
350
- return {
351
- totalActions: events.length,
352
- actionsByType,
353
- failedActions,
354
- recentActions: events.slice(0, 10)
197
+ timestamp: new Date(now).toISOString()
355
198
  };
199
+ await this.dispatchAlert(alert);
200
+ return alert;
356
201
  }
357
202
  /**
358
- * Generate compliance report
203
+ * Clear all sliding window state. Useful for testing.
359
204
  */
360
- async generateComplianceReport(startDate, endDate) {
361
- const events = await this.audit.query({
362
- startDate,
363
- endDate
364
- });
365
- const dataAccesses = events.filter((e) => e.type === "data.read").length;
366
- const dataModifications = events.filter(
367
- (e) => e.type === "data.update" || e.type === "data.create"
368
- ).length;
369
- const dataDeletions = events.filter((e) => e.type === "data.delete").length;
370
- const gdprRequests = events.filter((e) => e.type.startsWith("gdpr.")).length;
371
- const auditTrailComplete = this.checkAuditTrailContinuity(events);
372
- return {
373
- dataAccesses,
374
- dataModifications,
375
- dataDeletions,
376
- gdprRequests,
377
- auditTrailComplete
378
- };
205
+ reset() {
206
+ this.windows.clear();
379
207
  }
380
208
  /**
381
- * Check audit trail continuity
209
+ * Dispatch an alert to all configured handlers.
210
+ * Errors in individual handlers are logged but do not prevent
211
+ * other handlers from receiving the alert.
382
212
  */
383
- checkAuditTrailContinuity(events) {
384
- if (events.length === 0) return true;
385
- const sorted = events.sort(
386
- (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
387
- );
388
- return sorted.length > 0;
213
+ async dispatchAlert(alert) {
214
+ const results = this.config.handlers.map(async (handler) => {
215
+ try {
216
+ await handler.handle(alert);
217
+ } catch {
218
+ const logger = getSecurityLogger();
219
+ logger.error(`[SecurityAlertService] Handler failed for alert type=${alert.type}`);
220
+ }
221
+ });
222
+ await Promise.all(results);
389
223
  }
390
224
  };
391
- var audit = new AuditSystem(new InMemoryAuditStorage());
392
225
 
393
226
  // src/auth.ts
394
227
  import { createHmac, timingSafeEqual } from "crypto";
@@ -1506,17 +1339,6 @@ var TokenGenerator = {
1506
1339
 
1507
1340
  // src/gdpr.ts
1508
1341
  import { createHash, createHmac as createHmac2 } from "crypto";
1509
-
1510
- // src/logger.ts
1511
- var securityLogger = console;
1512
- function configureSecurityLogger(logger) {
1513
- securityLogger = logger;
1514
- }
1515
- function getSecurityLogger() {
1516
- return securityLogger;
1517
- }
1518
-
1519
- // src/gdpr.ts
1520
1342
  var ConsentManager = class {
1521
1343
  storage;
1522
1344
  consentVersion = "1.0.0";
@@ -2400,6 +2222,7 @@ function setRateLimitHeaders(response, limit, remaining, reset) {
2400
2222
  response.headers.set("X-RateLimit-Reset", reset.toString());
2401
2223
  }
2402
2224
  export {
2225
+ AuditAlertHandler,
2403
2226
  AuditReportGenerator,
2404
2227
  AuditSystem,
2405
2228
  AuditTrail,
@@ -2409,6 +2232,7 @@ export {
2409
2232
  CommonRoles,
2410
2233
  ConsentManager,
2411
2234
  CookieConsentManager,
2235
+ DEFAULT_THRESHOLDS,
2412
2236
  DataAnonymization,
2413
2237
  DataBreachManager,
2414
2238
  DataDeletionSystem,
@@ -2421,6 +2245,7 @@ export {
2421
2245
  InMemoryBreachStorage,
2422
2246
  InMemoryGDPRStorage,
2423
2247
  KeyRotationManager,
2248
+ LogAlertHandler,
2424
2249
  OAuthClient,
2425
2250
  OAuthProviders,
2426
2251
  PasswordHasher,
@@ -2430,10 +2255,12 @@ export {
2430
2255
  PrivacyPolicyManager,
2431
2256
  RequirePermission,
2432
2257
  RequireRole,
2258
+ SecurityAlertService,
2433
2259
  SecurityHeaders,
2434
2260
  SecurityPresets,
2435
2261
  TokenGenerator,
2436
2262
  TwoFactorAuth,
2263
+ WebhookAlertHandler,
2437
2264
  audit,
2438
2265
  authorization,
2439
2266
  canAccessResource,
@@ -2450,6 +2277,8 @@ export {
2450
2277
  encryption,
2451
2278
  permissionCache,
2452
2279
  privacyPolicyManager,
2453
- setRateLimitHeaders
2280
+ setRateLimitHeaders,
2281
+ signAuditEntry,
2282
+ verifyAuditEntry
2454
2283
  };
2455
2284
  //# sourceMappingURL=index.js.map