@secfn/core 0.0.1

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.
Files changed (64) hide show
  1. package/README.md +334 -0
  2. package/dist/access-control/rbac.d.ts +48 -0
  3. package/dist/access-control/rbac.d.ts.map +1 -0
  4. package/dist/access-control/rbac.js +203 -0
  5. package/dist/access-control/rbac.js.map +1 -0
  6. package/dist/core/secfn.d.ts +28 -0
  7. package/dist/core/secfn.d.ts.map +1 -0
  8. package/dist/core/secfn.js +73 -0
  9. package/dist/core/secfn.js.map +1 -0
  10. package/dist/index.d.ts +12 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +12 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/monitoring/security-monitor.d.ts +46 -0
  15. package/dist/monitoring/security-monitor.d.ts.map +1 -0
  16. package/dist/monitoring/security-monitor.js +172 -0
  17. package/dist/monitoring/security-monitor.js.map +1 -0
  18. package/dist/rate-limiting/rate-limiter.d.ts +31 -0
  19. package/dist/rate-limiting/rate-limiter.d.ts.map +1 -0
  20. package/dist/rate-limiting/rate-limiter.js +140 -0
  21. package/dist/rate-limiting/rate-limiter.js.map +1 -0
  22. package/dist/scanning/patterns.d.ts +3 -0
  23. package/dist/scanning/patterns.d.ts.map +1 -0
  24. package/dist/scanning/patterns.js +139 -0
  25. package/dist/scanning/patterns.js.map +1 -0
  26. package/dist/scanning/scanner.d.ts +36 -0
  27. package/dist/scanning/scanner.d.ts.map +1 -0
  28. package/dist/scanning/scanner.js +152 -0
  29. package/dist/scanning/scanner.js.map +1 -0
  30. package/dist/secrets/encryption.d.ts +17 -0
  31. package/dist/secrets/encryption.d.ts.map +1 -0
  32. package/dist/secrets/encryption.js +47 -0
  33. package/dist/secrets/encryption.js.map +1 -0
  34. package/dist/secrets/vault.d.ts +43 -0
  35. package/dist/secrets/vault.d.ts.map +1 -0
  36. package/dist/secrets/vault.js +126 -0
  37. package/dist/secrets/vault.js.map +1 -0
  38. package/dist/storage/indexeddb-storage.d.ts +37 -0
  39. package/dist/storage/indexeddb-storage.d.ts.map +1 -0
  40. package/dist/storage/indexeddb-storage.js +284 -0
  41. package/dist/storage/indexeddb-storage.js.map +1 -0
  42. package/dist/types/index.d.ts +189 -0
  43. package/dist/types/index.d.ts.map +1 -0
  44. package/dist/types/index.js +2 -0
  45. package/dist/types/index.js.map +1 -0
  46. package/dist/utils/helpers.d.ts +8 -0
  47. package/dist/utils/helpers.d.ts.map +1 -0
  48. package/dist/utils/helpers.js +73 -0
  49. package/dist/utils/helpers.js.map +1 -0
  50. package/examples/basic-usage.ts +143 -0
  51. package/package.json +68 -0
  52. package/src/access-control/rbac.ts +271 -0
  53. package/src/core/secfn.ts +90 -0
  54. package/src/index.ts +27 -0
  55. package/src/monitoring/security-monitor.ts +245 -0
  56. package/src/rate-limiting/rate-limiter.ts +181 -0
  57. package/src/scanning/patterns.ts +140 -0
  58. package/src/scanning/scanner.ts +199 -0
  59. package/src/secrets/encryption.ts +74 -0
  60. package/src/secrets/vault.ts +187 -0
  61. package/src/storage/indexeddb-storage.ts +355 -0
  62. package/src/types/index.ts +213 -0
  63. package/src/utils/helpers.ts +84 -0
  64. package/tsconfig.json +26 -0
