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/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
+ }
@@ -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 };