@push.rocks/smartmta 6.5.2 → 8.0.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.
Files changed (84) hide show
  1. package/changelog.md +40 -0
  2. package/dist_rust/mailer-bin_linux_amd64 +0 -0
  3. package/dist_rust/mailer-bin_linux_arm64 +0 -0
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/functions.errors.d.ts +3 -0
  6. package/dist_ts/functions.errors.js +8 -0
  7. package/dist_ts/index.d.ts +3 -0
  8. package/dist_ts/index.js +4 -1
  9. package/dist_ts/mail/core/classes.bouncemanager.d.ts +16 -12
  10. package/dist_ts/mail/core/classes.bouncemanager.js +146 -129
  11. package/dist_ts/mail/core/classes.email.js +15 -13
  12. package/dist_ts/mail/core/classes.emailvalidator.d.ts +3 -3
  13. package/dist_ts/mail/core/classes.emailvalidator.js +7 -5
  14. package/dist_ts/mail/core/classes.templatemanager.d.ts +10 -6
  15. package/dist_ts/mail/core/classes.templatemanager.js +35 -51
  16. package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +92 -22
  17. package/dist_ts/mail/delivery/classes.delivery.queue.js +738 -151
  18. package/dist_ts/mail/delivery/classes.delivery.system.d.ts +13 -7
  19. package/dist_ts/mail/delivery/classes.delivery.system.js +458 -145
  20. package/dist_ts/mail/delivery/classes.unified.rate.limiter.js +9 -8
  21. package/dist_ts/mail/delivery/functions.safe-observers.d.ts +10 -0
  22. package/dist_ts/mail/delivery/functions.safe-observers.js +37 -0
  23. package/dist_ts/mail/delivery/interfaces.d.ts +21 -0
  24. package/dist_ts/mail/delivery/interfaces.js +1 -1
  25. package/dist_ts/mail/interfaces.storage.d.ts +37 -6
  26. package/dist_ts/mail/interfaces.storage.js +33 -3
  27. package/dist_ts/mail/routing/classes.dkim.manager.d.ts +10 -6
  28. package/dist_ts/mail/routing/classes.dkim.manager.js +47 -31
  29. package/dist_ts/mail/routing/classes.dns.manager.d.ts +7 -5
  30. package/dist_ts/mail/routing/classes.dns.manager.js +22 -11
  31. package/dist_ts/mail/routing/classes.email.action.executor.d.ts +2 -1
  32. package/dist_ts/mail/routing/classes.email.action.executor.js +45 -16
  33. package/dist_ts/mail/routing/classes.email.router.d.ts +5 -2
  34. package/dist_ts/mail/routing/classes.email.router.js +17 -12
  35. package/dist_ts/mail/routing/classes.unified.email.server.d.ts +12 -6
  36. package/dist_ts/mail/routing/classes.unified.email.server.js +69 -78
  37. package/dist_ts/mail/routing/interfaces.d.ts +0 -2
  38. package/dist_ts/mail/security/classes.dkimcreator.d.ts +22 -45
  39. package/dist_ts/mail/security/classes.dkimcreator.js +93 -296
  40. package/dist_ts/mail/security/classes.spfverifier.js +5 -3
  41. package/dist_ts/paths.d.ts +0 -12
  42. package/dist_ts/paths.js +3 -36
  43. package/dist_ts/plugins.d.ts +2 -5
  44. package/dist_ts/plugins.js +3 -6
  45. package/dist_ts/security/classes.contentscanner.js +14 -12
  46. package/dist_ts/security/classes.ipreputationchecker.d.ts +9 -6
  47. package/dist_ts/security/classes.ipreputationchecker.js +42 -93
  48. package/dist_ts/security/classes.rustsecuritybridge.d.ts +52 -4
  49. package/dist_ts/security/classes.rustsecuritybridge.js +201 -4
  50. package/dist_ts/security/classes.securitylogger.js +7 -5
  51. package/dist_ts/security/index.d.ts +1 -1
  52. package/dist_ts/security/index.js +2 -2
  53. package/package.json +8 -9
  54. package/readme.hints.md +4 -3
  55. package/readme.md +50 -18
  56. package/readme.plan.md +6 -0
  57. package/ts/00_commitinfo_data.ts +1 -1
  58. package/ts/functions.errors.ts +8 -0
  59. package/ts/index.ts +3 -0
  60. package/ts/mail/core/classes.bouncemanager.ts +188 -155
  61. package/ts/mail/core/classes.email.ts +20 -14
  62. package/ts/mail/core/classes.emailvalidator.ts +9 -7
  63. package/ts/mail/core/classes.templatemanager.ts +42 -57
  64. package/ts/mail/delivery/classes.delivery.queue.ts +996 -185
  65. package/ts/mail/delivery/classes.delivery.system.ts +583 -170
  66. package/ts/mail/delivery/classes.unified.rate.limiter.ts +9 -8
  67. package/ts/mail/delivery/functions.safe-observers.ts +45 -0
  68. package/ts/mail/delivery/interfaces.ts +27 -1
  69. package/ts/mail/interfaces.storage.ts +64 -10
  70. package/ts/mail/routing/classes.dkim.manager.ts +65 -40
  71. package/ts/mail/routing/classes.dns.manager.ts +39 -16
  72. package/ts/mail/routing/classes.email.action.executor.ts +64 -17
  73. package/ts/mail/routing/classes.email.router.ts +20 -13
  74. package/ts/mail/routing/classes.unified.email.server.ts +107 -86
  75. package/ts/mail/routing/interfaces.ts +0 -2
  76. package/ts/mail/security/classes.dkimcreator.ts +150 -355
  77. package/ts/mail/security/classes.spfverifier.ts +4 -2
  78. package/ts/paths.ts +2 -41
  79. package/ts/plugins.ts +1 -6
  80. package/ts/security/classes.contentscanner.ts +14 -12
  81. package/ts/security/classes.ipreputationchecker.ts +46 -99
  82. package/ts/security/classes.rustsecuritybridge.ts +272 -6
  83. package/ts/security/classes.securitylogger.ts +6 -4
  84. package/ts/security/index.ts +5 -1
