@secfn/core 0.0.1 → 0.0.2

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 (100) hide show
  1. package/README.md +30 -322
  2. package/dist/encryption.d.ts +8 -0
  3. package/dist/encryption.d.ts.map +1 -0
  4. package/dist/encryption.js +76 -0
  5. package/dist/encryption.js.map +1 -0
  6. package/dist/errors.d.ts +24 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +47 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/id.d.ts +3 -0
  11. package/dist/id.d.ts.map +1 -0
  12. package/dist/id.js +11 -0
  13. package/dist/id.js.map +1 -0
  14. package/dist/index.d.ts +9 -11
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +9 -11
  17. package/dist/index.js.map +1 -1
  18. package/dist/scanner/reporters.d.ts +14 -0
  19. package/dist/scanner/reporters.d.ts.map +1 -0
  20. package/dist/scanner/reporters.js +85 -0
  21. package/dist/scanner/reporters.js.map +1 -0
  22. package/dist/scanner/rules.d.ts +24 -0
  23. package/dist/scanner/rules.d.ts.map +1 -0
  24. package/dist/scanner/rules.js +141 -0
  25. package/dist/scanner/rules.js.map +1 -0
  26. package/dist/scanner/scanner.d.ts +25 -0
  27. package/dist/scanner/scanner.d.ts.map +1 -0
  28. package/dist/scanner/scanner.js +73 -0
  29. package/dist/scanner/scanner.js.map +1 -0
  30. package/dist/scanner/utils.d.ts +15 -0
  31. package/dist/scanner/utils.d.ts.map +1 -0
  32. package/dist/scanner/utils.js +40 -0
  33. package/dist/scanner/utils.js.map +1 -0
  34. package/dist/schema.d.ts +9 -0
  35. package/dist/schema.d.ts.map +1 -0
  36. package/dist/schema.js +246 -0
  37. package/dist/schema.js.map +1 -0
  38. package/dist/types.d.ts +258 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +2 -0
  41. package/dist/types.js.map +1 -0
  42. package/package.json +29 -26
  43. package/dist/access-control/rbac.d.ts +0 -48
  44. package/dist/access-control/rbac.d.ts.map +0 -1
  45. package/dist/access-control/rbac.js +0 -203
  46. package/dist/access-control/rbac.js.map +0 -1
  47. package/dist/core/secfn.d.ts +0 -28
  48. package/dist/core/secfn.d.ts.map +0 -1
  49. package/dist/core/secfn.js +0 -73
  50. package/dist/core/secfn.js.map +0 -1
  51. package/dist/monitoring/security-monitor.d.ts +0 -46
  52. package/dist/monitoring/security-monitor.d.ts.map +0 -1
  53. package/dist/monitoring/security-monitor.js +0 -172
  54. package/dist/monitoring/security-monitor.js.map +0 -1
  55. package/dist/rate-limiting/rate-limiter.d.ts +0 -31
  56. package/dist/rate-limiting/rate-limiter.d.ts.map +0 -1
  57. package/dist/rate-limiting/rate-limiter.js +0 -140
  58. package/dist/rate-limiting/rate-limiter.js.map +0 -1
  59. package/dist/scanning/patterns.d.ts +0 -3
  60. package/dist/scanning/patterns.d.ts.map +0 -1
  61. package/dist/scanning/patterns.js +0 -139
  62. package/dist/scanning/patterns.js.map +0 -1
  63. package/dist/scanning/scanner.d.ts +0 -36
  64. package/dist/scanning/scanner.d.ts.map +0 -1
  65. package/dist/scanning/scanner.js +0 -152
  66. package/dist/scanning/scanner.js.map +0 -1
  67. package/dist/secrets/encryption.d.ts +0 -17
  68. package/dist/secrets/encryption.d.ts.map +0 -1
  69. package/dist/secrets/encryption.js +0 -47
  70. package/dist/secrets/encryption.js.map +0 -1
  71. package/dist/secrets/vault.d.ts +0 -43
  72. package/dist/secrets/vault.d.ts.map +0 -1
  73. package/dist/secrets/vault.js +0 -126
  74. package/dist/secrets/vault.js.map +0 -1
  75. package/dist/storage/indexeddb-storage.d.ts +0 -37
  76. package/dist/storage/indexeddb-storage.d.ts.map +0 -1
  77. package/dist/storage/indexeddb-storage.js +0 -284
  78. package/dist/storage/indexeddb-storage.js.map +0 -1
  79. package/dist/types/index.d.ts +0 -189
  80. package/dist/types/index.d.ts.map +0 -1
  81. package/dist/types/index.js +0 -2
  82. package/dist/types/index.js.map +0 -1
  83. package/dist/utils/helpers.d.ts +0 -8
  84. package/dist/utils/helpers.d.ts.map +0 -1
  85. package/dist/utils/helpers.js +0 -73
  86. package/dist/utils/helpers.js.map +0 -1
  87. package/examples/basic-usage.ts +0 -143
  88. package/src/access-control/rbac.ts +0 -271
  89. package/src/core/secfn.ts +0 -90
  90. package/src/index.ts +0 -27
  91. package/src/monitoring/security-monitor.ts +0 -245
  92. package/src/rate-limiting/rate-limiter.ts +0 -181
  93. package/src/scanning/patterns.ts +0 -140
  94. package/src/scanning/scanner.ts +0 -199
  95. package/src/secrets/encryption.ts +0 -74
  96. package/src/secrets/vault.ts +0 -187
  97. package/src/storage/indexeddb-storage.ts +0 -355
  98. package/src/types/index.ts +0 -213
  99. package/src/utils/helpers.ts +0 -84
  100. package/tsconfig.json +0 -26
