alert2action 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/parser.js ADDED
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Alert Parser Module
3
+ * Normalizes different alert formats into a standard structure
4
+ */
5
+
6
+ /**
7
+ * Parse and normalize an alert from various formats
8
+ * Supports: Generic, Splunk, Microsoft Sentinel, Elastic, CrowdStrike, etc.
9
+ */
10
+ function parseAlert(rawAlert) {
11
+ const normalized = {
12
+ // Basic info
13
+ id: null,
14
+ title: null,
15
+ description: null,
16
+ severity: 'medium',
17
+ timestamp: null,
18
+ source: null,
19
+
20
+ // Network indicators
21
+ sourceIp: null,
22
+ destIp: null,
23
+ sourcePort: null,
24
+ destPort: null,
25
+ protocol: null,
26
+
27
+ // Host indicators
28
+ hostname: null,
29
+ username: null,
30
+ domain: null,
31
+
32
+ // Process indicators
33
+ processName: null,
34
+ processPath: null,
35
+ processCommandLine: null,
36
+ parentProcess: null,
37
+ processId: null,
38
+
39
+ // File indicators
40
+ filePath: null,
41
+ fileHash: null,
42
+ fileName: null,
43
+
44
+ // Additional context
45
+ category: null,
46
+ action: null,
47
+ status: null,
48
+ eventType: null,
49
+ rawData: rawAlert,
50
+
51
+ // Extracted indicators for MITRE mapping
52
+ indicators: [],
53
+ keywords: []
54
+ };
55
+
56
+ // Try to extract from common field patterns
57
+ normalized.id = extractField(rawAlert, ['id', 'alert_id', 'event_id', 'alertId', 'eventId', '_id', 'uuid', 'host_id']);
58
+ normalized.title = extractField(rawAlert, ['title', 'name', 'alert_name', 'alertName', 'rule_name', 'ruleName', 'signature', 'event_type', 'eventType', 'rule_name', 'summary']);
59
+ normalized.description = extractField(rawAlert, ['description', 'message', 'msg', 'details', 'summary', 'reason', 'technique_details']);
60
+ normalized.severity = normalizeSeverity(extractField(rawAlert, ['severity', 'event_severity', 'priority', 'risk_level', 'riskLevel', 'threat_level', 'urgency', 'criticality']));
61
+ normalized.timestamp = extractField(rawAlert, ['timestamp', 'time', 'created_at', 'createdAt', 'detection_time', '@timestamp', 'event_time', 'eventTime', 'ingested_time']);
62
+ normalized.source = extractField(rawAlert, ['source', 'product', 'vendor', 'tool', 'detector', 'data_source', 'log_source']);
63
+
64
+ // Network
65
+ normalized.sourceIp = extractField(rawAlert, ['source_ip', 'sourceIp', 'src_ip', 'srcIp', 'src', 'attacker_ip', 'remote_ip', 'client_ip', 'ip_address']);
66
+ normalized.destIp = extractField(rawAlert, ['dest_ip', 'destIp', 'dst_ip', 'dstIp', 'dst', 'destination_ip', 'target_ip', 'server_ip', 'local_ip']);
67
+ normalized.sourcePort = extractField(rawAlert, ['source_port', 'sourcePort', 'src_port', 'srcPort']);
68
+ normalized.destPort = extractField(rawAlert, ['dest_port', 'destPort', 'dst_port', 'dstPort', 'port']);
69
+ normalized.protocol = extractField(rawAlert, ['protocol', 'proto', 'network_protocol']);
70
+
71
+ // Host
72
+ normalized.hostname = extractField(rawAlert, ['hostname', 'host', 'computer_name', 'computerName', 'machine', 'device_name', 'endpoint']);
73
+ normalized.username = extractField(rawAlert, ['username', 'user', 'user_name', 'userName', 'account', 'account_name', 'actor', 'original_user', 'escalated_user']);
74
+ normalized.domain = extractField(rawAlert, ['domain', 'domain_name', 'ad_domain']);
75
+
76
+ // Process
77
+ normalized.processName = extractField(rawAlert, ['process_name', 'processName', 'process', 'image', 'exe', 'executable', 'suspicious_binary']);
78
+ normalized.processPath = extractField(rawAlert, ['process_path', 'processPath', 'image_path', 'file_path', 'exe_path', 'service_binary']);
79
+ normalized.processCommandLine = extractField(rawAlert, ['command_line', 'commandLine', 'cmdline', 'cmd', 'process_command_line', 'command']);
80
+ normalized.parentProcess = extractField(rawAlert, ['parent_process', 'parentProcess', 'parent_image', 'parent']);
81
+ normalized.processId = extractField(rawAlert, ['process_id', 'processId', 'pid']);
82
+
83
+ // File
84
+ normalized.filePath = extractField(rawAlert, ['file_path', 'filePath', 'path', 'target_path', 'service_binary']);
85
+ normalized.fileHash = extractField(rawAlert, ['file_hash', 'fileHash', 'hash', 'md5', 'sha256', 'sha1']);
86
+ normalized.fileName = extractField(rawAlert, ['file_name', 'fileName', 'filename', 'file', 'suspicious_binary']);
87
+
88
+ // Context
89
+ normalized.category = extractField(rawAlert, ['category', 'type', 'alert_type', 'alertType', 'tactic', 'technique', 'method', 'vector']);
90
+ normalized.action = extractField(rawAlert, ['action', 'event_action', 'result', 'outcome', 'success']);
91
+ normalized.status = extractField(rawAlert, ['status', 'state', 'resolution', 'event_status', 'isolation_status']);
92
+ normalized.eventType = extractField(rawAlert, ['event_type', 'eventType', 'type', 'activity_type']);
93
+
94
+ // Extract keywords for MITRE mapping
95
+ normalized.keywords = extractKeywords(normalized);
96
+ normalized.indicators = extractIndicators(normalized);
97
+
98
+ return normalized;
99
+ }
100
+
101
+ /**
102
+ * Extract a field value from multiple possible field names
103
+ * Searches top-level, common nested paths, and deeply nested objects
104
+ */
105
+ function extractField(obj, fieldNames) {
106
+ for (const field of fieldNames) {
107
+ // Check top level
108
+ if (obj[field] !== undefined && obj[field] !== null && obj[field] !== '') {
109
+ // If it's an object (like host: {hostname: ...}), skip it
110
+ if (typeof obj[field] !== 'object') {
111
+ return obj[field];
112
+ }
113
+ }
114
+
115
+ // Check nested common paths (expanded for more formats)
116
+ const nestedPaths = [
117
+ 'data', 'event', 'alert', 'result', 'fields', 'source',
118
+ 'host', 'user', 'process', 'file', 'network', 'destination',
119
+ 'file_activity', 'persistence', 'detection', 'exploitation',
120
+ 'privilege_change', 'ioc', 'analyst_notes', 'hashes'
121
+ ];
122
+ for (const path of nestedPaths) {
123
+ if (obj[path] && typeof obj[path] === 'object') {
124
+ if (obj[path][field] !== undefined && obj[path][field] !== null && obj[path][field] !== '') {
125
+ if (typeof obj[path][field] !== 'object') {
126
+ return obj[path][field];
127
+ }
128
+ }
129
+ // Check one level deeper (e.g., host.os.name)
130
+ for (const subPath of Object.keys(obj[path])) {
131
+ if (typeof obj[path][subPath] === 'object' && obj[path][subPath]) {
132
+ if (obj[path][subPath][field] !== undefined) {
133
+ return obj[path][subPath][field];
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+
143
+ /**
144
+ * Normalize severity levels to standard values
145
+ */
146
+ function normalizeSeverity(severity) {
147
+ if (!severity) return 'medium';
148
+
149
+ const severityStr = String(severity).toLowerCase();
150
+
151
+ // Map various severity representations
152
+ const severityMap = {
153
+ 'critical': 'critical',
154
+ 'crit': 'critical',
155
+ '5': 'critical',
156
+ 'very high': 'critical',
157
+ 'emergency': 'critical',
158
+
159
+ 'high': 'high',
160
+ '4': 'high',
161
+ 'major': 'high',
162
+ 'severe': 'high',
163
+
164
+ 'medium': 'medium',
165
+ 'med': 'medium',
166
+ '3': 'medium',
167
+ 'moderate': 'medium',
168
+ 'warning': 'medium',
169
+
170
+ 'low': 'low',
171
+ '2': 'low',
172
+ 'minor': 'low',
173
+
174
+ 'informational': 'informational',
175
+ 'info': 'informational',
176
+ '1': 'informational',
177
+ 'notice': 'informational'
178
+ };
179
+
180
+ return severityMap[severityStr] || 'medium';
181
+ }
182
+
183
+ /**
184
+ * Extract keywords from alert for MITRE technique mapping
185
+ */
186
+ function extractKeywords(alert) {
187
+ const keywords = new Set();
188
+
189
+ // Extract from title and description
190
+ const textFields = [alert.title, alert.description, alert.category, alert.eventType, alert.action];
191
+ const keywordPatterns = [
192
+ // Authentication & Brute Force
193
+ 'brute force', 'login', 'authentication', 'credential', 'password', 'logon', 'failed login',
194
+ 'password spray', 'credential stuffing', 'account lockout',
195
+
196
+ // Execution
197
+ 'powershell', 'cmd', 'script', 'execution', 'wscript', 'cscript', 'macro', 'command line',
198
+ 'living off the land', 'lolbin', 'mshta', 'regsvr32', 'rundll32', 'certutil',
199
+
200
+ // Persistence
201
+ 'registry', 'scheduled task', 'service', 'startup', 'autorun', 'persistence',
202
+ 'cron', 'boot', 'wmi subscription', 'service creation',
203
+
204
+ // Privilege Escalation
205
+ 'privilege', 'escalation', 'admin', 'root', 'sudo', 'elevation', 'uac bypass',
206
+ 'token manipulation', 'impersonation', 'system', 'kernel exploit', 'setuid',
207
+
208
+ // Defense Evasion
209
+ 'obfuscation', 'encoded', 'base64', 'hidden', 'masquerading', 'disable', 'bypass',
210
+ 'process injection', 'dll injection', 'hollowing', 'av evasion', 'edr tampering',
211
+ 'log clearing', 'timestomp', 'signed binary', 'code signing',
212
+
213
+ // Credential Access
214
+ 'mimikatz', 'lsass', 'credential dump', 'hash', 'kerberos', 'ntlm', 'keylogger',
215
+ 'password store', 'browser credential', 'sam', 'ntds', 'dcSync',
216
+
217
+ // Discovery
218
+ 'enumeration', 'reconnaissance', 'scan', 'discovery', 'query', 'whoami',
219
+ 'network scan', 'port scan', 'service enumeration', 'account discovery',
220
+ 'domain enumeration', 'process discovery', 'system information',
221
+
222
+ // Lateral Movement
223
+ 'lateral', 'psexec', 'wmi', 'remote', 'smb', 'rdp', 'ssh', 'winrm',
224
+ 'pass the hash', 'pass the ticket', 'remote service', 'dcom',
225
+
226
+ // Collection
227
+ 'data collection', 'keylogger', 'screenshot', 'clipboard', 'archive', 'staging',
228
+
229
+ // Command and Control
230
+ 'c2', 'command and control', 'beacon', 'callback', 'beaconing',
231
+ 'dns tunneling', 'encrypted channel', 'proxy', 'covert channel',
232
+
233
+ // Exfiltration
234
+ 'exfiltration', 'data transfer', 'upload', 'data theft', 'large transfer',
235
+ 'cloud exfil', 'ftp', 'dns exfil',
236
+
237
+ // Impact
238
+ 'ransomware', 'encrypt', 'wipe', 'destruct', 'delete', 'defacement',
239
+ 'resource hijacking', 'cryptomining', 'dos', 'denial of service',
240
+
241
+ // Malware
242
+ 'malware', 'virus', 'trojan', 'worm', 'backdoor', 'rat', 'rootkit',
243
+ 'dropper', 'loader', 'implant',
244
+
245
+ // Network & Initial Access
246
+ 'phishing', 'spam', 'suspicious', 'anomaly', 'unusual', 'dns', 'http', 'https',
247
+ 'drive-by', 'exploit', 'vulnerability', 'cve', 'web shell',
248
+
249
+ // File & Payload
250
+ 'suspicious file', 'malicious', 'dropper', 'payload', 'attachment',
251
+
252
+ // Identity & Compliance
253
+ 'impossible travel', 'privileged login', 'service account', 'oauth',
254
+ 'policy violation', 'audit', 'compliance', 'configuration drift',
255
+
256
+ // Threat Intel
257
+ 'ioc', 'indicator', 'threat intel', 'behavior anomaly', 'threat hunt'
258
+ ];
259
+
260
+ for (const text of textFields) {
261
+ if (!text) continue;
262
+ const lowerText = String(text).toLowerCase();
263
+
264
+ for (const pattern of keywordPatterns) {
265
+ if (lowerText.includes(pattern)) {
266
+ keywords.add(pattern);
267
+ }
268
+ }
269
+ }
270
+
271
+ // Check command line for suspicious patterns
272
+ if (alert.processCommandLine) {
273
+ const cmd = alert.processCommandLine.toLowerCase();
274
+ if (cmd.includes('-enc') || cmd.includes('-encoded')) keywords.add('encoded');
275
+ if (cmd.includes('downloadstring') || cmd.includes('invoke-webrequest')) keywords.add('download');
276
+ if (cmd.includes('bypass') || cmd.includes('-ep bypass')) keywords.add('bypass');
277
+ if (cmd.includes('hidden') || cmd.includes('-w hidden')) keywords.add('hidden');
278
+ if (cmd.includes('invoke-mimikatz')) keywords.add('mimikatz');
279
+ }
280
+
281
+ // Check process names
282
+ if (alert.processName) {
283
+ const proc = alert.processName.toLowerCase();
284
+ const suspiciousProcs = ['powershell', 'cmd', 'wscript', 'cscript', 'mshta', 'regsvr32', 'rundll32', 'certutil', 'bitsadmin'];
285
+ for (const sp of suspiciousProcs) {
286
+ if (proc.includes(sp)) keywords.add(sp);
287
+ }
288
+ }
289
+
290
+ return Array.from(keywords);
291
+ }
292
+
293
+ /**
294
+ * Extract IOC indicators from alert
295
+ */
296
+ function extractIndicators(alert) {
297
+ const indicators = [];
298
+
299
+ if (alert.sourceIp) indicators.push({ type: 'ip', value: alert.sourceIp, context: 'source' });
300
+ if (alert.destIp) indicators.push({ type: 'ip', value: alert.destIp, context: 'destination' });
301
+ if (alert.fileHash) indicators.push({ type: 'hash', value: alert.fileHash, context: 'file' });
302
+ if (alert.hostname) indicators.push({ type: 'hostname', value: alert.hostname, context: 'endpoint' });
303
+ if (alert.username) indicators.push({ type: 'username', value: alert.username, context: 'actor' });
304
+ if (alert.processName) indicators.push({ type: 'process', value: alert.processName, context: 'execution' });
305
+
306
+ return indicators;
307
+ }
308
+
309
+ module.exports = { parseAlert };