@@ -0,0 +1,355 @@
1
+ import type {
2
+ Secret,
3
+ SecretAccess,
4
+ Role,
5
+ UserRole,
6
+ SecurityEvent,
7
+ RateLimitEntry,
8
+ SecretScanResult,
9
+ EventQuery,
10
+ SecurityMetrics,
11
+ TimeRange,
12
+ } from '../types/index.js';
13
+
14
+ const DB_VERSION = 1;
15
+
16
+ const STORES = {
17
+ secrets: 'secfn_secrets',
18
+ secretAccess: 'secfn_secret_access',
19
+ roles: 'secfn_roles',
20
+ userRoles: 'secfn_user_roles',
21
+ securityEvents: 'secfn_security_events',
22
+ rateLimits: 'secfn_rate_limits',
23
+ scanResults: 'secfn_scan_results',
24
+ config: 'secfn_config',
25
+ } as const;
26
+
27
+ export class IndexedDBStorage {
28
+ private db: IDBDatabase | null = null;
29
+ private dbName: string;
30
+
31
+ constructor(dbName = 'secfn') {
32
+ this.dbName = dbName;
33
+ }
34
+
35
+ async initialize(): Promise<void> {
36
+ if (this.db) return;
37
+
38
+ return new Promise((resolve, reject) => {
39
+ const request = indexedDB.open(this.dbName, DB_VERSION);
40
+
41
+ request.onerror = () => reject(request.error);
42
+ request.onsuccess = () => {
43
+ this.db = request.result;
44
+ resolve();
45
+ };
46
+
47
+ request.onupgradeneeded = (event) => {
48
+ const db = (event.target as IDBOpenDBRequest).result;
49
+
50
+ if (!db.objectStoreNames.contains(STORES.secrets)) {
51
+ const secretsStore = db.createObjectStore(STORES.secrets, { keyPath: 'id' });
52
+ secretsStore.createIndex('key', 'key', { unique: true });
53
+ secretsStore.createIndex('environment', 'environment');
54
+ secretsStore.createIndex('expiresAt', 'expiresAt');
55
+ secretsStore.createIndex('tags', 'tags', { multiEntry: true });
56
+ }
57
+
58
+ if (!db.objectStoreNames.contains(STORES.secretAccess)) {
59
+ const accessStore = db.createObjectStore(STORES.secretAccess, { keyPath: 'id' });
60
+ accessStore.createIndex('secretId', 'secretId');
61
+ accessStore.createIndex('userId', 'userId');
62
+ accessStore.createIndex('timestamp', 'timestamp');
63
+ }
64
+
65
+ if (!db.objectStoreNames.contains(STORES.roles)) {
66
+ db.createObjectStore(STORES.roles, { keyPath: 'id' });
67
+ }
68
+
69
+ if (!db.objectStoreNames.contains(STORES.userRoles)) {
70
+ const userRolesStore = db.createObjectStore(STORES.userRoles, { keyPath: ['userId', 'roleId'] });
71
+ userRolesStore.createIndex('userId', 'userId');
72
+ userRolesStore.createIndex('roleId', 'roleId');
73
+ }
74
+
75
+ if (!db.objectStoreNames.contains(STORES.securityEvents)) {
76
+ const eventsStore = db.createObjectStore(STORES.securityEvents, { keyPath: 'id' });
77
+ eventsStore.createIndex('timestamp', 'timestamp');
78
+ eventsStore.createIndex('type', 'type');
79
+ eventsStore.createIndex('severity', 'severity');
80
+ eventsStore.createIndex('userId', 'userId');
81
+ eventsStore.createIndex('ip', 'ip');
82
+ }
83
+
84
+ if (!db.objectStoreNames.contains(STORES.rateLimits)) {
85
+ db.createObjectStore(STORES.rateLimits, { keyPath: 'key' });
86
+ }
87
+
88
+ if (!db.objectStoreNames.contains(STORES.scanResults)) {
89
+ const scanStore = db.createObjectStore(STORES.scanResults, { keyPath: 'id' });
90
+ scanStore.createIndex('timestamp', 'timestamp');
91
+ scanStore.createIndex('severity', 'severity');
92
+ scanStore.createIndex('file', 'file');
93
+ }
94
+
95
+ if (!db.objectStoreNames.contains(STORES.config)) {
96
+ db.createObjectStore(STORES.config, { keyPath: 'key' });
97
+ }
98
+ };
99
+ });
100
+ }
101
+
102
+ private getStore(storeName: string, mode: IDBTransactionMode = 'readonly'): IDBObjectStore {
103
+ if (!this.db) throw new Error('Database not initialized');
104
+ const tx = this.db.transaction(storeName, mode);
105
+ return tx.objectStore(storeName);
106
+ }
107
+
108
+ async saveSecret(secret: Secret): Promise<void> {
109
+ const store = this.getStore(STORES.secrets, 'readwrite');
110
+ await this.promisifyRequest(store.put(secret));
111
+ }
112
+
113
+ async getSecret(id: string): Promise<Secret | null> {
114
+ const store = this.getStore(STORES.secrets);
115
+ const result = await this.promisifyRequest<Secret>(store.get(id));
116
+ return result || null;
117
+ }
118
+
119
+ async getSecretByKey(key: string): Promise<Secret | null> {
120
+ const store = this.getStore(STORES.secrets);
121
+ const index = store.index('key');
122
+ const result = await this.promisifyRequest<Secret>(index.get(key));
123
+ return result || null;
124
+ }
125
+
126
+ async listSecrets(filter?: { environment?: string; tags?: string[] }): Promise<Secret[]> {
127
+ const store = this.getStore(STORES.secrets);
128
+ const secrets = await this.promisifyRequest<Secret[]>(store.getAll());
129
+
130
+ if (!filter) return secrets;
131
+
132
+ return secrets.filter(secret => {
133
+ if (filter.environment && secret.environment !== filter.environment) {
134
+ return false;
135
+ }
136
+ if (filter.tags && !filter.tags.some(tag => secret.tags.includes(tag))) {
137
+ return false;
138
+ }
139
+ return true;
140
+ });
141
+ }
142
+
143
+ async deleteSecret(id: string): Promise<void> {
144
+ const store = this.getStore(STORES.secrets, 'readwrite');
145
+ await this.promisifyRequest(store.delete(id));
146
+ }
147
+
148
+ async logSecretAccess(access: SecretAccess): Promise<void> {
149
+ const store = this.getStore(STORES.secretAccess, 'readwrite');
150
+ await this.promisifyRequest(store.put(access));
151
+ }
152
+
153
+ async getSecretAccessLog(secretId: string, limit = 100): Promise<SecretAccess[]> {
154
+ const store = this.getStore(STORES.secretAccess);
155
+ const index = store.index('secretId');
156
+ const allAccess = await this.promisifyRequest<SecretAccess[]>(index.getAll(secretId));
157
+ return allAccess.slice(-limit);
158
+ }
159
+
160
+ async saveRole(role: Role): Promise<void> {
161
+ const store = this.getStore(STORES.roles, 'readwrite');
162
+ await this.promisifyRequest(store.put(role));
163
+ }
164
+
165
+ async getRole(id: string): Promise<Role | null> {
166
+ const store = this.getStore(STORES.roles);
167
+ const result = await this.promisifyRequest<Role>(store.get(id));
168
+ return result || null;
169
+ }
170
+
171
+ async listRoles(): Promise<Role[]> {
172
+ const store = this.getStore(STORES.roles);
173
+ return await this.promisifyRequest<Role[]>(store.getAll());
174
+ }
175
+
176
+ async deleteRole(id: string): Promise<void> {
177
+ const store = this.getStore(STORES.roles, 'readwrite');
178
+ await this.promisifyRequest(store.delete(id));
179
+ }
180
+
181
+ async assignUserRole(userRole: UserRole): Promise<void> {
182
+ const store = this.getStore(STORES.userRoles, 'readwrite');
183
+ await this.promisifyRequest(store.put(userRole));
184
+ }
185
+
186
+ async getUserRoles(userId: string): Promise<UserRole[]> {
187
+ const store = this.getStore(STORES.userRoles);
188
+ const index = store.index('userId');
189
+ return await this.promisifyRequest<UserRole[]>(index.getAll(userId));
190
+ }
191
+
192
+ async removeUserRole(userId: string, roleId: string): Promise<void> {
193
+ const store = this.getStore(STORES.userRoles, 'readwrite');
194
+ await this.promisifyRequest(store.delete([userId, roleId]));
195
+ }
196
+
197
+ async logSecurityEvent(event: SecurityEvent): Promise<void> {
198
+ const store = this.getStore(STORES.securityEvents, 'readwrite');
199
+ await this.promisifyRequest(store.put(event));
200
+ }
201
+
202
+ async queryEvents(query: EventQuery): Promise<SecurityEvent[]> {
203
+ const store = this.getStore(STORES.securityEvents);
204
+ let events = await this.promisifyRequest<SecurityEvent[]>(store.getAll());
205
+
206
+ if (query.type) {
207
+ const types = Array.isArray(query.type) ? query.type : [query.type];
208
+ events = events.filter(e => types.includes(e.type));
209
+ }
210
+
211
+ if (query.severity) {
212
+ const severities = Array.isArray(query.severity) ? query.severity : [query.severity];
213
+ events = events.filter(e => severities.includes(e.severity));
214
+ }
215
+
216
+ if (query.userId) {
217
+ events = events.filter(e => e.userId === query.userId);
218
+ }
219
+
220
+ if (query.ip) {
221
+ events = events.filter(e => e.ip === query.ip);
222
+ }
223
+
224
+ if (query.startDate) {
225
+ events = events.filter(e => e.timestamp >= query.startDate!);
226
+ }
227
+
228
+ if (query.endDate) {
229
+ events = events.filter(e => e.timestamp <= query.endDate!);
230
+ }
231
+
232
+ if (query.resolved !== undefined) {
233
+ events = events.filter(e => e.resolved === query.resolved);
234
+ }
235
+
236
+ events.sort((a, b) => b.timestamp - a.timestamp);
237
+
238
+ if (query.offset) {
239
+ events = events.slice(query.offset);
240
+ }
241
+
242
+ if (query.limit) {
243
+ events = events.slice(0, query.limit);
244
+ }
245
+
246
+ return events;
247
+ }
248
+
249
+ async getMetrics(range: TimeRange): Promise<SecurityMetrics> {
250
+ const events = await this.queryEvents({
251
+ startDate: range.start,
252
+ endDate: range.end,
253
+ });
254
+
255
+ const eventsByType: Record<string, number> = {};
256
+ const eventsBySeverity: Record<string, number> = {};
257
+ const ipCounts: Record<string, number> = {};
258
+ const userCounts: Record<string, number> = {};
259
+
260
+ for (const event of events) {
261
+ eventsByType[event.type] = (eventsByType[event.type] || 0) + 1;
262
+ eventsBySeverity[event.severity] = (eventsBySeverity[event.severity] || 0) + 1;
263
+ ipCounts[event.ip] = (ipCounts[event.ip] || 0) + 1;
264
+ if (event.userId) {
265
+ userCounts[event.userId] = (userCounts[event.userId] || 0) + 1;
266
+ }
267
+ }
268
+
269
+ const topIPs = Object.entries(ipCounts)
270
+ .map(([ip, count]) => ({ ip, count }))
271
+ .sort((a, b) => b.count - a.count)
272
+ .slice(0, 10);
273
+
274
+ const topUsers = Object.entries(userCounts)
275
+ .map(([userId, count]) => ({ userId, count }))
276
+ .sort((a, b) => b.count - a.count)
277
+ .slice(0, 10);
278
+
279
+ return {
280
+ timeRange: range,
281
+ totalEvents: events.length,
282
+ eventsByType,
283
+ eventsBySeverity,
284
+ topIPs,
285
+ topUsers,
286
+ };
287
+ }
288
+
289
+ async getRateLimitEntry(key: string): Promise<RateLimitEntry | null> {
290
+ const store = this.getStore(STORES.rateLimits);
291
+ const result = await this.promisifyRequest<RateLimitEntry>(store.get(key));
292
+ return result || null;
293
+ }
294
+
295
+ async setRateLimitEntry(entry: RateLimitEntry): Promise<void> {
296
+ const store = this.getStore(STORES.rateLimits, 'readwrite');
297
+ await this.promisifyRequest(store.put(entry));
298
+ }
299
+
300
+ async deleteRateLimitEntry(key: string): Promise<void> {
301
+ const store = this.getStore(STORES.rateLimits, 'readwrite');
302
+ await this.promisifyRequest(store.delete(key));
303
+ }
304
+
305
+ async saveScanResult(result: SecretScanResult): Promise<void> {
306
+ const store = this.getStore(STORES.scanResults, 'readwrite');
307
+ await this.promisifyRequest(store.put(result));
308
+ }
309
+
310
+ async getScanHistory(limit = 100): Promise<SecretScanResult[]> {
311
+ const store = this.getStore(STORES.scanResults);
312
+ const results = await this.promisifyRequest<SecretScanResult[]>(store.getAll());
313
+ return results.slice(-limit);
314
+ }
315
+
316
+ async cleanup(retentionMs: number): Promise<void> {
317
+ const cutoffTime = Date.now() - retentionMs;
318
+
319
+ const eventsStore = this.getStore(STORES.securityEvents, 'readwrite');
320
+ const events = await this.promisifyRequest<SecurityEvent[]>(eventsStore.getAll());
321
+ for (const event of events) {
322
+ if (event.timestamp < cutoffTime) {
323
+ await this.promisifyRequest(eventsStore.delete(event.id));
324
+ }
325
+ }
326
+
327
+ const scanStore = this.getStore(STORES.scanResults, 'readwrite');
328
+ const scans = await this.promisifyRequest<SecretScanResult[]>(scanStore.getAll());
329
+ for (const scan of scans) {
330
+ if (scan.timestamp < cutoffTime) {
331
+ await this.promisifyRequest(scanStore.delete(scan.id));
332
+ }
333
+ }
334
+ }
335
+
336
+ async destroy(): Promise<void> {
337
+ if (this.db) {
338
+ this.db.close();
339
+ this.db = null;
340
+ }
341
+
342
+ return new Promise((resolve, reject) => {
343
+ const request = indexedDB.deleteDatabase(this.dbName);
344
+ request.onsuccess = () => resolve();
345
+ request.onerror = () => reject(request.error);
346
+ });
347
+ }
348
+
349
+ private promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {
350
+ return new Promise((resolve, reject) => {
351
+ request.onsuccess = () => resolve(request.result);
352
+ request.onerror = () => reject(request.error);
353
+ });
354
+ }
355
+ }
@@ -0,0 +1,213 @@
1
+ export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
2
+ export type Environment = 'development' | 'staging' | 'production';
3
+
4
+ export interface StorageConfig {
5
+ type: 'indexeddb' | 'file' | 'db';
6
+ dbName?: string;
7
+ path?: string;
8
+ adapter?: any;
9
+ retentionDays?: number;
10
+ }
11
+
12
+ export interface SecretPattern {
13
+ name: string;
14
+ pattern: RegExp;
15
+ entropy?: number;
16
+ description: string;
17
+ severity: Severity;
18
+ }
19
+
20
+ export interface SecretScanResult {
21
+ id: string;
22
+ timestamp: number;
23
+ file: string;
24
+ line: number;
25
+ column: number;
26
+ pattern: string;
27
+ match: string;
28
+ redactedMatch: string;
29
+ severity: Severity;
30
+ entropy: number;
31
+ context: string;
32
+ resolved: boolean;
33
+ falsePositive: boolean;
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+
37
+ export interface Secret {
38
+ id: string;
39
+ key: string;
40
+ value: string;
41
+ encrypted: boolean;
42
+ version: number;
43
+ tags: string[];
44
+ environment?: Environment;
45
+ expiresAt?: number;
46
+ rotateEvery?: number;
47
+ lastRotated?: number;
48
+ lastAccessed?: number;
49
+ accessCount: number;
50
+ createdBy: string;
51
+ createdAt: number;
52
+ updatedAt: number;
53
+ }
54
+
55
+ export interface SecretAccess {
56
+ id: string;
57
+ secretId: string;
58
+ userId: string;
59
+ action: 'read' | 'write' | 'delete' | 'rotate';
60
+ timestamp: number;
61
+ ip?: string;
62
+ userAgent?: string;
63
+ }
64
+
65
+ export interface Role {
66
+ id: string;
67
+ name: string;
68
+ description: string;
69
+ permissions: string[];
70
+ inherits?: string[];
71
+ metadata?: Record<string, unknown>;
72
+ createdAt: number;
73
+ updatedAt: number;
74
+ }
75
+
76
+ export interface Permission {
77
+ id: string;
78
+ resource: string;
79
+ action: string;
80
+ conditions?: PolicyCondition[];
81
+ }
82
+
83
+ export interface UserRole {
84
+ userId: string;
85
+ roleId: string;
86
+ resourceIds?: string[];
87
+ expiresAt?: number;
88
+ assignedAt: number;
89
+ }
90
+
91
+ export interface PolicyCondition {
92
+ field: string;
93
+ operator: 'eq' | 'ne' | 'in' | 'nin' | 'gt' | 'lt' | 'contains';
94
+ value: unknown;
95
+ }
96
+
97
+ export interface AccessRequest {
98
+ userId: string;
99
+ action: string;
100
+ resourceId?: string;
101
+ context?: Record<string, unknown>;
102
+ }
103
+
104
+ export interface RateLimitRule {
105
+ id?: string;
106
+ name?: string;
107
+ type?: 'global' | 'perUser' | 'perIP' | 'perEndpoint';
108
+ requests: number;
109
+ window: number;
110
+ blockDuration?: number;
111
+ endpoints?: string[];
112
+ }
113
+
114
+ export interface RateLimitEntry {
115
+ key: string;
116
+ count: number;
117
+ resetAt: number;
118
+ blocked: boolean;
119
+ blockedUntil?: number;
120
+ }
121
+
122
+ export interface RateLimitResult {
123
+ allowed: boolean;
124
+ remaining: number;
125
+ resetAt: number;
126
+ retryAfter?: number;
127
+ limit?: number;
128
+ }
129
+
130
+ export interface RateLimitViolation {
131
+ id: string;
132
+ timestamp: number;
133
+ key: string;
134
+ ip: string;
135
+ userId?: string;
136
+ endpoint: string;
137
+ requestCount: number;
138
+ limit: number;
139
+ window: number;
140
+ blocked: boolean;
141
+ }
142
+
143
+ export type SecurityEventType =
144
+ | 'auth_failure'
145
+ | 'rate_limit_exceeded'
146
+ | 'unauthorized_access'
147
+ | 'secret_accessed'
148
+ | 'permission_denied'
149
+ | 'suspicious_activity'
150
+ | 'secret_exposed'
151
+ | 'policy_violation';
152
+
153
+ export interface SecurityEvent {
154
+ id: string;
155
+ timestamp: number;
156
+ type: SecurityEventType;
157
+ severity: Severity;
158
+ userId?: string;
159
+ ip: string;
160
+ userAgent?: string;
161
+ resource?: string;
162
+ action?: string;
163
+ metadata: Record<string, unknown>;
164
+ resolved: boolean;
165
+ resolvedAt?: number;
166
+ resolvedBy?: string;
167
+ notes?: string;
168
+ }
169
+
170
+ export interface SecurityMetrics {
171
+ timeRange: { start: number; end: number };
172
+ totalEvents: number;
173
+ eventsByType: Record<string, number>;
174
+ eventsBySeverity: Record<string, number>;
175
+ topIPs: Array<{ ip: string; count: number }>;
176
+ topUsers: Array<{ userId: string; count: number }>;
177
+ anomalies?: SecurityAnomaly[];
178
+ }
179
+
180
+ export type AnomalyType =
181
+ | 'unusual_access_pattern'
182
+ | 'geographic_anomaly'
183
+ | 'spike_in_failures'
184
+ | 'new_endpoint_access'
185
+ | 'unusual_time';
186
+
187
+ export interface SecurityAnomaly {
188
+ id: string;
189
+ timestamp: number;
190
+ type: AnomalyType;
191
+ score: number;
192
+ description: string;
193
+ relatedEvents: string[];
194
+ userId?: string;
195
+ ip?: string;
196
+ }
197
+
198
+ export interface EventQuery {
199
+ type?: SecurityEventType | SecurityEventType[];
200
+ severity?: Severity | Severity[];
201
+ userId?: string;
202
+ ip?: string;
203
+ startDate?: number;
204
+ endDate?: number;
205
+ resolved?: boolean;
206
+ limit?: number;
207
+ offset?: number;
208
+ }
209
+
210
+ export interface TimeRange {
211
+ start: number;
212
+ end: number;
213
+ }
@@ -0,0 +1,84 @@
1
+ import { randomBytes } from 'crypto';
2
+
3
+ export function generateId(prefix?: string): string {
4
+ const random = randomBytes(16).toString('hex');
5
+ return prefix ? `${prefix}_${random}` : random;
6
+ }
7
+
8
+ export function calculateEntropy(str: string): number {
9
+ if (!str) return 0;
10
+
11
+ const len = str.length;
12
+ const frequencies: Record<string, number> = {};
13
+
14
+ for (let i = 0; i < len; i++) {
15
+ const char = str[i];
16
+ frequencies[char] = (frequencies[char] || 0) + 1;
17
+ }
18
+
19
+ let entropy = 0;
20
+ for (const char in frequencies) {
21
+ const probability = frequencies[char] / len;
22
+ entropy -= probability * Math.log2(probability);
23
+ }
24
+
25
+ return entropy;
26
+ }
27
+
28
+ export function redactSecret(secret: string, visibleChars = 4): string {
29
+ if (secret.length <= visibleChars) {
30
+ return '*'.repeat(secret.length);
31
+ }
32
+
33
+ const start = secret.slice(0, visibleChars);
34
+ const rest = '*'.repeat(secret.length - visibleChars);
35
+ return start + rest;
36
+ }
37
+
38
+ export function isExpired(timestamp?: number): boolean {
39
+ if (!timestamp) return false;
40
+ return Date.now() > timestamp;
41
+ }
42
+
43
+ export function delay(ms: number): Promise<void> {
44
+ return new Promise(resolve => setTimeout(resolve, ms));
45
+ }
46
+
47
+ export function sanitizeForLog(obj: Record<string, unknown>): Record<string, unknown> {
48
+ const sanitized: Record<string, unknown> = {};
49
+ const sensitiveKeys = ['password', 'secret', 'token', 'key', 'apiKey', 'api_key'];
50
+
51
+ for (const [key, value] of Object.entries(obj)) {
52
+ const lowerKey = key.toLowerCase();
53
+ if (sensitiveKeys.some(sk => lowerKey.includes(sk))) {
54
+ sanitized[key] = '[REDACTED]';
55
+ } else if (typeof value === 'object' && value !== null) {
56
+ sanitized[key] = sanitizeForLog(value as Record<string, unknown>);
57
+ } else {
58
+ sanitized[key] = value;
59
+ }
60
+ }
61
+
62
+ return sanitized;
63
+ }
64
+
65
+ export function parseTimeWindow(window: string | number): number {
66
+ if (typeof window === 'number') return window;
67
+
68
+ const match = window.match(/^(\d+)(ms|s|m|h|d)$/);
69
+ if (!match) {
70
+ throw new Error(`Invalid time window format: ${window}`);
71
+ }
72
+
73
+ const [, value, unit] = match;
74
+ const num = parseInt(value, 10);
75
+
76
+ switch (unit) {
77
+ case 'ms': return num;
78
+ case 's': return num * 1000;
79
+ case 'm': return num * 60 * 1000;
80
+ case 'h': return num * 60 * 60 * 1000;
81
+ case 'd': return num * 24 * 60 * 60 * 1000;
82
+ default: throw new Error(`Unknown time unit: ${unit}`);
83
+ }
84
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022", "DOM"],
6
+ "moduleResolution": "bundler",
7
+ "resolveJsonModule": true,
8
+ "allowJs": false,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noImplicitReturns": true,
18
+ "noFallthroughCasesInSwitch": true,
19
+ "skipLibCheck": true,
20
+ "esModuleInterop": true,
21
+ "allowSyntheticDefaultImports": true,
22
+ "forceConsistentCasingInFileNames": true
23
+ },
24
+ "include": ["src/**/*"],
25
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
26
+ }