@@ -1,187 +0,0 @@
1
- import type { Secret, SecretAccess, Environment } from '../types/index.js';
2
- import type { IndexedDBStorage } from '../storage/indexeddb-storage.js';
3
- import { SecretEncryption } from './encryption.js';
4
- import { generateId, isExpired } from '../utils/helpers.js';
5
-
6
- export interface SecretsVaultConfig {
7
- storage: IndexedDBStorage;
8
- encryption: {
9
- algorithm: 'AES-256-GCM';
10
- masterKey: string;
11
- keyDerivation?: 'PBKDF2' | 'scrypt';
12
- };
13
- rotation?: {
14
- checkInterval: number;
15
- notifyBefore: number;
16
- };
17
- }
18
-
19
- export interface SetSecretOptions {
20
- tags?: string[];
21
- environment?: Environment;
22
- expiresAt?: number;
23
- rotateEvery?: number;
24
- createdBy?: string;
25
- }
26
-
27
- export class SecretsVault {
28
- private storage: IndexedDBStorage;
29
- private encryption: SecretEncryption;
30
- private masterKey: string;
31
- private rotationInterval?: NodeJS.Timeout;
32
-
33
- constructor(config: SecretsVaultConfig) {
34
- this.storage = config.storage;
35
- this.encryption = new SecretEncryption();
36
- this.masterKey = config.encryption.masterKey;
37
-
38
- if (config.rotation) {
39
- this.startRotationCheck(config.rotation.checkInterval, config.rotation.notifyBefore);
40
- }
41
- }
42
-
43
- async set(key: string, value: string, options: SetSecretOptions = {}): Promise<string> {
44
- const existing = await this.storage.getSecretByKey(key);
45
- const now = Date.now();
46
-
47
- const encryptedValue = await this.encryption.encrypt(value, this.masterKey);
48
-
49
- const secret: Secret = {
50
- id: existing?.id || generateId('secret'),
51
- key,
52
- value: encryptedValue,
53
- encrypted: true,
54
- version: existing ? existing.version + 1 : 1,
55
- tags: options.tags || [],
56
- environment: options.environment,
57
- expiresAt: options.expiresAt,
58
- rotateEvery: options.rotateEvery,
59
- lastRotated: now,
60
- lastAccessed: undefined,
61
- accessCount: 0,
62
- createdBy: options.createdBy || 'system',
63
- createdAt: existing?.createdAt || now,
64
- updatedAt: now,
65
- };
66
-
67
- await this.storage.saveSecret(secret);
68
-
69
- await this.logAccess(secret.id, options.createdBy || 'system', 'write');
70
-
71
- return secret.id;
72
- }
73
-
74
- async get(key: string, userId = 'system'): Promise<string | null> {
75
- const secret = await this.storage.getSecretByKey(key);
76
-
77
- if (!secret) return null;
78
-
79
- if (isExpired(secret.expiresAt)) {
80
- throw new Error(`Secret '${key}' has expired`);
81
- }
82
-
83
- const decryptedValue = await this.encryption.decrypt(secret.value, this.masterKey);
84
-
85
- secret.lastAccessed = Date.now();
86
- secret.accessCount += 1;
87
- await this.storage.saveSecret(secret);
88
-
89
- await this.logAccess(secret.id, userId, 'read');
90
-
91
- return decryptedValue;
92
- }
93
-
94
- async list(filter?: { environment?: Environment; tags?: string[] }): Promise<Array<Omit<Secret, 'value'>>> {
95
- const secrets = await this.storage.listSecrets(filter);
96
-
97
- return secrets.map(({ value, ...rest }) => rest);
98
- }
99
-
100
- async rotate(key: string, newValue: string, userId = 'system'): Promise<void> {
101
- const secret = await this.storage.getSecretByKey(key);
102
-
103
- if (!secret) {
104
- throw new Error(`Secret '${key}' not found`);
105
- }
106
-
107
- const encryptedValue = await this.encryption.encrypt(newValue, this.masterKey);
108
-
109
- secret.value = encryptedValue;
110
- secret.version += 1;
111
- secret.lastRotated = Date.now();
112
- secret.updatedAt = Date.now();
113
-
114
- await this.storage.saveSecret(secret);
115
-
116
- await this.logAccess(secret.id, userId, 'rotate');
117
- }
118
-
119
- async delete(key: string, userId = 'system'): Promise<void> {
120
- const secret = await this.storage.getSecretByKey(key);
121
-
122
- if (!secret) {
123
- throw new Error(`Secret '${key}' not found`);
124
- }
125
-
126
- await this.storage.deleteSecret(secret.id);
127
-
128
- await this.logAccess(secret.id, userId, 'delete');
129
- }
130
-
131
- async getAccessLog(key: string, limit = 100): Promise<SecretAccess[]> {
132
- const secret = await this.storage.getSecretByKey(key);
133
-
134
- if (!secret) {
135
- throw new Error(`Secret '${key}' not found`);
136
- }
137
-
138
- return await this.storage.getSecretAccessLog(secret.id, limit);
139
- }
140
-
141
- async getSecretsNeedingRotation(beforeMs: number = 7 * 24 * 60 * 60 * 1000): Promise<Secret[]> {
142
- const allSecrets = await this.storage.listSecrets();
143
- const now = Date.now();
144
- const threshold = now + beforeMs;
145
-
146
- return allSecrets.filter(secret => {
147
- if (!secret.rotateEvery || !secret.lastRotated) return false;
148
-
149
- const nextRotation = secret.lastRotated + secret.rotateEvery;
150
- return nextRotation <= threshold;
151
- });
152
- }
153
-
154
- async getExpiredSecrets(): Promise<Secret[]> {
155
- const allSecrets = await this.storage.listSecrets();
156
- return allSecrets.filter(secret => isExpired(secret.expiresAt));
157
- }
158
-
159
- private async logAccess(secretId: string, userId: string, action: 'read' | 'write' | 'delete' | 'rotate'): Promise<void> {
160
- const accessLog: SecretAccess = {
161
- id: generateId('access'),
162
- secretId,
163
- userId,
164
- action,
165
- timestamp: Date.now(),
166
- };
167
-
168
- await this.storage.logSecretAccess(accessLog);
169
- }
170
-
171
- private startRotationCheck(interval: number, notifyBefore: number): void {
172
- this.rotationInterval = setInterval(async () => {
173
- const needsRotation = await this.getSecretsNeedingRotation(notifyBefore);
174
-
175
- if (needsRotation.length > 0) {
176
- console.warn(`[SecFn] ${needsRotation.length} secret(s) need rotation:`,
177
- needsRotation.map(s => s.key));
178
- }
179
- }, interval);
180
- }
181
-
182
- stop(): void {
183
- if (this.rotationInterval) {
184
- clearInterval(this.rotationInterval);
185
- }
186
- }
187
- }
@@ -1,355 +0,0 @@
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
- }