@serve.zone/dcrouter 11.0.4 → 11.0.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.
Files changed (118) hide show
  1. package/dist_serve/bundle.js +1 -1
  2. package/package.json +1 -1
  3. package/ts/00_commitinfo_data.ts +1 -1
  4. package/ts_web/00_commitinfo_data.ts +1 -1
  5. package/dist_ts/00_commitinfo_data.d.ts +0 -8
  6. package/dist_ts/00_commitinfo_data.js +0 -9
  7. package/dist_ts/cache/classes.cache.cleaner.d.ts +0 -47
  8. package/dist_ts/cache/classes.cache.cleaner.js +0 -130
  9. package/dist_ts/cache/documents/classes.cached.email.d.ts +0 -125
  10. package/dist_ts/cache/documents/classes.cached.email.js +0 -337
  11. package/dist_ts/cache/documents/classes.cached.ip.reputation.d.ts +0 -119
  12. package/dist_ts/cache/documents/classes.cached.ip.reputation.js +0 -323
  13. package/dist_ts/cache/documents/index.d.ts +0 -2
  14. package/dist_ts/cache/documents/index.js +0 -3
  15. package/dist_ts/cache/index.d.ts +0 -4
  16. package/dist_ts/cache/index.js +0 -7
  17. package/dist_ts/classes.cert-provision-scheduler.d.ts +0 -53
  18. package/dist_ts/classes.cert-provision-scheduler.js +0 -110
  19. package/dist_ts/classes.dcrouter.d.ts +0 -337
  20. package/dist_ts/classes.dcrouter.js +0 -1405
  21. package/dist_ts/classes.storage-cert-manager.d.ts +0 -18
  22. package/dist_ts/classes.storage-cert-manager.js +0 -43
  23. package/dist_ts/config/classes.api-token-manager.d.ts +0 -46
  24. package/dist_ts/config/classes.api-token-manager.js +0 -150
  25. package/dist_ts/config/classes.route-config-manager.d.ts +0 -35
  26. package/dist_ts/config/classes.route-config-manager.js +0 -231
  27. package/dist_ts/config/index.d.ts +0 -3
  28. package/dist_ts/config/index.js +0 -5
  29. package/dist_ts/config/validator.d.ts +0 -104
  30. package/dist_ts/config/validator.js +0 -152
  31. package/dist_ts/errors/base.errors.d.ts +0 -224
  32. package/dist_ts/errors/base.errors.js +0 -320
  33. package/dist_ts/errors/error-handler.d.ts +0 -98
  34. package/dist_ts/errors/error-handler.js +0 -282
  35. package/dist_ts/errors/error.codes.d.ts +0 -115
  36. package/dist_ts/errors/error.codes.js +0 -136
  37. package/dist_ts/errors/index.d.ts +0 -54
  38. package/dist_ts/errors/index.js +0 -136
  39. package/dist_ts/errors/reputation.errors.d.ts +0 -183
  40. package/dist_ts/errors/reputation.errors.js +0 -292
  41. package/dist_ts/index.d.ts +0 -7
  42. package/dist_ts/index.js +0 -11
  43. package/dist_ts/logger.d.ts +0 -21
  44. package/dist_ts/logger.js +0 -81
  45. package/dist_ts/monitoring/classes.metricscache.d.ts +0 -32
  46. package/dist_ts/monitoring/classes.metricscache.js +0 -63
  47. package/dist_ts/monitoring/classes.metricsmanager.d.ts +0 -178
  48. package/dist_ts/monitoring/classes.metricsmanager.js +0 -642
  49. package/dist_ts/monitoring/index.d.ts +0 -1
  50. package/dist_ts/monitoring/index.js +0 -2
  51. package/dist_ts/opsserver/classes.opsserver.d.ts +0 -37
  52. package/dist_ts/opsserver/classes.opsserver.js +0 -85
  53. package/dist_ts/opsserver/handlers/admin.handler.d.ts +0 -31
  54. package/dist_ts/opsserver/handlers/admin.handler.js +0 -180
  55. package/dist_ts/opsserver/handlers/api-token.handler.d.ts +0 -6
  56. package/dist_ts/opsserver/handlers/api-token.handler.js +0 -62
  57. package/dist_ts/opsserver/handlers/certificate.handler.d.ts +0 -32
  58. package/dist_ts/opsserver/handlers/certificate.handler.js +0 -421
  59. package/dist_ts/opsserver/handlers/config.handler.d.ts +0 -7
  60. package/dist_ts/opsserver/handlers/config.handler.js +0 -192
  61. package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +0 -30
  62. package/dist_ts/opsserver/handlers/email-ops.handler.js +0 -227
  63. package/dist_ts/opsserver/handlers/index.d.ts +0 -11
  64. package/dist_ts/opsserver/handlers/index.js +0 -12
  65. package/dist_ts/opsserver/handlers/logs.handler.d.ts +0 -25
  66. package/dist_ts/opsserver/handlers/logs.handler.js +0 -256
  67. package/dist_ts/opsserver/handlers/radius.handler.d.ts +0 -6
  68. package/dist_ts/opsserver/handlers/radius.handler.js +0 -295
  69. package/dist_ts/opsserver/handlers/remoteingress.handler.d.ts +0 -6
  70. package/dist_ts/opsserver/handlers/remoteingress.handler.js +0 -156
  71. package/dist_ts/opsserver/handlers/route-management.handler.d.ts +0 -14
  72. package/dist_ts/opsserver/handlers/route-management.handler.js +0 -117
  73. package/dist_ts/opsserver/handlers/security.handler.d.ts +0 -9
  74. package/dist_ts/opsserver/handlers/security.handler.js +0 -231
  75. package/dist_ts/opsserver/handlers/stats.handler.d.ts +0 -11
  76. package/dist_ts/opsserver/handlers/stats.handler.js +0 -399
  77. package/dist_ts/opsserver/helpers/guards.d.ts +0 -27
  78. package/dist_ts/opsserver/helpers/guards.js +0 -43
  79. package/dist_ts/opsserver/index.d.ts +0 -1
  80. package/dist_ts/opsserver/index.js +0 -2
  81. package/dist_ts/paths.d.ts +0 -26
  82. package/dist_ts/paths.js +0 -45
  83. package/dist_ts/plugins.d.ts +0 -79
  84. package/dist_ts/plugins.js +0 -113
  85. package/dist_ts/radius/classes.accounting.manager.d.ts +0 -218
  86. package/dist_ts/radius/classes.accounting.manager.js +0 -417
  87. package/dist_ts/radius/classes.radius.server.d.ts +0 -171
  88. package/dist_ts/radius/classes.radius.server.js +0 -385
  89. package/dist_ts/radius/classes.vlan.manager.d.ts +0 -128
  90. package/dist_ts/radius/classes.vlan.manager.js +0 -279
  91. package/dist_ts/radius/index.d.ts +0 -13
  92. package/dist_ts/radius/index.js +0 -14
  93. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +0 -82
  94. package/dist_ts/remoteingress/classes.remoteingress-manager.js +0 -227
  95. package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +0 -59
  96. package/dist_ts/remoteingress/classes.tunnel-manager.js +0 -165
  97. package/dist_ts/remoteingress/index.d.ts +0 -2
  98. package/dist_ts/remoteingress/index.js +0 -3
  99. package/dist_ts/security/classes.contentscanner.d.ts +0 -164
  100. package/dist_ts/security/classes.contentscanner.js +0 -642
  101. package/dist_ts/security/classes.ipreputationchecker.d.ts +0 -160
  102. package/dist_ts/security/classes.ipreputationchecker.js +0 -537
  103. package/dist_ts/security/classes.securitylogger.d.ts +0 -144
  104. package/dist_ts/security/classes.securitylogger.js +0 -233
  105. package/dist_ts/security/index.d.ts +0 -3
  106. package/dist_ts/security/index.js +0 -4
  107. package/dist_ts/sms/classes.smsservice.d.ts +0 -15
  108. package/dist_ts/sms/classes.smsservice.js +0 -72
  109. package/dist_ts/sms/config/sms.config.d.ts +0 -93
  110. package/dist_ts/sms/config/sms.config.js +0 -2
  111. package/dist_ts/sms/config/sms.schema.d.ts +0 -5
  112. package/dist_ts/sms/config/sms.schema.js +0 -121
  113. package/dist_ts/sms/index.d.ts +0 -1
  114. package/dist_ts/sms/index.js +0 -2
  115. package/dist_ts/storage/classes.storagemanager.d.ts +0 -83
  116. package/dist_ts/storage/classes.storagemanager.js +0 -350
  117. package/dist_ts/storage/index.d.ts +0 -1
  118. package/dist_ts/storage/index.js +0 -3
