@serve.zone/dcrouter 5.0.2 → 5.0.4

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