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/README.md +203 -0
- package/alert2action.cmd +2 -0
- package/bin/alert2action.js +77 -0
- package/examples/brute-force-alert.json +33 -0
- package/examples/credential-dump-alert.json +32 -0
- package/examples/lateral-movement-alert.json +29 -0
- package/examples/malware-alert-2.json +30 -0
- package/examples/malware-alert.json +35 -0
- package/examples/phishing-alert.json +28 -0
- package/examples/privesc-alert.json +112 -0
- package/examples/soc-test-alert.json +80 -0
- package/package.json +48 -0
- package/src/formatter.js +267 -0
- package/src/guide-generator.js +478 -0
- package/src/index.js +28 -0
- package/src/mitre.js +837 -0
- package/src/parser.js +309 -0
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "alert2action",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SOC Alert to Investigation Guide CLI - Transform security alerts into actionable investigation playbooks with MITRE ATT&CK mapping",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"alert2action": "./bin/alert2action.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/alert2action.js",
|
|
11
|
+
"build": "echo 'No build step required - pure JavaScript CLI' && node -c bin/alert2action.js && node -c src/index.js",
|
|
12
|
+
"test": "node bin/alert2action.js examples/brute-force-alert.json",
|
|
13
|
+
"test:all": "node bin/alert2action.js examples/brute-force-alert.json && node bin/alert2action.js examples/malware-alert.json && node bin/alert2action.js examples/phishing-alert.json",
|
|
14
|
+
"lint": "node --check bin/alert2action.js src/*.js",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/notsointresting/alert2action.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/notsointresting/alert2action#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/notsointresting/alert2action/issues"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"soc",
|
|
27
|
+
"security",
|
|
28
|
+
"mitre-attack",
|
|
29
|
+
"incident-response",
|
|
30
|
+
"threat-detection",
|
|
31
|
+
"investigation",
|
|
32
|
+
"cli",
|
|
33
|
+
"cybersecurity",
|
|
34
|
+
"siem",
|
|
35
|
+
"playbook",
|
|
36
|
+
"alert",
|
|
37
|
+
"blue-team"
|
|
38
|
+
],
|
|
39
|
+
"author": "notsointresting",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"chalk": "^4.1.2",
|
|
43
|
+
"commander": "^11.1.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=14.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/formatter.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Formatter
|
|
3
|
+
* Formats investigation guides for CLI display with colors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format the investigation guide for output
|
|
10
|
+
*/
|
|
11
|
+
function formatOutput(guide, options = {}) {
|
|
12
|
+
const format = options.output || 'text';
|
|
13
|
+
|
|
14
|
+
switch (format) {
|
|
15
|
+
case 'json':
|
|
16
|
+
return JSON.stringify(guide, null, 2);
|
|
17
|
+
case 'markdown':
|
|
18
|
+
return formatMarkdown(guide);
|
|
19
|
+
case 'text':
|
|
20
|
+
default:
|
|
21
|
+
return formatText(guide, options);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Format as colored CLI text output
|
|
27
|
+
*/
|
|
28
|
+
function formatText(guide, options) {
|
|
29
|
+
const lines = [];
|
|
30
|
+
const divider = chalk.gray('─'.repeat(65));
|
|
31
|
+
const sectionDivider = chalk.cyan('━'.repeat(65));
|
|
32
|
+
|
|
33
|
+
// Header with severity coloring
|
|
34
|
+
const severityColors = {
|
|
35
|
+
critical: chalk.bgRed.white.bold,
|
|
36
|
+
high: chalk.bgYellow.black.bold,
|
|
37
|
+
medium: chalk.bgBlue.white.bold,
|
|
38
|
+
low: chalk.bgGreen.white.bold,
|
|
39
|
+
informational: chalk.bgGray.white
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const severityColor = severityColors[guide.severity] || severityColors.medium;
|
|
43
|
+
|
|
44
|
+
lines.push('');
|
|
45
|
+
lines.push(severityColor(` ${guide.severity.toUpperCase()} SEVERITY `));
|
|
46
|
+
lines.push(chalk.bold.white(`📋 ${guide.alertTitle}`));
|
|
47
|
+
lines.push(chalk.gray(` Timestamp: ${guide.timestamp}`));
|
|
48
|
+
lines.push('');
|
|
49
|
+
lines.push(sectionDivider);
|
|
50
|
+
|
|
51
|
+
// Section 1: What Happened
|
|
52
|
+
lines.push('');
|
|
53
|
+
lines.push(chalk.bold.cyan('📖 WHAT HAPPENED'));
|
|
54
|
+
lines.push(divider);
|
|
55
|
+
for (const item of guide.whatHappened) {
|
|
56
|
+
// Parse markdown-like formatting
|
|
57
|
+
const formatted = item
|
|
58
|
+
.replace(/\*\*(.*?)\*\*/g, (_, text) => chalk.bold(text))
|
|
59
|
+
.replace(/`(.*?)`/g, (_, text) => chalk.yellow(text));
|
|
60
|
+
lines.push(` ${formatted}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('');
|
|
63
|
+
|
|
64
|
+
// Section 2: MITRE ATT&CK Mapping
|
|
65
|
+
lines.push(chalk.bold.magenta('🎯 MITRE ATT&CK MAPPING'));
|
|
66
|
+
lines.push(divider);
|
|
67
|
+
for (const mapping of guide.mitreMapping) {
|
|
68
|
+
const confidenceIcon = mapping.confidence === 'high' ? '🔴' : mapping.confidence === 'medium' ? '🟠' : '🟡';
|
|
69
|
+
lines.push(` ${confidenceIcon} ${chalk.bold.yellow(mapping.id)} - ${chalk.bold(mapping.name)}`);
|
|
70
|
+
lines.push(` ${chalk.gray('Tactic:')} ${mapping.tactic}`);
|
|
71
|
+
lines.push(` ${chalk.gray('Confidence:')} ${mapping.confidence}`);
|
|
72
|
+
if (mapping.description) {
|
|
73
|
+
lines.push(` ${chalk.gray(mapping.description)}`);
|
|
74
|
+
}
|
|
75
|
+
if (mapping.url) {
|
|
76
|
+
lines.push(` ${chalk.blue.underline(mapping.url)}`);
|
|
77
|
+
}
|
|
78
|
+
if (mapping.matchedKeywords && mapping.matchedKeywords.length > 0) {
|
|
79
|
+
lines.push(` ${chalk.gray('Matched:')} ${mapping.matchedKeywords.join(', ')}`);
|
|
80
|
+
}
|
|
81
|
+
lines.push('');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Section 3: Logs to Check
|
|
85
|
+
lines.push(chalk.bold.green('📁 LOGS TO CHECK'));
|
|
86
|
+
lines.push(divider);
|
|
87
|
+
for (const log of guide.logsToCheck) {
|
|
88
|
+
lines.push(` ${chalk.green('•')} ${log}`);
|
|
89
|
+
}
|
|
90
|
+
lines.push('');
|
|
91
|
+
|
|
92
|
+
// Section 4: Commands to Run
|
|
93
|
+
lines.push(chalk.bold.yellow('⚡ COMMANDS TO RUN'));
|
|
94
|
+
lines.push(divider);
|
|
95
|
+
|
|
96
|
+
if (guide.commands.windows.length > 0) {
|
|
97
|
+
lines.push(` ${chalk.bold.blue('Windows (PowerShell):')}`);
|
|
98
|
+
for (const cmd of guide.commands.windows) {
|
|
99
|
+
if (cmd.startsWith('#')) {
|
|
100
|
+
lines.push(` ${chalk.gray(cmd)}`);
|
|
101
|
+
} else {
|
|
102
|
+
lines.push(` ${chalk.cyan('$')} ${chalk.white(cmd)}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
lines.push('');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (guide.commands.linux.length > 0) {
|
|
109
|
+
const linuxHeader = guide.commands.linuxNote
|
|
110
|
+
? `Linux/MacOS ${chalk.gray.italic(guide.commands.linuxNote)}`
|
|
111
|
+
: 'Linux/MacOS:';
|
|
112
|
+
lines.push(` ${chalk.bold.magenta(linuxHeader)}`);
|
|
113
|
+
for (const cmd of guide.commands.linux) {
|
|
114
|
+
if (cmd.startsWith('#')) {
|
|
115
|
+
lines.push(` ${chalk.gray(cmd)}`);
|
|
116
|
+
} else {
|
|
117
|
+
lines.push(` ${chalk.green('$')} ${chalk.white(cmd)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
lines.push('');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Section 5: Containment Steps
|
|
124
|
+
lines.push(chalk.bold.red('🛡️ CONTAINMENT STEPS'));
|
|
125
|
+
lines.push(divider);
|
|
126
|
+
for (const phase of guide.containment) {
|
|
127
|
+
lines.push(` ${chalk.bold.underline(phase.phase)}`);
|
|
128
|
+
for (let i = 0; i < phase.actions.length; i++) {
|
|
129
|
+
const icon = phase.phase.includes('Immediate') ? '🚨' : phase.phase.includes('Short') ? '⚠️' : '📋';
|
|
130
|
+
lines.push(` ${icon} ${i + 1}. ${phase.actions[i]}`);
|
|
131
|
+
}
|
|
132
|
+
lines.push('');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Section 6: False Positive Hints
|
|
136
|
+
lines.push(chalk.bold.white('🤔 FALSE POSITIVE HINTS'));
|
|
137
|
+
lines.push(divider);
|
|
138
|
+
let inQuestions = false;
|
|
139
|
+
for (const hint of guide.falsePositives) {
|
|
140
|
+
if (hint.includes('---')) {
|
|
141
|
+
inQuestions = true;
|
|
142
|
+
lines.push(` ${chalk.gray.italic(hint.replace(/---/g, ''))}`);
|
|
143
|
+
} else if (inQuestions) {
|
|
144
|
+
lines.push(` ${chalk.cyan('?')} ${hint}`);
|
|
145
|
+
} else {
|
|
146
|
+
lines.push(` ${chalk.yellow('•')} ${hint}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
lines.push('');
|
|
150
|
+
|
|
151
|
+
// Indicators of Compromise
|
|
152
|
+
if (guide.indicators && guide.indicators.length > 0) {
|
|
153
|
+
lines.push(chalk.bold.red('🔍 INDICATORS OF COMPROMISE (IOCs)'));
|
|
154
|
+
lines.push(divider);
|
|
155
|
+
for (const ioc of guide.indicators) {
|
|
156
|
+
lines.push(` ${chalk.gray(`[${ioc.type.toUpperCase()}]`)} ${chalk.white(ioc.value)} ${chalk.gray(`(${ioc.context})`)}`);
|
|
157
|
+
}
|
|
158
|
+
lines.push('');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Footer
|
|
162
|
+
lines.push(sectionDivider);
|
|
163
|
+
lines.push(chalk.gray.italic(' Generated by alert2action | Always verify findings before taking action'));
|
|
164
|
+
lines.push('');
|
|
165
|
+
|
|
166
|
+
return lines.join('\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Format as Markdown (for documentation/ticketing)
|
|
171
|
+
*/
|
|
172
|
+
function formatMarkdown(guide) {
|
|
173
|
+
const lines = [];
|
|
174
|
+
|
|
175
|
+
lines.push(`# Investigation Guide: ${guide.alertTitle}`);
|
|
176
|
+
lines.push('');
|
|
177
|
+
lines.push(`**Severity:** ${guide.severity.toUpperCase()}`);
|
|
178
|
+
lines.push(`**Timestamp:** ${guide.timestamp}`);
|
|
179
|
+
lines.push('');
|
|
180
|
+
|
|
181
|
+
// What Happened
|
|
182
|
+
lines.push('## 📖 What Happened');
|
|
183
|
+
lines.push('');
|
|
184
|
+
for (const item of guide.whatHappened) {
|
|
185
|
+
lines.push(`- ${item}`);
|
|
186
|
+
}
|
|
187
|
+
lines.push('');
|
|
188
|
+
|
|
189
|
+
// MITRE Mapping
|
|
190
|
+
lines.push('## 🎯 MITRE ATT&CK Mapping');
|
|
191
|
+
lines.push('');
|
|
192
|
+
lines.push('| Technique ID | Name | Tactic | Confidence |');
|
|
193
|
+
lines.push('|-------------|------|--------|------------|');
|
|
194
|
+
for (const mapping of guide.mitreMapping) {
|
|
195
|
+
lines.push(`| [${mapping.id}](${mapping.url || '#'}) | ${mapping.name} | ${mapping.tactic} | ${mapping.confidence} |`);
|
|
196
|
+
}
|
|
197
|
+
lines.push('');
|
|
198
|
+
|
|
199
|
+
// Logs to Check
|
|
200
|
+
lines.push('## 📁 Logs to Check');
|
|
201
|
+
lines.push('');
|
|
202
|
+
for (const log of guide.logsToCheck) {
|
|
203
|
+
lines.push(`- [ ] ${log}`);
|
|
204
|
+
}
|
|
205
|
+
lines.push('');
|
|
206
|
+
|
|
207
|
+
// Commands
|
|
208
|
+
lines.push('## ⚡ Commands to Run');
|
|
209
|
+
lines.push('');
|
|
210
|
+
if (guide.commands.windows.length > 0) {
|
|
211
|
+
lines.push('### Windows (PowerShell)');
|
|
212
|
+
lines.push('```powershell');
|
|
213
|
+
lines.push(guide.commands.windows.join('\n'));
|
|
214
|
+
lines.push('```');
|
|
215
|
+
lines.push('');
|
|
216
|
+
}
|
|
217
|
+
if (guide.commands.linux.length > 0) {
|
|
218
|
+
const linuxHeader = guide.commands.linuxNote
|
|
219
|
+
? `### Linux/MacOS ${guide.commands.linuxNote}`
|
|
220
|
+
: '### Linux/MacOS';
|
|
221
|
+
lines.push(linuxHeader);
|
|
222
|
+
lines.push('```bash');
|
|
223
|
+
lines.push(guide.commands.linux.join('\n'));
|
|
224
|
+
lines.push('```');
|
|
225
|
+
lines.push('');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Containment
|
|
229
|
+
lines.push('## 🛡️ Containment Steps');
|
|
230
|
+
lines.push('');
|
|
231
|
+
for (const phase of guide.containment) {
|
|
232
|
+
lines.push(`### ${phase.phase}`);
|
|
233
|
+
for (let i = 0; i < phase.actions.length; i++) {
|
|
234
|
+
lines.push(`${i + 1}. ${phase.actions[i]}`);
|
|
235
|
+
}
|
|
236
|
+
lines.push('');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// False Positives
|
|
240
|
+
lines.push('## 🤔 False Positive Hints');
|
|
241
|
+
lines.push('');
|
|
242
|
+
for (const hint of guide.falsePositives) {
|
|
243
|
+
if (!hint.includes('---')) {
|
|
244
|
+
lines.push(`- ${hint}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
lines.push('');
|
|
248
|
+
|
|
249
|
+
// IOCs
|
|
250
|
+
if (guide.indicators && guide.indicators.length > 0) {
|
|
251
|
+
lines.push('## 🔍 Indicators of Compromise');
|
|
252
|
+
lines.push('');
|
|
253
|
+
lines.push('| Type | Value | Context |');
|
|
254
|
+
lines.push('|------|-------|---------|');
|
|
255
|
+
for (const ioc of guide.indicators) {
|
|
256
|
+
lines.push(`| ${ioc.type} | \`${ioc.value}\` | ${ioc.context} |`);
|
|
257
|
+
}
|
|
258
|
+
lines.push('');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
lines.push('---');
|
|
262
|
+
lines.push('*Generated by alert2action*');
|
|
263
|
+
|
|
264
|
+
return lines.join('\n');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = { formatOutput };
|