@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.
- package/dist_serve/bundle.js +1 -1
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/dist_ts/00_commitinfo_data.d.ts +0 -8
- package/dist_ts/00_commitinfo_data.js +0 -9
- package/dist_ts/cache/classes.cache.cleaner.d.ts +0 -47
- package/dist_ts/cache/classes.cache.cleaner.js +0 -130
- package/dist_ts/cache/documents/classes.cached.email.d.ts +0 -125
- package/dist_ts/cache/documents/classes.cached.email.js +0 -337
- package/dist_ts/cache/documents/classes.cached.ip.reputation.d.ts +0 -119
- package/dist_ts/cache/documents/classes.cached.ip.reputation.js +0 -323
- package/dist_ts/cache/documents/index.d.ts +0 -2
- package/dist_ts/cache/documents/index.js +0 -3
- package/dist_ts/cache/index.d.ts +0 -4
- package/dist_ts/cache/index.js +0 -7
- package/dist_ts/classes.cert-provision-scheduler.d.ts +0 -53
- package/dist_ts/classes.cert-provision-scheduler.js +0 -110
- package/dist_ts/classes.dcrouter.d.ts +0 -337
- package/dist_ts/classes.dcrouter.js +0 -1405
- package/dist_ts/classes.storage-cert-manager.d.ts +0 -18
- package/dist_ts/classes.storage-cert-manager.js +0 -43
- package/dist_ts/config/classes.api-token-manager.d.ts +0 -46
- package/dist_ts/config/classes.api-token-manager.js +0 -150
- package/dist_ts/config/classes.route-config-manager.d.ts +0 -35
- package/dist_ts/config/classes.route-config-manager.js +0 -231
- package/dist_ts/config/index.d.ts +0 -3
- package/dist_ts/config/index.js +0 -5
- package/dist_ts/config/validator.d.ts +0 -104
- package/dist_ts/config/validator.js +0 -152
- package/dist_ts/errors/base.errors.d.ts +0 -224
- package/dist_ts/errors/base.errors.js +0 -320
- package/dist_ts/errors/error-handler.d.ts +0 -98
- package/dist_ts/errors/error-handler.js +0 -282
- package/dist_ts/errors/error.codes.d.ts +0 -115
- package/dist_ts/errors/error.codes.js +0 -136
- package/dist_ts/errors/index.d.ts +0 -54
- package/dist_ts/errors/index.js +0 -136
- package/dist_ts/errors/reputation.errors.d.ts +0 -183
- package/dist_ts/errors/reputation.errors.js +0 -292
- package/dist_ts/index.d.ts +0 -7
- package/dist_ts/index.js +0 -11
- package/dist_ts/logger.d.ts +0 -21
- package/dist_ts/logger.js +0 -81
- package/dist_ts/monitoring/classes.metricscache.d.ts +0 -32
- package/dist_ts/monitoring/classes.metricscache.js +0 -63
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +0 -178
- package/dist_ts/monitoring/classes.metricsmanager.js +0 -642
- package/dist_ts/monitoring/index.d.ts +0 -1
- package/dist_ts/monitoring/index.js +0 -2
- package/dist_ts/opsserver/classes.opsserver.d.ts +0 -37
- package/dist_ts/opsserver/classes.opsserver.js +0 -85
- package/dist_ts/opsserver/handlers/admin.handler.d.ts +0 -31
- package/dist_ts/opsserver/handlers/admin.handler.js +0 -180
- package/dist_ts/opsserver/handlers/api-token.handler.d.ts +0 -6
- package/dist_ts/opsserver/handlers/api-token.handler.js +0 -62
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +0 -32
- package/dist_ts/opsserver/handlers/certificate.handler.js +0 -421
- package/dist_ts/opsserver/handlers/config.handler.d.ts +0 -7
- package/dist_ts/opsserver/handlers/config.handler.js +0 -192
- package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +0 -30
- package/dist_ts/opsserver/handlers/email-ops.handler.js +0 -227
- package/dist_ts/opsserver/handlers/index.d.ts +0 -11
- package/dist_ts/opsserver/handlers/index.js +0 -12
- package/dist_ts/opsserver/handlers/logs.handler.d.ts +0 -25
- package/dist_ts/opsserver/handlers/logs.handler.js +0 -256
- package/dist_ts/opsserver/handlers/radius.handler.d.ts +0 -6
- package/dist_ts/opsserver/handlers/radius.handler.js +0 -295
- package/dist_ts/opsserver/handlers/remoteingress.handler.d.ts +0 -6
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +0 -156
- package/dist_ts/opsserver/handlers/route-management.handler.d.ts +0 -14
- package/dist_ts/opsserver/handlers/route-management.handler.js +0 -117
- package/dist_ts/opsserver/handlers/security.handler.d.ts +0 -9
- package/dist_ts/opsserver/handlers/security.handler.js +0 -231
- package/dist_ts/opsserver/handlers/stats.handler.d.ts +0 -11
- package/dist_ts/opsserver/handlers/stats.handler.js +0 -399
- package/dist_ts/opsserver/helpers/guards.d.ts +0 -27
- package/dist_ts/opsserver/helpers/guards.js +0 -43
- package/dist_ts/opsserver/index.d.ts +0 -1
- package/dist_ts/opsserver/index.js +0 -2
- package/dist_ts/paths.d.ts +0 -26
- package/dist_ts/paths.js +0 -45
- package/dist_ts/plugins.d.ts +0 -79
- package/dist_ts/plugins.js +0 -113
- package/dist_ts/radius/classes.accounting.manager.d.ts +0 -218
- package/dist_ts/radius/classes.accounting.manager.js +0 -417
- package/dist_ts/radius/classes.radius.server.d.ts +0 -171
- package/dist_ts/radius/classes.radius.server.js +0 -385
- package/dist_ts/radius/classes.vlan.manager.d.ts +0 -128
- package/dist_ts/radius/classes.vlan.manager.js +0 -279
- package/dist_ts/radius/index.d.ts +0 -13
- package/dist_ts/radius/index.js +0 -14
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +0 -82
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +0 -227
- package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +0 -59
- package/dist_ts/remoteingress/classes.tunnel-manager.js +0 -165
- package/dist_ts/remoteingress/index.d.ts +0 -2
- package/dist_ts/remoteingress/index.js +0 -3
- package/dist_ts/security/classes.contentscanner.d.ts +0 -164
- package/dist_ts/security/classes.contentscanner.js +0 -642
- package/dist_ts/security/classes.ipreputationchecker.d.ts +0 -160
- package/dist_ts/security/classes.ipreputationchecker.js +0 -537
- package/dist_ts/security/classes.securitylogger.d.ts +0 -144
- package/dist_ts/security/classes.securitylogger.js +0 -233
- package/dist_ts/security/index.d.ts +0 -3
- package/dist_ts/security/index.js +0 -4
- package/dist_ts/sms/classes.smsservice.d.ts +0 -15
- package/dist_ts/sms/classes.smsservice.js +0 -72
- package/dist_ts/sms/config/sms.config.d.ts +0 -93
- package/dist_ts/sms/config/sms.config.js +0 -2
- package/dist_ts/sms/config/sms.schema.d.ts +0 -5
- package/dist_ts/sms/config/sms.schema.js +0 -121
- package/dist_ts/sms/index.d.ts +0 -1
- package/dist_ts/sms/index.js +0 -2
- package/dist_ts/storage/classes.storagemanager.d.ts +0 -83
- package/dist_ts/storage/classes.storagemanager.js +0 -350
- package/dist_ts/storage/index.d.ts +0 -1
- 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(/ /g, ' ')
|
|
505
|
-
.replace(/</g, '<')
|
|
506
|
-
.replace(/>/g, '>')
|
|
507
|
-
.replace(/&/g, '&')
|
|
508
|
-
.replace(/"/g, '"')
|
|
509
|
-
.replace(/'/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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
|