@@ -1,642 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- import { logger } from '../logger.js';
3
- import { Email } from '@push.rocks/smartmta';
4
- import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
5
- import { LRUCache } from 'lru-cache';
6
- /**
7
- * Threat categories
8
- */
9
- export var ThreatCategory;
10
- (function (ThreatCategory) {
11
- ThreatCategory["SPAM"] = "spam";
12
- ThreatCategory["PHISHING"] = "phishing";
13
- ThreatCategory["MALWARE"] = "malware";
14
- ThreatCategory["EXECUTABLE"] = "executable";
15
- ThreatCategory["SUSPICIOUS_LINK"] = "suspicious_link";
16
- ThreatCategory["MALICIOUS_MACRO"] = "malicious_macro";
17
- ThreatCategory["XSS"] = "xss";
18
- ThreatCategory["SENSITIVE_DATA"] = "sensitive_data";
19
- ThreatCategory["BLACKLISTED_CONTENT"] = "blacklisted_content";
20
- ThreatCategory["CUSTOM_RULE"] = "custom_rule";
21
- })(ThreatCategory || (ThreatCategory = {}));
22
- /**
23
- * Content Scanner for detecting malicious email content
24
- */
25
- export class ContentScanner {
26
- static instance;
27
- scanCache;
28
- options;
29
- // Predefined patterns for common threats
30
- static MALICIOUS_PATTERNS = {
31
- // Phishing patterns
32
- phishing: [
33
- /(?:verify|confirm|update|login).*(?:account|password|details)/i,
34
- /urgent.*(?:action|attention|required)/i,
35
- /(?:paypal|apple|microsoft|amazon|google|bank).*(?:verify|confirm|suspend)/i,
36
- /your.*(?:account).*(?:suspended|compromised|locked)/i,
37
- /\b(?:password reset|security alert|security notice)\b/i
38
- ],
39
- // Spam indicators
40
- spam: [
41
- /\b(?:viagra|cialis|enlargement|diet pill|lose weight fast|cheap meds)\b/i,
42
- /\b(?:million dollars|lottery winner|prize claim|inheritance|rich widow)\b/i,
43
- /\b(?:earn from home|make money fast|earn \$\d{3,}\/day)\b/i,
44
- /\b(?:limited time offer|act now|exclusive deal|only \d+ left)\b/i,
45
- /\b(?:forex|stock tip|investment opportunity|cryptocurrency|bitcoin)\b/i
46
- ],
47
- // Malware indicators in text
48
- malware: [
49
- /(?:attached file|see attachment).*(?:invoice|receipt|statement|document)/i,
50
- /open.*(?:the attached|this attachment)/i,
51
- /(?:enable|allow).*(?:macros|content|editing)/i,
52
- /download.*(?:attachment|file|document)/i,
53
- /\b(?:ransomware protection|virus alert|malware detected)\b/i
54
- ],
55
- // Suspicious links
56
- suspiciousLinks: [
57
- /https?:\/\/bit\.ly\//i,
58
- /https?:\/\/goo\.gl\//i,
59
- /https?:\/\/t\.co\//i,
60
- /https?:\/\/tinyurl\.com\//i,
61
- /https?:\/\/(?:\d{1,3}\.){3}\d{1,3}/i, // IP address URLs
62
- /https?:\/\/.*\.(?:xyz|top|club|gq|cf)\//i, // Suspicious TLDs
63
- /(?:login|account|signin|auth).*\.(?!gov|edu|com|org|net)\w+\.\w+/i, // Login pages on unusual domains
64
- ],
65
- // XSS and script injection
66
- scriptInjection: [
67
- /<script.*>.*<\/script>/is,
68
- /javascript:/i,
69
- /on(?:click|load|mouse|error|focus|blur)=".*"/i,
70
- /document\.(?:cookie|write|location)/i,
71
- /eval\s*\(/i
72
- ],
73
- // Sensitive data patterns
74
- sensitiveData: [
75
- /\b(?:\d{3}-\d{2}-\d{4}|\d{9})\b/, // SSN
76
- /\b\d{13,16}\b/, // Credit card numbers
77
- /\b(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})\b/ // Possible Base64
78
- ]
79
- };
80
- // Common executable extensions
81
- static EXECUTABLE_EXTENSIONS = [
82
- '.exe', '.dll', '.bat', '.cmd', '.msi', '.js', '.vbs', '.ps1',
83
- '.sh', '.jar', '.py', '.com', '.scr', '.pif', '.hta', '.cpl',
84
- '.reg', '.vba', '.lnk', '.wsf', '.msi', '.msp', '.mst'
85
- ];
86
- // Document formats that may contain macros
87
- static MACRO_DOCUMENT_EXTENSIONS = [
88
- '.doc', '.docm', '.xls', '.xlsm', '.ppt', '.pptm', '.dotm', '.xlsb', '.ppam', '.potm'
89
- ];
90
- /**
91
- * Default options for the content scanner
92
- */
93
- static DEFAULT_OPTIONS = {
94
- maxCacheSize: 10000,
95
- cacheTTL: 24 * 60 * 60 * 1000, // 24 hours
96
- scanSubject: true,
97
- scanBody: true,
98
- scanAttachments: true,
99
- maxAttachmentSizeToScan: 10 * 1024 * 1024, // 10MB
100
- scanAttachmentNames: true,
101
- blockExecutables: true,
102
- blockMacros: true,
103
- customRules: [],
104
- minThreatScore: 30, // Minimum score to consider content as a threat
105
- highThreatScore: 70 // Score above which content is considered high threat
106
- };
107
- /**
108
- * Constructor for the ContentScanner
109
- * @param options Configuration options
110
- */
111
- constructor(options = {}) {
112
- // Merge with default options
113
- this.options = {
114
- ...ContentScanner.DEFAULT_OPTIONS,
115
- ...options
116
- };
117
- // Initialize cache
118
- this.scanCache = new LRUCache({
119
- max: this.options.maxCacheSize,
120
- ttl: this.options.cacheTTL,
121
- });
122
- logger.log('info', 'ContentScanner initialized');
123
- }
124
- /**
125
- * Get the singleton instance of the scanner
126
- * @param options Configuration options
127
- * @returns Singleton scanner instance
128
- */
129
- static getInstance(options = {}) {
130
- if (!ContentScanner.instance) {
131
- ContentScanner.instance = new ContentScanner(options);
132
- }
133
- return ContentScanner.instance;
134
- }
135
- /**
136
- * Reset the singleton instance (for shutdown/testing)
137
- */
138
- static resetInstance() {
139
- ContentScanner.instance = undefined;
140
- }
141
- /**
142
- * Scan an email for malicious content
143
- * @param email The email to scan
144
- * @returns Scan result
145
- */
146
- async scanEmail(email) {
147
- try {
148
- // Generate a cache key from the email
149
- const cacheKey = this.generateCacheKey(email);
150
- // Check cache first
151
- const cachedResult = this.scanCache.get(cacheKey);
152
- if (cachedResult) {
153
- logger.log('info', `Using cached scan result for email ${email.getMessageId()}`);
154
- return cachedResult;
155
- }
156
- // Initialize scan result
157
- const result = {
158
- isClean: true,
159
- threatScore: 0,
160
- scannedElements: [],
161
- timestamp: Date.now()
162
- };
163
- // List of scan promises
164
- const scanPromises = [];
165
- // Scan subject
166
- if (this.options.scanSubject && email.subject) {
167
- scanPromises.push(this.scanSubject(email.subject, result));
168
- }
169
- // Scan body content
170
- if (this.options.scanBody) {
171
- if (email.text) {
172
- scanPromises.push(this.scanTextContent(email.text, result));
173
- }
174
- if (email.html) {
175
- scanPromises.push(this.scanHtmlContent(email.html, result));
176
- }
177
- }
178
- // Scan attachments
179
- if (this.options.scanAttachments && email.attachments && email.attachments.length > 0) {
180
- for (const attachment of email.attachments) {
181
- scanPromises.push(this.scanAttachment(attachment, result));
182
- }
183
- }
184
- // Run all scans in parallel
185
- await Promise.all(scanPromises);
186
- // Determine if the email is clean based on threat score
187
- result.isClean = result.threatScore < this.options.minThreatScore;
188
- // Save to cache
189
- this.scanCache.set(cacheKey, result);
190
- // Log high threat findings
191
- if (result.threatScore >= this.options.highThreatScore) {
192
- this.logHighThreatFound(email, result);
193
- }
194
- else if (!result.isClean) {
195
- this.logThreatFound(email, result);
196
- }
197
- return result;
198
- }
199
- catch (error) {
200
- logger.log('error', `Error scanning email: ${error.message}`, {
201
- messageId: email.getMessageId(),
202
- error: error.stack
203
- });
204
- // Return a safe default with error indication
205
- return {
206
- isClean: true, // Let it pass if scanner fails (configure as desired)
207
- threatScore: 0,
208
- scannedElements: ['error'],
209
- timestamp: Date.now(),
210
- threatType: 'scan_error',
211
- threatDetails: `Scan error: ${error.message}`
212
- };
213
- }
214
- }
215
- /**
216
- * Generate a cache key from an email
217
- * @param email The email to generate a key for
218
- * @returns Cache key
219
- */
220
- generateCacheKey(email) {
221
- // Use message ID if available
222
- if (email.getMessageId()) {
223
- return `email:${email.getMessageId()}`;
224
- }
225
- // Fallback to a hash of key content
226
- const contentToHash = [
227
- email.from,
228
- email.subject || '',
229
- email.text?.substring(0, 1000) || '',
230
- email.html?.substring(0, 1000) || '',
231
- email.attachments?.length || 0
232
- ].join(':');
233
- return `email:${plugins.crypto.createHash('sha256').update(contentToHash).digest('hex')}`;
234
- }
235
- /**
236
- * Scan email subject for threats
237
- * @param subject The subject to scan
238
- * @param result The scan result to update
239
- */
240
- async scanSubject(subject, result) {
241
- result.scannedElements.push('subject');
242
- // Check against phishing patterns
243
- for (const pattern of ContentScanner.MALICIOUS_PATTERNS.phishing) {
244
- if (pattern.test(subject)) {
245
- result.threatScore += 25;
246
- result.threatType = ThreatCategory.PHISHING;
247
- result.threatDetails = `Subject contains potential phishing indicators: ${subject}`;
248
- return;
249
- }
250
- }
251
- // Check against spam patterns
252
- for (const pattern of ContentScanner.MALICIOUS_PATTERNS.spam) {
253
- if (pattern.test(subject)) {
254
- result.threatScore += 15;
255
- result.threatType = ThreatCategory.SPAM;
256
- result.threatDetails = `Subject contains potential spam indicators: ${subject}`;
257
- return;
258
- }
259
- }
260
- // Check custom rules
261
- for (const rule of this.options.customRules) {
262
- const pattern = rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern, 'i');
263
- if (pattern.test(subject)) {
264
- result.threatScore += rule.score;
265
- result.threatType = rule.type;
266
- result.threatDetails = rule.description;
267
- return;
268
- }
269
- }
270
- }
271
- /**
272
- * Scan plain text content for threats
273
- * @param text The text content to scan
274
- * @param result The scan result to update
275
- */
276
- async scanTextContent(text, result) {
277
- result.scannedElements.push('text');
278
- // Check suspicious links
279
- for (const pattern of ContentScanner.MALICIOUS_PATTERNS.suspiciousLinks) {
280
- if (pattern.test(text)) {
281
- result.threatScore += 20;
282
- if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.SUSPICIOUS_LINK ? 0 : 20)) {
283
- result.threatType = ThreatCategory.SUSPICIOUS_LINK;
284
- result.threatDetails = `Text contains suspicious links`;
285
- }
286
- }
287
- }
288
- // Check phishing
289
- for (const pattern of ContentScanner.MALICIOUS_PATTERNS.phishing) {
290
- if (pattern.test(text)) {
291
- result.threatScore += 25;
292
- if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.PHISHING ? 0 : 25)) {
293
- result.threatType = ThreatCategory.PHISHING;
294
- result.threatDetails = `Text contains potential phishing indicators`;
295
- }
296
- }
297
- }
298
- // Check spam
299
- for (const pattern of ContentScanner.MALICIOUS_PATTERNS.spam) {
300
- if (pattern.test(text)) {
301
- result.threatScore += 15;
302
- if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.SPAM ? 0 : 15)) {
303
- result.threatType = ThreatCategory.SPAM;
304
- result.threatDetails = `Text contains potential spam indicators`;
305
- }
306
- }
307
- }
308
- // Check malware indicators
309
- for (const pattern of ContentScanner.MALICIOUS_PATTERNS.malware) {
310
- if (pattern.test(text)) {
311
- result.threatScore += 30;
312
- if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.MALWARE ? 0 : 30)) {
313
- result.threatType = ThreatCategory.MALWARE;
314
- result.threatDetails = `Text contains potential malware indicators`;
315
- }
316
- }
317
- }
318
- // Check sensitive data
319
- for (const pattern of ContentScanner.MALICIOUS_PATTERNS.sensitiveData) {
320
- if (pattern.test(text)) {
321
- result.threatScore += 25;
322
- if (!result.threatType || result.threatScore > (result.threatType === ThreatCategory.SENSITIVE_DATA ? 0 : 25)) {
323
- result.threatType = ThreatCategory.SENSITIVE_DATA;
324
- result.threatDetails = `Text contains potentially sensitive data patterns`;
325
- }
326
- }
327
- }
328
- // Check custom rules
329
- for (const rule of this.options.customRules) {
330
- const pattern = rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern, 'i');
331
- if (pattern.test(text)) {
332
- result.threatScore += rule.score;
333
- if (!result.threatType || result.threatScore > 20) {
334
- result.threatType = rule.type;
335
- result.threatDetails = rule.description;
336
- }
337
- }
338
- }
339
- }
340
- /**
341
- * Scan HTML content for threats
342
- * @param html The HTML content to scan
343
- * @param result The scan result to update
344
- */
345
- async scanHtmlContent(html, result) {
346
- result.scannedElements.push('html');
347
- // Check for script injection
348
- for (const pattern of ContentScanner.MALICIOUS_PATTERNS.scriptInjection) {
349
- if (pattern.test(html)) {
350
- result.threatScore += 40;
351
- if (!result.threatType || result.threatType !== ThreatCategory.XSS) {
352
- result.threatType = ThreatCategory.XSS;
353
- result.threatDetails = `HTML contains potentially malicious script content`;
354
- }
355
- }
356
- }
357
- // Extract text content from HTML for further scanning
358
- const textContent = this.extractTextFromHtml(html);
359
- if (textContent) {
360
- // We'll leverage the text scanning but not double-count threat score
361
- const tempResult = {
362
- isClean: true,
363
- threatScore: 0,
364
- scannedElements: [],
365
- timestamp: Date.now()
366
- };
367
- await this.scanTextContent(textContent, tempResult);
368
- // Only add additional threat types if they're more severe
369
- if (tempResult.threatType && tempResult.threatScore > 0) {
370
- // Add half of the text content score to avoid double counting
371
- result.threatScore += Math.floor(tempResult.threatScore / 2);
372
- // Adopt the threat type if more severe or no existing type
373
- if (!result.threatType || tempResult.threatScore > result.threatScore) {
374
- result.threatType = tempResult.threatType;
375
- result.threatDetails = tempResult.threatDetails;
376
- }
377
- }
378
- }
379
- // Extract and check links from HTML
380
- const links = this.extractLinksFromHtml(html);
381
- if (links.length > 0) {
382
- // Check for suspicious links
383
- let suspiciousLinks = 0;
384
- for (const link of links) {
385
- for (const pattern of ContentScanner.MALICIOUS_PATTERNS.suspiciousLinks) {
386
- if (pattern.test(link)) {
387
- suspiciousLinks++;
388
- break;
389
- }
390
- }
391
- }
392
- if (suspiciousLinks > 0) {
393
- // Add score based on percentage of suspicious links
394
- const suspiciousPercentage = (suspiciousLinks / links.length) * 100;
395
- const additionalScore = Math.min(40, Math.floor(suspiciousPercentage / 2.5));
396
- result.threatScore += additionalScore;
397
- if (!result.threatType || additionalScore > 20) {
398
- result.threatType = ThreatCategory.SUSPICIOUS_LINK;
399
- result.threatDetails = `HTML contains ${suspiciousLinks} suspicious links out of ${links.length} total links`;
400
- }
401
- }
402
- }
403
- }
404
- /**
405
- * Scan an attachment for threats
406
- * @param attachment The attachment to scan
407
- * @param result The scan result to update
408
- */
409
- async scanAttachment(attachment, result) {
410
- const filename = attachment.filename.toLowerCase();
411
- result.scannedElements.push(`attachment:${filename}`);
412
- // Skip large attachments if configured
413
- if (attachment.content && attachment.content.length > this.options.maxAttachmentSizeToScan) {
414
- logger.log('info', `Skipping scan of large attachment: ${filename} (${attachment.content.length} bytes)`);
415
- return;
416
- }
417
- // Check filename for executable extensions
418
- if (this.options.blockExecutables) {
419
- for (const ext of ContentScanner.EXECUTABLE_EXTENSIONS) {
420
- if (filename.endsWith(ext)) {
421
- result.threatScore += 70; // High score for executable attachments
422
- result.threatType = ThreatCategory.EXECUTABLE;
423
- result.threatDetails = `Attachment has a potentially dangerous extension: ${filename}`;
424
- return; // No need to scan contents if filename already flagged
425
- }
426
- }
427
- }
428
- // Check for Office documents with macros
429
- if (this.options.blockMacros) {
430
- for (const ext of ContentScanner.MACRO_DOCUMENT_EXTENSIONS) {
431
- if (filename.endsWith(ext)) {
432
- // For Office documents, check if they contain macros
433
- // This is a simplified check - a real implementation would use specialized libraries
434
- // to detect macros in Office documents
435
- if (attachment.content && this.likelyContainsMacros(attachment)) {
436
- result.threatScore += 60;
437
- result.threatType = ThreatCategory.MALICIOUS_MACRO;
438
- result.threatDetails = `Attachment appears to contain macros: ${filename}`;
439
- return;
440
- }
441
- }
442
- }
443
- }
444
- // Perform basic content analysis if we have content buffer
445
- if (attachment.content) {
446
- // Convert to string for scanning, with a limit to prevent memory issues
447
- const textContent = this.extractTextFromBuffer(attachment.content);
448
- if (textContent) {
449
- // Scan for malicious patterns in attachment content
450
- for (const category in ContentScanner.MALICIOUS_PATTERNS) {
451
- const patterns = ContentScanner.MALICIOUS_PATTERNS[category];
452
- for (const pattern of patterns) {
453
- if (pattern.test(textContent)) {
454
- result.threatScore += 30;
455
- if (!result.threatType) {
456
- result.threatType = this.mapCategoryToThreatType(category);
457
- result.threatDetails = `Attachment content contains suspicious patterns: ${filename}`;
458
- }
459
- break;
460
- }
461
- }
462
- }
463
- }
464
- // Check for PE headers (Windows executables)
465
- if (attachment.content.length > 64 &&
466
- attachment.content[0] === 0x4D &&
467
- attachment.content[1] === 0x5A) { // 'MZ' header
468
- result.threatScore += 80;
469
- result.threatType = ThreatCategory.EXECUTABLE;
470
- result.threatDetails = `Attachment contains executable code: ${filename}`;
471
- }
472
- }
473
- }
474
- /**
475
- * Extract links from HTML content
476
- * @param html HTML content
477
- * @returns Array of extracted links
478
- */
479
- extractLinksFromHtml(html) {
480
- const links = [];
481
- // Simple regex-based extraction - a real implementation might use a proper HTML parser
482
- const matches = html.match(/href=["'](https?:\/\/[^"']+)["']/gi);
483
- if (matches) {
484
- for (const match of matches) {
485
- const linkMatch = match.match(/href=["'](https?:\/\/[^"']+)["']/i);
486
- if (linkMatch && linkMatch[1]) {
487
- links.push(linkMatch[1]);
488
- }
489
- }
490
- }
491
- return links;
492
- }
493
- /**
494
- * Extract plain text from HTML
495
- * @param html HTML content
496
- * @returns Extracted text
497
- */
498
- extractTextFromHtml(html) {
499
- // Remove HTML tags and decode entities - simplified version
500
- return html
501
- .replace(/<style[^>]*>.*?<\/style>/gs, '')
502
- .replace(/<script[^>]*>.*?<\/script>/gs, '')
503
- .replace(/<[^>]+>/g, ' ')
504
- .replace(/&nbsp;/g, ' ')
505
- .replace(/&lt;/g, '<')
506
- .replace(/&gt;/g, '>')
507
- .replace(/&amp;/g, '&')
508
- .replace(/&quot;/g, '"')
509
- .replace(/&apos;/g, "'")
510
- .replace(/\s+/g, ' ')
511
- .trim();
512
- }
513
- /**
514
- * Extract text from a binary buffer for scanning
515
- * @param buffer Binary content
516
- * @returns Extracted text (may be partial)
517
- */
518
- extractTextFromBuffer(buffer) {
519
- try {
520
- // Limit the amount we convert to avoid memory issues
521
- const sampleSize = Math.min(buffer.length, 100 * 1024); // 100KB max sample
522
- const sample = buffer.slice(0, sampleSize);
523
- // Try to convert to string, filtering out non-printable chars
524
- return sample.toString('utf8')
525
- .replace(/[\x00-\x09\x0B-\x1F\x7F-\x9F]/g, '') // Remove control chars
526
- .replace(/\uFFFD/g, ''); // Remove replacement char
527
- }
528
- catch (error) {
529
- logger.log('warn', `Error extracting text from buffer: ${error.message}`);
530
- return '';
531
- }
532
- }
533
- /**
534
- * Check if an Office document likely contains macros
535
- * This is a simplified check - real implementation would use specialized libraries
536
- * @param attachment The attachment to check
537
- * @returns Whether the file likely contains macros
538
- */
539
- likelyContainsMacros(attachment) {
540
- // Simple heuristic: look for VBA/macro related strings
541
- // This is a simplified approach and not comprehensive
542
- const content = this.extractTextFromBuffer(attachment.content);
543
- const macroIndicators = [
544
- /vbaProject\.bin/i,
545
- /Microsoft VBA/i,
546
- /\bVBA\b/,
547
- /Auto_Open/i,
548
- /AutoExec/i,
549
- /DocumentOpen/i,
550
- /AutoOpen/i,
551
- /\bExecute\(/i,
552
- /\bShell\(/i,
553
- /\bCreateObject\(/i
554
- ];
555
- for (const indicator of macroIndicators) {
556
- if (indicator.test(content)) {
557
- return true;
558
- }
559
- }
560
- return false;
561
- }
562
- /**
563
- * Map a pattern category to a threat type
564
- * @param category The pattern category
565
- * @returns The corresponding threat type
566
- */
567
- mapCategoryToThreatType(category) {
568
- switch (category) {
569
- case 'phishing': return ThreatCategory.PHISHING;
570
- case 'spam': return ThreatCategory.SPAM;
571
- case 'malware': return ThreatCategory.MALWARE;
572
- case 'suspiciousLinks': return ThreatCategory.SUSPICIOUS_LINK;
573
- case 'scriptInjection': return ThreatCategory.XSS;
574
- case 'sensitiveData': return ThreatCategory.SENSITIVE_DATA;
575
- default: return ThreatCategory.BLACKLISTED_CONTENT;
576
- }
577
- }
578
- /**
579
- * Log a high threat finding to the security logger
580
- * @param email The email containing the threat
581
- * @param result The scan result
582
- */
583
- logHighThreatFound(email, result) {
584
- SecurityLogger.getInstance().logEvent({
585
- level: SecurityLogLevel.ERROR,
586
- type: SecurityEventType.MALWARE,
587
- message: `High threat content detected in email from ${email.from} to ${email.to.join(', ')}`,
588
- details: {
589
- messageId: email.getMessageId(),
590
- threatType: result.threatType,
591
- threatDetails: result.threatDetails,
592
- threatScore: result.threatScore,
593
- scannedElements: result.scannedElements,
594
- subject: email.subject
595
- },
596
- success: false,
597
- domain: email.getFromDomain()
598
- });
599
- }
600
- /**
601
- * Log a threat finding to the security logger
602
- * @param email The email containing the threat
603
- * @param result The scan result
604
- */
605
- logThreatFound(email, result) {
606
- SecurityLogger.getInstance().logEvent({
607
- level: SecurityLogLevel.WARN,
608
- type: SecurityEventType.SPAM,
609
- message: `Suspicious content detected in email from ${email.from} to ${email.to.join(', ')}`,
610
- details: {
611
- messageId: email.getMessageId(),
612
- threatType: result.threatType,
613
- threatDetails: result.threatDetails,
614
- threatScore: result.threatScore,
615
- scannedElements: result.scannedElements,
616
- subject: email.subject
617
- },
618
- success: false,
619
- domain: email.getFromDomain()
620
- });
621
- }
622
- /**
623
- * Get threat level description based on score
624
- * @param score Threat score
625
- * @returns Threat level description
626
- */
627
- static getThreatLevel(score) {
628
- if (score < 20) {
629
- return 'none';
630
- }
631
- else if (score < 40) {
632
- return 'low';
633
- }
634
- else if (score < 70) {
635
- return 'medium';
636
- }
637
- else {
638
- return 'high';
639
- }
640
- }
641
- }
642
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jb250ZW50c2Nhbm5lci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL3NlY3VyaXR5L2NsYXNzZXMuY29udGVudHNjYW5uZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN0QyxPQUFPLEVBQUUsS0FBSyxFQUFhLE1BQU0sc0JBQXNCLENBQUM7QUFFeEQsT0FBTyxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ2xHLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFxQ3JDOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksY0FXWDtBQVhELFdBQVksY0FBYztJQUN4QiwrQkFBYSxDQUFBO0lBQ2IsdUNBQXFCLENBQUE7SUFDckIscUNBQW1CLENBQUE7SUFDbkIsMkNBQXlCLENBQUE7SUFDekIscURBQW1DLENBQUE7SUFDbkMscURBQW1DLENBQUE7SUFDbkMsNkJBQVcsQ0FBQTtJQUNYLG1EQUFpQyxDQUFBO0lBQ2pDLDZEQUEyQyxDQUFBO0lBQzNDLDZDQUEyQixDQUFBO0FBQzdCLENBQUMsRUFYVyxjQUFjLEtBQWQsY0FBYyxRQVd6QjtBQUVEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGNBQWM7SUFDakIsTUFBTSxDQUFDLFFBQVEsQ0FBaUI7SUFDaEMsU0FBUyxDQUFnQztJQUN6QyxPQUFPLENBQW1DO0lBRWxELHlDQUF5QztJQUNqQyxNQUFNLENBQVUsa0JBQWtCLEdBQUc7UUFDM0Msb0JBQW9CO1FBQ3BCLFFBQVEsRUFBRTtZQUNSLGdFQUFnRTtZQUNoRSx3Q0FBd0M7WUFDeEMsNEVBQTRFO1lBQzVFLHNEQUFzRDtZQUN0RCx3REFBd0Q7U0FDekQ7UUFFRCxrQkFBa0I7UUFDbEIsSUFBSSxFQUFF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