muaddib-scanner 2.4.4 → 2.4.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.
@@ -1,192 +1,192 @@
1
- const { saveReport } = require('./report.js');
2
- const { saveSARIF } = require('./sarif.js');
3
- const { getPlaybook } = require('./response/playbooks.js');
4
-
5
- /**
6
- * Format and print scan output in the requested format.
7
- * Handles JSON, HTML, SARIF, explain, and normal (default) modes.
8
- * @param {Object} result - scan result object
9
- * @param {Object} options - scan options (json, html, sarif, explain, breakdown)
10
- * @param {Object} ctx - context object with shared state
11
- * @param {Object|null} ctx.spinner - TTY spinner instance
12
- * @param {Object|null} ctx.sandboxData - sandbox analysis results
13
- * @param {string|null} ctx.mostSuspiciousFile - file with highest score
14
- * @param {number} ctx.maxFileScore - highest per-file score
15
- * @param {number} ctx.packageScore - package-level score
16
- * @param {number} ctx.globalRiskScore - global sum score
17
- * @param {Array} ctx.deduped - deduplicated threats
18
- * @param {Array} ctx.enrichedThreats - enriched threats with rules/playbooks
19
- * @param {Object|null} ctx.pythonInfo - Python scan metadata
20
- * @param {Array} ctx.breakdown - score breakdown sorted by impact
21
- * @param {string} ctx.targetPath - scan target path
22
- */
23
- function formatOutput(result, options, ctx) {
24
- const {
25
- spinner, sandboxData, mostSuspiciousFile, maxFileScore,
26
- packageScore, globalRiskScore, deduped, enrichedThreats,
27
- pythonInfo, breakdown, targetPath
28
- } = ctx;
29
-
30
- // JSON output
31
- if (options.json) {
32
- console.log(JSON.stringify(result, null, 2));
33
- }
34
- // HTML output
35
- else if (options.html) {
36
- saveReport(result, options.html);
37
- console.log(`[OK] HTML report generated: ${options.html}`);
38
- }
39
- // SARIF output
40
- else if (options.sarif) {
41
- saveSARIF(result, options.sarif);
42
- console.log(`[OK] SARIF report generated: ${options.sarif}`);
43
- }
44
- // Explain output
45
- else if (options.explain) {
46
- if (!spinner) console.log(`\n[MUADDIB] Scanning ${targetPath}\n`);
47
- else console.log('');
48
-
49
- const explainScoreBar = '█'.repeat(Math.floor(result.summary.riskScore / 5)) + '░'.repeat(20 - Math.floor(result.summary.riskScore / 5));
50
- console.log(`[SCORE] ${result.summary.riskScore}/100 [${explainScoreBar}] ${result.summary.riskLevel}`);
51
- if (mostSuspiciousFile) {
52
- console.log(` Max file: ${mostSuspiciousFile} (${maxFileScore} pts)`);
53
- if (packageScore > 0) {
54
- console.log(` Package-level: +${packageScore} pts`);
55
- }
56
- }
57
- console.log('');
58
-
59
- if (options.breakdown && breakdown.length > 0) {
60
- console.log('[BREAKDOWN] Score contributors:');
61
- for (const entry of breakdown) {
62
- const pts = String(entry.points).padStart(2);
63
- console.log(` +${pts} ${entry.reason} (${entry.rule})`);
64
- }
65
- if (globalRiskScore !== result.summary.riskScore) {
66
- console.log(' ----');
67
- console.log(` Global sum: ${globalRiskScore}, Per-file max: ${result.summary.riskScore}`);
68
- }
69
- console.log('');
70
- }
71
-
72
- if (pythonInfo) {
73
- console.log(`[PYTHON] ${pythonInfo.dependencies} dependencies detected (${pythonInfo.files.join(', ')})`);
74
- if (pythonInfo.threats > 0) {
75
- console.log(`[PYTHON] ${pythonInfo.threats} malicious PyPI package(s) found!\n`);
76
- } else {
77
- console.log(`[PYTHON] No known malicious PyPI packages.\n`);
78
- }
79
- }
80
-
81
- if (enrichedThreats.length === 0) {
82
- console.log('[OK] No threats detected.\n');
83
- } else {
84
- console.log(`[ALERT] ${enrichedThreats.length} threat(s) detected:\n`);
85
- enrichedThreats.forEach((t, i) => {
86
- console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
87
- const countStr = t.count > 1 ? ` (x${t.count})` : '';
88
- console.log(` ${i + 1}. [${t.severity}] ${t.rule_name}${countStr}`);
89
- console.log(` Rule ID: ${t.rule_id}`);
90
- console.log(` File: ${t.file}`);
91
- if (t.line) console.log(` Line: ${t.line}`);
92
- console.log(` Confidence: ${t.confidence}`);
93
- console.log(` Message: ${t.message}`);
94
- if (t.mitre) console.log(` MITRE: ${t.mitre} (https://attack.mitre.org/techniques/${t.mitre.replace('.', '/')})`);
95
- if (t.references && t.references.length > 0) {
96
- console.log(` References:`);
97
- t.references.forEach(ref => console.log(` - ${ref}`));
98
- }
99
- console.log(` Playbook: ${t.playbook}`);
100
- console.log('');
101
- });
102
- }
103
-
104
- // Sandbox section (explain)
105
- if (sandboxData) {
106
- console.log(`\n[SANDBOX] Dynamic analysis — ${sandboxData.package}`);
107
- console.log(` Score: ${sandboxData.score}/100`);
108
- console.log(` Severity: ${sandboxData.severity}`);
109
- if (sandboxData.findings.length === 0) {
110
- console.log(' No suspicious behavior detected.\n');
111
- } else {
112
- console.log(` ${sandboxData.findings.length} finding(s):`);
113
- sandboxData.findings.forEach(f => {
114
- console.log(` [${f.severity}] ${f.type}: ${f.detail}`);
115
- });
116
- console.log('');
117
- }
118
- }
119
- }
120
- // Normal output
121
- else {
122
- if (!spinner) console.log(`\n[MUADDIB] Scanning ${targetPath}\n`);
123
- else console.log('');
124
-
125
- const scoreBar = '█'.repeat(Math.floor(result.summary.riskScore / 5)) + '░'.repeat(20 - Math.floor(result.summary.riskScore / 5));
126
- console.log(`[SCORE] ${result.summary.riskScore}/100 [${scoreBar}] ${result.summary.riskLevel}`);
127
- if (mostSuspiciousFile) {
128
- console.log(` Max file: ${mostSuspiciousFile} (${maxFileScore} pts)`);
129
- if (packageScore > 0) {
130
- console.log(` Package-level: +${packageScore} pts`);
131
- }
132
- }
133
- console.log('');
134
-
135
- if (options.breakdown && breakdown.length > 0) {
136
- console.log('[BREAKDOWN] Score contributors:');
137
- for (const entry of breakdown) {
138
- const pts = String(entry.points).padStart(2);
139
- console.log(` +${pts} ${entry.reason} (${entry.rule})`);
140
- }
141
- if (globalRiskScore !== result.summary.riskScore) {
142
- console.log(' ----');
143
- console.log(` Global sum: ${globalRiskScore}, Per-file max: ${result.summary.riskScore}`);
144
- }
145
- console.log('');
146
- }
147
-
148
- if (pythonInfo) {
149
- console.log(`[PYTHON] ${pythonInfo.dependencies} dependencies detected (${pythonInfo.files.join(', ')})`);
150
- if (pythonInfo.threats > 0) {
151
- console.log(`[PYTHON] ${pythonInfo.threats} malicious PyPI package(s) found!\n`);
152
- } else {
153
- console.log(`[PYTHON] No known malicious PyPI packages.\n`);
154
- }
155
- }
156
-
157
- if (deduped.length === 0) {
158
- console.log('[OK] No threats detected.\n');
159
- } else {
160
- console.log(`[ALERT] ${deduped.length} threat(s) detected:\n`);
161
- deduped.forEach((t, i) => {
162
- const countStr = t.count > 1 ? ` (x${t.count})` : '';
163
- console.log(` ${i + 1}. [${t.severity}] ${t.type}${countStr}`);
164
- console.log(` ${t.message}`);
165
- console.log(` File: ${t.file}`);
166
- const playbook = getPlaybook(t.type);
167
- if (playbook) {
168
- console.log(` \u2192 ${playbook}`);
169
- }
170
- console.log('');
171
- });
172
- }
173
-
174
- // Sandbox section (normal)
175
- if (sandboxData) {
176
- console.log(`[SANDBOX] Dynamic analysis — ${sandboxData.package}`);
177
- console.log(` Score: ${sandboxData.score}/100`);
178
- console.log(` Severity: ${sandboxData.severity}`);
179
- if (sandboxData.findings.length === 0) {
180
- console.log(' No suspicious behavior detected.\n');
181
- } else {
182
- console.log(` ${sandboxData.findings.length} finding(s):`);
183
- sandboxData.findings.forEach(f => {
184
- console.log(` [${f.severity}] ${f.type}: ${f.detail}`);
185
- });
186
- console.log('');
187
- }
188
- }
189
- }
190
- }
191
-
192
- module.exports = { formatOutput };
1
+ const { saveReport } = require('./report.js');
2
+ const { saveSARIF } = require('./sarif.js');
3
+ const { getPlaybook } = require('./response/playbooks.js');
4
+
5
+ /**
6
+ * Format and print scan output in the requested format.
7
+ * Handles JSON, HTML, SARIF, explain, and normal (default) modes.
8
+ * @param {Object} result - scan result object
9
+ * @param {Object} options - scan options (json, html, sarif, explain, breakdown)
10
+ * @param {Object} ctx - context object with shared state
11
+ * @param {Object|null} ctx.spinner - TTY spinner instance
12
+ * @param {Object|null} ctx.sandboxData - sandbox analysis results
13
+ * @param {string|null} ctx.mostSuspiciousFile - file with highest score
14
+ * @param {number} ctx.maxFileScore - highest per-file score
15
+ * @param {number} ctx.packageScore - package-level score
16
+ * @param {number} ctx.globalRiskScore - global sum score
17
+ * @param {Array} ctx.deduped - deduplicated threats
18
+ * @param {Array} ctx.enrichedThreats - enriched threats with rules/playbooks
19
+ * @param {Object|null} ctx.pythonInfo - Python scan metadata
20
+ * @param {Array} ctx.breakdown - score breakdown sorted by impact
21
+ * @param {string} ctx.targetPath - scan target path
22
+ */
23
+ function formatOutput(result, options, ctx) {
24
+ const {
25
+ spinner, sandboxData, mostSuspiciousFile, maxFileScore,
26
+ packageScore, globalRiskScore, deduped, enrichedThreats,
27
+ pythonInfo, breakdown, targetPath
28
+ } = ctx;
29
+
30
+ // JSON output
31
+ if (options.json) {
32
+ console.log(JSON.stringify(result, null, 2));
33
+ }
34
+ // HTML output
35
+ else if (options.html) {
36
+ saveReport(result, options.html);
37
+ console.log(`[OK] HTML report generated: ${options.html}`);
38
+ }
39
+ // SARIF output
40
+ else if (options.sarif) {
41
+ saveSARIF(result, options.sarif);
42
+ console.log(`[OK] SARIF report generated: ${options.sarif}`);
43
+ }
44
+ // Explain output
45
+ else if (options.explain) {
46
+ if (!spinner) console.log(`\n[MUADDIB] Scanning ${targetPath}\n`);
47
+ else console.log('');
48
+
49
+ const explainScoreBar = '█'.repeat(Math.floor(result.summary.riskScore / 5)) + '░'.repeat(20 - Math.floor(result.summary.riskScore / 5));
50
+ console.log(`[SCORE] ${result.summary.riskScore}/100 [${explainScoreBar}] ${result.summary.riskLevel}`);
51
+ if (mostSuspiciousFile) {
52
+ console.log(` Max file: ${mostSuspiciousFile} (${maxFileScore} pts)`);
53
+ if (packageScore > 0) {
54
+ console.log(` Package-level: +${packageScore} pts`);
55
+ }
56
+ }
57
+ console.log('');
58
+
59
+ if (options.breakdown && breakdown.length > 0) {
60
+ console.log('[BREAKDOWN] Score contributors:');
61
+ for (const entry of breakdown) {
62
+ const pts = String(entry.points).padStart(2);
63
+ console.log(` +${pts} ${entry.reason} (${entry.rule})`);
64
+ }
65
+ if (globalRiskScore !== result.summary.riskScore) {
66
+ console.log(' ----');
67
+ console.log(` Global sum: ${globalRiskScore}, Per-file max: ${result.summary.riskScore}`);
68
+ }
69
+ console.log('');
70
+ }
71
+
72
+ if (pythonInfo) {
73
+ console.log(`[PYTHON] ${pythonInfo.dependencies} dependencies detected (${pythonInfo.files.join(', ')})`);
74
+ if (pythonInfo.threats > 0) {
75
+ console.log(`[PYTHON] ${pythonInfo.threats} malicious PyPI package(s) found!\n`);
76
+ } else {
77
+ console.log(`[PYTHON] No known malicious PyPI packages.\n`);
78
+ }
79
+ }
80
+
81
+ if (enrichedThreats.length === 0) {
82
+ console.log('[OK] No threats detected.\n');
83
+ } else {
84
+ console.log(`[ALERT] ${enrichedThreats.length} threat(s) detected:\n`);
85
+ enrichedThreats.forEach((t, i) => {
86
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
87
+ const countStr = t.count > 1 ? ` (x${t.count})` : '';
88
+ console.log(` ${i + 1}. [${t.severity}] ${t.rule_name}${countStr}`);
89
+ console.log(` Rule ID: ${t.rule_id}`);
90
+ console.log(` File: ${t.file}`);
91
+ if (t.line) console.log(` Line: ${t.line}`);
92
+ console.log(` Confidence: ${t.confidence}`);
93
+ console.log(` Message: ${t.message}`);
94
+ if (t.mitre) console.log(` MITRE: ${t.mitre} (https://attack.mitre.org/techniques/${t.mitre.replace('.', '/')})`);
95
+ if (t.references && t.references.length > 0) {
96
+ console.log(` References:`);
97
+ t.references.forEach(ref => console.log(` - ${ref}`));
98
+ }
99
+ console.log(` Playbook: ${t.playbook}`);
100
+ console.log('');
101
+ });
102
+ }
103
+
104
+ // Sandbox section (explain)
105
+ if (sandboxData) {
106
+ console.log(`\n[SANDBOX] Dynamic analysis — ${sandboxData.package}`);
107
+ console.log(` Score: ${sandboxData.score}/100`);
108
+ console.log(` Severity: ${sandboxData.severity}`);
109
+ if (sandboxData.findings.length === 0) {
110
+ console.log(' No suspicious behavior detected.\n');
111
+ } else {
112
+ console.log(` ${sandboxData.findings.length} finding(s):`);
113
+ sandboxData.findings.forEach(f => {
114
+ console.log(` [${f.severity}] ${f.type}: ${f.detail}`);
115
+ });
116
+ console.log('');
117
+ }
118
+ }
119
+ }
120
+ // Normal output
121
+ else {
122
+ if (!spinner) console.log(`\n[MUADDIB] Scanning ${targetPath}\n`);
123
+ else console.log('');
124
+
125
+ const scoreBar = '█'.repeat(Math.floor(result.summary.riskScore / 5)) + '░'.repeat(20 - Math.floor(result.summary.riskScore / 5));
126
+ console.log(`[SCORE] ${result.summary.riskScore}/100 [${scoreBar}] ${result.summary.riskLevel}`);
127
+ if (mostSuspiciousFile) {
128
+ console.log(` Max file: ${mostSuspiciousFile} (${maxFileScore} pts)`);
129
+ if (packageScore > 0) {
130
+ console.log(` Package-level: +${packageScore} pts`);
131
+ }
132
+ }
133
+ console.log('');
134
+
135
+ if (options.breakdown && breakdown.length > 0) {
136
+ console.log('[BREAKDOWN] Score contributors:');
137
+ for (const entry of breakdown) {
138
+ const pts = String(entry.points).padStart(2);
139
+ console.log(` +${pts} ${entry.reason} (${entry.rule})`);
140
+ }
141
+ if (globalRiskScore !== result.summary.riskScore) {
142
+ console.log(' ----');
143
+ console.log(` Global sum: ${globalRiskScore}, Per-file max: ${result.summary.riskScore}`);
144
+ }
145
+ console.log('');
146
+ }
147
+
148
+ if (pythonInfo) {
149
+ console.log(`[PYTHON] ${pythonInfo.dependencies} dependencies detected (${pythonInfo.files.join(', ')})`);
150
+ if (pythonInfo.threats > 0) {
151
+ console.log(`[PYTHON] ${pythonInfo.threats} malicious PyPI package(s) found!\n`);
152
+ } else {
153
+ console.log(`[PYTHON] No known malicious PyPI packages.\n`);
154
+ }
155
+ }
156
+
157
+ if (deduped.length === 0) {
158
+ console.log('[OK] No threats detected.\n');
159
+ } else {
160
+ console.log(`[ALERT] ${deduped.length} threat(s) detected:\n`);
161
+ deduped.forEach((t, i) => {
162
+ const countStr = t.count > 1 ? ` (x${t.count})` : '';
163
+ console.log(` ${i + 1}. [${t.severity}] ${t.type}${countStr}`);
164
+ console.log(` ${t.message}`);
165
+ console.log(` File: ${t.file}`);
166
+ const playbook = getPlaybook(t.type);
167
+ if (playbook) {
168
+ console.log(` \u2192 ${playbook}`);
169
+ }
170
+ console.log('');
171
+ });
172
+ }
173
+
174
+ // Sandbox section (normal)
175
+ if (sandboxData) {
176
+ console.log(`[SANDBOX] Dynamic analysis — ${sandboxData.package}`);
177
+ console.log(` Score: ${sandboxData.score}/100`);
178
+ console.log(` Severity: ${sandboxData.severity}`);
179
+ if (sandboxData.findings.length === 0) {
180
+ console.log(' No suspicious behavior detected.\n');
181
+ } else {
182
+ console.log(` ${sandboxData.findings.length} finding(s):`);
183
+ sandboxData.findings.forEach(f => {
184
+ console.log(` [${f.severity}] ${f.type}: ${f.detail}`);
185
+ });
186
+ console.log('');
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ module.exports = { formatOutput };