package/ts/paths.ts CHANGED
@@ -1,48 +1,9 @@
1
1
  import * as plugins from './plugins.js';
2
2
 
3
- // Base directories
4
- export const baseDir = process.cwd();
3
+ // Code and packaged executable paths only. SmartMTA data is supplied through
4
+ // managed storage interfaces and never persisted directly to the filesystem.
5
5
  export const packageDir = plugins.path.join(
6
6
  plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
7
7
  '../'
8
8
  );
9
9
  export const distServe = plugins.path.join(packageDir, './dist_serve');
10
-
11
- // Configure data directory with environment variable or default to .nogit/data
12
- const DEFAULT_DATA_PATH = '.nogit/data';
13
- export const dataDir = process.env.DATA_DIR
14
- ? process.env.DATA_DIR
15
- : plugins.path.join(baseDir, DEFAULT_DATA_PATH);
16
-
17
- // MTA directories
18
- export const keysDir = plugins.path.join(dataDir, 'keys');
19
- export const dnsRecordsDir = plugins.path.join(dataDir, 'dns');
20
- export const sentEmailsDir = plugins.path.join(dataDir, 'emails', 'sent');
21
- export const receivedEmailsDir = plugins.path.join(dataDir, 'emails', 'received');
22
- export const failedEmailsDir = plugins.path.join(dataDir, 'emails', 'failed'); // For failed emails
23
- export const logsDir = plugins.path.join(dataDir, 'logs'); // For logs
24
-
25
- // Email template directories
26
- export const emailTemplatesDir = plugins.path.join(dataDir, 'templates', 'email');
27
- export const MtaAttachmentsDir = plugins.path.join(dataDir, 'attachments'); // For email attachments
28
-
29
- // Configuration path
30
- export const configPath = process.env.CONFIG_PATH
31
- ? process.env.CONFIG_PATH
32
- : plugins.path.join(baseDir, 'config.json');
33
-
34
- // Create directories if they don't exist
35
- export async function ensureDirectories() {
36
- // Ensure data directories
37
- await plugins.smartfs.directory(dataDir).recursive().create();
38
- await plugins.smartfs.directory(keysDir).recursive().create();
39
- await plugins.smartfs.directory(dnsRecordsDir).recursive().create();
40
- await plugins.smartfs.directory(sentEmailsDir).recursive().create();
41
- await plugins.smartfs.directory(receivedEmailsDir).recursive().create();
42
- await plugins.smartfs.directory(failedEmailsDir).recursive().create();
43
- await plugins.smartfs.directory(logsDir).recursive().create();
44
-
45
- // Ensure email template directories
46
- await plugins.smartfs.directory(emailTemplatesDir).recursive().create();
47
- await plugins.smartfs.directory(MtaAttachmentsDir).recursive().create();
48
- }
package/ts/plugins.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  // node native
2
2
  import * as dns from 'dns';
3
- import * as fs from 'fs';
4
3
  import * as crypto from 'crypto';
5
4
  import * as http from 'http';
6
5
  import * as net from 'net';
@@ -11,7 +10,6 @@ import * as util from 'util';
11
10
 
12
11
  export {
13
12
  dns,
14
- fs,
15
13
  crypto,
16
14
  http,
17
15
  net,
@@ -23,15 +21,12 @@ export {
23
21
 
24
22
  // @push.rocks scope
25
23
  import * as smartfile from '@push.rocks/smartfile';
26
- import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
27
24
  import * as smartlog from '@push.rocks/smartlog';
28
25
  import * as smartmail from '@push.rocks/smartmail';
29
26
  import * as smartpath from '@push.rocks/smartpath';
30
27
  import * as smartrust from '@push.rocks/smartrust';
31
28
 
32
- export const smartfs = new SmartFs(new SmartFsProviderNode());
33
-
34
- export { smartfile, SmartFs, smartlog, smartmail, smartpath, smartrust };
29
+ export { smartfile, smartlog, smartmail, smartpath, smartrust };
35
30
 
36
31
  // Define SmartLog types for use in error handling
37
32
  export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug';
@@ -1,11 +1,12 @@
1
1
  import * as plugins from '../plugins.js';
2
- import * as paths from '../paths.js';
3
2
  import { logger } from '../logger.js';
4
3
  import { Email } from '../mail/core/classes.email.js';
5
4
  import type { IAttachment } from '../mail/core/classes.email.js';
6
5
  import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
7
6
  import { RustSecurityBridge } from './classes.rustsecuritybridge.js';
8
7
  import { LRUCache } from 'lru-cache';
8
+ import { getErrorMessage, getErrorStack } from '../functions.errors.js';
9
+ import { callObserverSafely } from '../mail/delivery/functions.safe-observers.js';
9
10
 
10
11
  /**
11
12
  * Scan result information
@@ -180,9 +181,10 @@ export class ContentScanner {
180
181
 
181
182
  return result;
182
183
  } catch (error) {
183
- logger.log('error', `Error scanning email: ${error.message}`, {
184
+ const errorMessage = getErrorMessage(error);
185
+ logger.log('error', `Error scanning email: ${errorMessage}`, {
184
186
  messageId: email.getMessageId(),
185
- error: error.stack
187
+ error: getErrorStack(error)
186
188
  });
187
189
 
188
190
  // Return a safe default with error indication
@@ -192,7 +194,7 @@ export class ContentScanner {
192
194
  scannedElements: ['error'],
193
195
  timestamp: Date.now(),
194
196
  threatType: 'scan_error',
195
- threatDetails: `Scan error: ${error.message}`
197
+ threatDetails: `Scan error: ${errorMessage}`
196
198
  };
197
199
  }
198
200
  }
@@ -301,7 +303,7 @@ export class ContentScanner {
301
303
  .replace(/[\x00-\x09\x0B-\x1F\x7F-\x9F]/g, '') // Remove control chars
302
304
  .replace(/\uFFFD/g, ''); // Remove replacement char
303
305
  } catch (error) {
304
- logger.log('warn', `Error extracting text from buffer: ${error.message}`);
306
+ logger.log('warn', `Error extracting text from buffer: ${getErrorMessage(error)}`);
305
307
  return '';
306
308
  }
307
309
  }
@@ -341,7 +343,7 @@ export class ContentScanner {
341
343
  * @param result The scan result
342
344
  */
343
345
  private logHighThreatFound(email: Email, result: IScanResult): void {
344
- SecurityLogger.getInstance().logEvent({
346
+ void callObserverSafely('content scan security log', () => SecurityLogger.getInstance().logEvent({
345
347
  level: SecurityLogLevel.ERROR,
346
348
  type: SecurityEventType.MALWARE,
347
349
  message: `High threat content detected in email from ${email.from} to ${email.to.join(', ')}`,
@@ -354,8 +356,8 @@ export class ContentScanner {
354
356
  subject: email.subject
355
357
  },
356
358
  success: false,
357
- domain: email.getFromDomain()
358
- });
359
+ domain: email.getFromDomain() || undefined
360
+ }));
359
361
  }
360
362
 
361
363
  /**
@@ -364,7 +366,7 @@ export class ContentScanner {
364
366
  * @param result The scan result
365
367
  */
366
368
  private logThreatFound(email: Email, result: IScanResult): void {
367
- SecurityLogger.getInstance().logEvent({
369
+ void callObserverSafely('content scan security log', () => SecurityLogger.getInstance().logEvent({
368
370
  level: SecurityLogLevel.WARN,
369
371
  type: SecurityEventType.SPAM,
370
372
  message: `Suspicious content detected in email from ${email.from} to ${email.to.join(', ')}`,
@@ -377,8 +379,8 @@ export class ContentScanner {
377
379
  subject: email.subject
378
380
  },
379
381
  success: false,
380
- domain: email.getFromDomain()
381
- });
382
+ domain: email.getFromDomain() || undefined
383
+ }));
382
384
  }
383
385
 
384
386
  /**
@@ -397,4 +399,4 @@ export class ContentScanner {
397
399
  return 'high';
398
400
  }
399
401
  }
400
- }
402
+ }
@@ -1,10 +1,10 @@
1
- import * as plugins from '../plugins.js';
2
- import * as paths from '../paths.js';
3
1
  import { logger } from '../logger.js';
4
- import { hasStorageManagerMethods, type IStorageManagerLike } from '../mail/interfaces.storage.js';
2
+ import { getErrorMessage, getErrorStack } from '../functions.errors.js';
3
+ import type { IStorageManager } from '../mail/interfaces.storage.js';
5
4
  import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
6
5
  import { RustSecurityBridge } from './classes.rustsecuritybridge.js';
7
6
  import { LRUCache } from 'lru-cache';
7
+ import { callObserverSafely } from '../mail/delivery/functions.safe-observers.js';
8
8
 
9
9
  /**
10
10
  * Reputation check result information
@@ -54,20 +54,21 @@ export interface IIPReputationOptions {
54
54
  highRiskThreshold?: number; // Score below this is high risk
55
55
  mediumRiskThreshold?: number; // Score below this is medium risk
56
56
  lowRiskThreshold?: number; // Score below this is low risk
57
- enableLocalCache?: boolean; // Whether to persist cache to disk (default: true)
57
+ persistCache?: boolean; // Whether to persist cache through IStorageManager (default: true)
58
58
  enableDNSBL?: boolean; // Whether to use DNSBL checks (default: true)
59
59
  enableIPInfo?: boolean; // Whether to use IP info service (default: true)
60
60
  }
61
61
 
62
62
  /**
63
63
  * IP reputation checker — delegates DNSBL lookups to the Rust security bridge.
64
- * Retains LRU caching and disk persistence in TypeScript.
64
+ * Retains an in-memory LRU cache and optionally persists it through managed storage.
65
65
  */
66
66
  export class IPReputationChecker {
67
67
  private static instance: IPReputationChecker;
68
68
  private reputationCache: LRUCache<string, IReputationResult>;
69
69
  private options: Required<IIPReputationOptions>;
70
- private storageManager?: IStorageManagerLike;
70
+ private storageManager?: IStorageManager;
71
+ private initializationPromise: Promise<void>;
71
72
 
72
73
  private static readonly DEFAULT_OPTIONS: Required<IIPReputationOptions> = {
73
74
  maxCacheSize: 10000,
@@ -76,12 +77,12 @@ export class IPReputationChecker {
76
77
  highRiskThreshold: ReputationThreshold.HIGH_RISK,
77
78
  mediumRiskThreshold: ReputationThreshold.MEDIUM_RISK,
78
79
  lowRiskThreshold: ReputationThreshold.LOW_RISK,
79
- enableLocalCache: true,
80
+ persistCache: true,
80
81
  enableDNSBL: true,
81
82
  enableIPInfo: true
82
83
  };
83
84
 
84
- constructor(options: IIPReputationOptions = {}, storageManager?: IStorageManagerLike) {
85
+ constructor(options: IIPReputationOptions = {}, storageManager?: IStorageManager) {
85
86
  this.options = {
86
87
  ...IPReputationChecker.DEFAULT_OPTIONS,
87
88
  ...options
@@ -94,14 +95,19 @@ export class IPReputationChecker {
94
95
  ttl: this.options.cacheTTL,
95
96
  });
96
97
 
97
- if (this.options.enableLocalCache) {
98
- this.loadCache().catch(error => {
99
- logger.log('error', `Failed to load IP reputation cache during initialization: ${error.message}`);
100
- });
101
- }
98
+ const hydrationPromise = this.options.persistCache
99
+ ? this.loadCache()
100
+ : Promise.resolve();
101
+ hydrationPromise.catch(() => undefined);
102
+ this.initializationPromise = hydrationPromise;
103
+ }
104
+
105
+ /** Wait until the managed reputation cache has been hydrated. */
106
+ public async initialize(): Promise<void> {
107
+ await this.initializationPromise;
102
108
  }
103
109
 
104
- public static getInstance(options: IIPReputationOptions = {}, storageManager?: IStorageManagerLike): IPReputationChecker {
110
+ public static getInstance(options: IIPReputationOptions = {}, storageManager?: IStorageManager): IPReputationChecker {
105
111
  if (!IPReputationChecker.instance) {
106
112
  IPReputationChecker.instance = new IPReputationChecker(options, storageManager);
107
113
  }
@@ -113,6 +119,8 @@ export class IPReputationChecker {
113
119
  */
114
120
  public async checkReputation(ip: string): Promise<IReputationResult> {
115
121
  try {
122
+ await this.initialize();
123
+
116
124
  if (!this.isValidIPAddress(ip)) {
117
125
  logger.log('warn', `Invalid IP address format: ${ip}`);
118
126
  return this.createErrorResult(ip, 'Invalid IP address format');
@@ -146,7 +154,7 @@ export class IPReputationChecker {
146
154
 
147
155
  this.reputationCache.set(ip, result);
148
156
 
149
- if (this.options.enableLocalCache) {
157
+ if (this.options.persistCache) {
150
158
  this.saveCache().catch(error => {
151
159
  logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
152
160
  });
@@ -155,11 +163,12 @@ export class IPReputationChecker {
155
163
  this.logReputationCheck(ip, result);
156
164
  return result;
157
165
  } catch (error) {
158
- logger.log('error', `Error checking IP reputation for ${ip}: ${error.message}`, {
166
+ const errorMessage = getErrorMessage(error);
167
+ logger.log('error', `Error checking IP reputation for ${ip}: ${errorMessage}`, {
159
168
  ip,
160
- stack: error.stack
169
+ stack: getErrorStack(error)
161
170
  });
162
- const errorResult = this.createErrorResult(ip, error.message);
171
+ const errorResult = this.createErrorResult(ip, errorMessage);
163
172
  // Cache error results to avoid repeated failing lookups
164
173
  this.reputationCache.set(ip, errorResult);
165
174
  return errorResult;
@@ -189,7 +198,7 @@ export class IPReputationChecker {
189
198
  logLevel = SecurityLogLevel.WARN;
190
199
  }
191
200
 
192
- SecurityLogger.getInstance().logEvent({
201
+ void callObserverSafely('IP reputation security log', () => SecurityLogger.getInstance().logEvent({
193
202
  level: logLevel,
194
203
  type: SecurityEventType.IP_REPUTATION,
195
204
  message: `IP reputation check ${result.isSpam ? 'flagged spam' : 'completed'} for ${ip}`,
@@ -204,91 +213,29 @@ export class IPReputationChecker {
204
213
  blacklists: result.blacklists
205
214
  },
206
215
  success: !result.isSpam
207
- });
216
+ }));
208
217
  }
209
218
 
210
219
  private async saveCache(): Promise<void> {
211
- try {
212
- const entries = Array.from(this.reputationCache.entries()).map(([ip, data]) => ({
213
- ip,
214
- data
215
- }));
216
-
217
- if (entries.length === 0) {
218
- return;
219
- }
220
-
221
- const cacheData = JSON.stringify(entries);
222
-
223
- if (hasStorageManagerMethods(this.storageManager, ['set'])) {
224
- await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
225
- logger.log('info', `Saved ${entries.length} IP reputation cache entries to StorageManager`);
226
- } else {
227
- const cacheDir = plugins.path.join(paths.dataDir, 'security');
228
- await plugins.smartfs.directory(cacheDir).recursive().create();
229
- const cacheFile = plugins.path.join(cacheDir, 'ip_reputation_cache.json');
230
- await plugins.smartfs.file(cacheFile).write(cacheData);
231
- logger.log('info', `Saved ${entries.length} IP reputation cache entries to disk`);
232
- }
233
- } catch (error) {
234
- logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
235
- }
220
+ if (!this.storageManager) return;
221
+ const entries = Array.from(this.reputationCache.entries()).map(([ip, data]) => ({ ip, data }));
222
+ if (entries.length === 0) return;
223
+ await this.storageManager.set('/security/ip-reputation-cache.json', JSON.stringify(entries));
224
+ logger.log('info', `Saved ${entries.length} IP reputation cache entries`);
236
225
  }
237
226
 
238
227
  private async loadCache(): Promise<void> {
239
- try {
240
- let cacheData: string | null = null;
241
- let fromFilesystem = false;
242
-
243
- if (hasStorageManagerMethods(this.storageManager, ['get', 'set'])) {
244
- try {
245
- cacheData = await this.storageManager.get('/security/ip-reputation-cache.json');
246
-
247
- if (!cacheData) {
248
- const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
249
- if (plugins.fs.existsSync(cacheFile)) {
250
- logger.log('info', 'Migrating IP reputation cache from filesystem to StorageManager');
251
- cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
252
- fromFilesystem = true;
253
- await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
254
- logger.log('info', 'IP reputation cache migrated to StorageManager successfully');
255
- try {
256
- plugins.fs.unlinkSync(cacheFile);
257
- logger.log('info', 'Old cache file removed after migration');
258
- } catch (deleteError) {
259
- logger.log('warn', `Could not delete old cache file: ${deleteError.message}`);
260
- }
261
- }
262
- }
263
- } catch (error) {
264
- logger.log('error', `Error loading from StorageManager: ${error.message}`);
265
- }
266
- } else {
267
- const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
268
- if (plugins.fs.existsSync(cacheFile)) {
269
- cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
270
- fromFilesystem = true;
271
- }
272
- }
273
-
274
- if (cacheData) {
275
- const entries = JSON.parse(cacheData);
276
- const now = Date.now();
277
- const validEntries = entries.filter(entry => {
278
- const age = now - entry.data.timestamp;
279
- return age < this.options.cacheTTL;
280
- });
281
-
282
- for (const entry of validEntries) {
283
- this.reputationCache.set(entry.ip, entry.data);
284
- }
285
-
286
- const source = fromFilesystem ? 'disk' : 'StorageManager';
287
- logger.log('info', `Loaded ${validEntries.length} IP reputation cache entries from ${source}`);
288
- }
289
- } catch (error) {
290
- logger.log('error', `Failed to load IP reputation cache: ${error.message}`);
228
+ if (!this.storageManager) return;
229
+ const cacheData = await this.storageManager.get('/security/ip-reputation-cache.json');
230
+ if (!cacheData) return;
231
+
232
+ const entries = JSON.parse(cacheData);
233
+ const now = Date.now();
234
+ const validEntries = entries.filter((entry) => now - entry.data.timestamp < this.options.cacheTTL);
235
+ for (const entry of validEntries) {
236
+ this.reputationCache.set(entry.ip, entry.data);
291
237
  }
238
+ logger.log('info', `Loaded ${validEntries.length} IP reputation cache entries`);
292
239
  }
293
240
 
294
241
  public static getRiskLevel(score: number): 'high' | 'medium' | 'low' | 'trusted' {
@@ -303,11 +250,11 @@ export class IPReputationChecker {
303
250
  }
304
251
  }
305
252
 
306
- public updateStorageManager(storageManager: IStorageManagerLike): void {
253
+ public updateStorageManager(storageManager: IStorageManager): void {
307
254
  this.storageManager = storageManager;
308
255
  logger.log('info', 'IPReputationChecker storage manager updated');
309
256
 
310
- if (this.options.enableLocalCache && this.reputationCache.size > 0) {
257
+ if (this.options.persistCache && this.reputationCache.size > 0) {
311
258
  this.saveCache().catch(error => {
312
259
  logger.log('error', `Failed to save cache to new storage manager: ${error.message}`);
313
260
  });