muaddib-scanner 2.3.3 → 2.4.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/report.js CHANGED
@@ -1,230 +1,230 @@
1
- const fs = require('fs');
2
- const { escapeHtml } = require('./utils.js');
3
-
4
- function generateHTML(results) {
5
- const { target, timestamp, threats, summary } = results;
6
-
7
- const threatRows = threats.map(t => `
8
- <tr class="${escapeHtml(t.severity).toLowerCase()}">
9
- <td>${escapeHtml(t.severity)}</td>
10
- <td>${escapeHtml(t.type)}</td>
11
- <td>${escapeHtml(t.message)}</td>
12
- <td>${escapeHtml(t.file)}</td>
13
- <td>${escapeHtml(t.playbook)}</td>
14
- </tr>
15
- `).join('');
16
-
17
- return `<!DOCTYPE html>
18
- <html lang="en">
19
- <head>
20
- <meta charset="UTF-8">
21
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
- <title>MUAD'DIB - Scan Report</title>
23
- <style>
24
- body {
25
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
26
- background: #1a1a2e;
27
- color: #eee;
28
- margin: 0;
29
- padding: 20px;
30
- }
31
- .container {
32
- max-width: 1200px;
33
- margin: 0 auto;
34
- }
35
- h1 {
36
- color: #e94560;
37
- border-bottom: 2px solid #e94560;
38
- padding-bottom: 10px;
39
- }
40
- .summary {
41
- display: flex;
42
- gap: 20px;
43
- margin: 20px 0;
44
- }
45
- .summary-card {
46
- background: #16213e;
47
- padding: 20px;
48
- border-radius: 8px;
49
- flex: 1;
50
- }
51
- .summary-card h3 {
52
- margin: 0 0 10px 0;
53
- color: #888;
54
- font-size: 14px;
55
- }
56
- .summary-card .value {
57
- font-size: 32px;
58
- font-weight: bold;
59
- }
60
- .critical .value { color: #e94560; }
61
- .high .value { color: #ff6b35; }
62
- .medium .value { color: #f9c74f; }
63
- .total .value { color: #4ecdc4; }
64
- table {
65
- width: 100%;
66
- border-collapse: collapse;
67
- margin-top: 20px;
68
- }
69
- th, td {
70
- padding: 12px;
71
- text-align: left;
72
- border-bottom: 1px solid #333;
73
- }
74
- th {
75
- background: #16213e;
76
- color: #e94560;
77
- }
78
- tr.critical { background: rgba(233, 69, 96, 0.2); }
79
- tr.high { background: rgba(255, 107, 53, 0.2); }
80
- tr.medium { background: rgba(249, 199, 79, 0.1); }
81
- .meta {
82
- color: #666;
83
- font-size: 12px;
84
- margin-top: 40px;
85
- }
86
- .ok {
87
- background: #16213e;
88
- padding: 40px;
89
- border-radius: 8px;
90
- text-align: center;
91
- color: #4ecdc4;
92
- font-size: 24px;
93
- }
94
- .sandbox-section {
95
- background: #16213e;
96
- padding: 20px;
97
- border-radius: 8px;
98
- margin-top: 30px;
99
- border-left: 4px solid #9b59b6;
100
- }
101
- .sandbox-section h2 {
102
- color: #9b59b6;
103
- margin-top: 0;
104
- }
105
- .sandbox-meta {
106
- display: flex;
107
- gap: 30px;
108
- margin-bottom: 15px;
109
- }
110
- .sandbox-meta span {
111
- color: #aaa;
112
- }
113
- .sandbox-finding {
114
- padding: 8px 12px;
115
- margin: 4px 0;
116
- border-radius: 4px;
117
- background: rgba(155, 89, 182, 0.1);
118
- }
119
- </style>
120
- </head>
121
- <body>
122
- <div class="container">
123
- <h1>MUAD'DIB - Scan Report</h1>
124
-
125
- <div class="summary">
126
- <div class="summary-card total">
127
- <h3>TOTAL</h3>
128
- <div class="value">${escapeHtml(summary.total)}</div>
129
- </div>
130
- <div class="summary-card critical">
131
- <h3>CRITICAL</h3>
132
- <div class="value">${escapeHtml(summary.critical)}</div>
133
- </div>
134
- <div class="summary-card high">
135
- <h3>HIGH</h3>
136
- <div class="value">${escapeHtml(summary.high)}</div>
137
- </div>
138
- <div class="summary-card medium">
139
- <h3>MEDIUM</h3>
140
- <div class="value">${escapeHtml(summary.medium)}</div>
141
- </div>
142
- </div>
143
-
144
- ${threats.length > 0 ? `
145
- <table>
146
- <thead>
147
- <tr>
148
- <th>Severity</th>
149
- <th>Type</th>
150
- <th>Message</th>
151
- <th>File</th>
152
- <th>Recommendation</th>
153
- </tr>
154
- </thead>
155
- <tbody>
156
- ${threatRows}
157
- </tbody>
158
- </table>
159
- ` : '<div class="ok">No threats detected</div>'}
160
-
161
- ${results.sandbox ? `
162
- <div class="sandbox-section">
163
- <h2>[SANDBOX] Dynamic Analysis</h2>
164
- <div class="sandbox-meta">
165
- <span>Package: <strong>${escapeHtml(results.sandbox.package)}</strong></span>
166
- <span>Score: <strong>${escapeHtml(String(results.sandbox.score))}/100</strong></span>
167
- <span>Severity: <strong>${escapeHtml(results.sandbox.severity)}</strong></span>
168
- </div>
169
- ${results.sandbox.findings.length === 0
170
- ? '<p style="color: #4ecdc4;">No suspicious behavior detected.</p>'
171
- : results.sandbox.findings.map(f => `
172
- <div class="sandbox-finding">
173
- <strong>[${escapeHtml(f.severity)}]</strong> ${escapeHtml(f.type)}: ${escapeHtml(f.detail)}
174
- </div>
175
- `).join('')}
176
-
177
- ${results.sandbox.network ? `
178
- <h3 style="color: #9b59b6; margin-top: 20px;">Network Activity</h3>
179
- ${(results.sandbox.network.dns_resolutions || []).length > 0 ? `
180
- <h4 style="color: #aaa;">DNS Resolutions</h4>
181
- ${results.sandbox.network.dns_resolutions.map(r => `
182
- <div class="sandbox-finding">${escapeHtml(r.domain)} &rarr; ${escapeHtml(r.ip)}</div>
183
- `).join('')}
184
- ` : ''}
185
- ${(results.sandbox.network.http_requests || []).length > 0 ? `
186
- <h4 style="color: #aaa;">HTTP Requests</h4>
187
- ${results.sandbox.network.http_requests.map(r => `
188
- <div class="sandbox-finding">${escapeHtml(r.method)} ${escapeHtml(r.host)}${escapeHtml(r.path)}</div>
189
- `).join('')}
190
- ` : ''}
191
- ${(results.sandbox.network.tls_connections || []).length > 0 ? `
192
- <h4 style="color: #aaa;">TLS Connections</h4>
193
- ${results.sandbox.network.tls_connections.map(t => `
194
- <div class="sandbox-finding">${escapeHtml(t.domain)} (${escapeHtml(t.ip)}:${escapeHtml(String(t.port))})</div>
195
- `).join('')}
196
- ` : ''}
197
- ${(results.sandbox.network.blocked_connections || []).length > 0 ? `
198
- <h4 style="color: #e94560;">Blocked Connections</h4>
199
- ${results.sandbox.network.blocked_connections.map(b => `
200
- <div class="sandbox-finding" style="background: rgba(233,69,96,0.2);">[BLOCKED] ${escapeHtml(b.ip)}:${escapeHtml(String(b.port))}</div>
201
- `).join('')}
202
- ` : ''}
203
- ` : ''}
204
- </div>
205
- ` : ''}
206
-
207
- <div class="meta">
208
- <p>Target: ${escapeHtml(target)}</p>
209
- <p>Date: ${escapeHtml(timestamp)}</p>
210
- <p>Generated by MUAD'DIB</p>
211
- </div>
212
- </div>
213
- </body>
214
- </html>`;
215
- }
216
-
217
- function saveReport(results, outputPath) {
218
- if (!outputPath || typeof outputPath !== 'string') {
219
- throw new Error('Invalid output path for HTML report');
220
- }
221
- const html = generateHTML(results);
222
- try {
223
- fs.writeFileSync(outputPath, html);
224
- } catch (e) {
225
- throw new Error(`Failed to write HTML report to ${outputPath}: ${e.message}`);
226
- }
227
- return outputPath;
228
- }
229
-
230
- module.exports = { generateHTML, saveReport };
1
+ const fs = require('fs');
2
+ const { escapeHtml } = require('./utils.js');
3
+
4
+ function generateHTML(results) {
5
+ const { target, timestamp, threats, summary } = results;
6
+
7
+ const threatRows = threats.map(t => `
8
+ <tr class="${escapeHtml(t.severity).toLowerCase()}">
9
+ <td>${escapeHtml(t.severity)}</td>
10
+ <td>${escapeHtml(t.type)}</td>
11
+ <td>${escapeHtml(t.message)}</td>
12
+ <td>${escapeHtml(t.file)}</td>
13
+ <td>${escapeHtml(t.playbook)}</td>
14
+ </tr>
15
+ `).join('');
16
+
17
+ return `<!DOCTYPE html>
18
+ <html lang="en">
19
+ <head>
20
+ <meta charset="UTF-8">
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
+ <title>MUAD'DIB - Scan Report</title>
23
+ <style>
24
+ body {
25
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
26
+ background: #1a1a2e;
27
+ color: #eee;
28
+ margin: 0;
29
+ padding: 20px;
30
+ }
31
+ .container {
32
+ max-width: 1200px;
33
+ margin: 0 auto;
34
+ }
35
+ h1 {
36
+ color: #e94560;
37
+ border-bottom: 2px solid #e94560;
38
+ padding-bottom: 10px;
39
+ }
40
+ .summary {
41
+ display: flex;
42
+ gap: 20px;
43
+ margin: 20px 0;
44
+ }
45
+ .summary-card {
46
+ background: #16213e;
47
+ padding: 20px;
48
+ border-radius: 8px;
49
+ flex: 1;
50
+ }
51
+ .summary-card h3 {
52
+ margin: 0 0 10px 0;
53
+ color: #888;
54
+ font-size: 14px;
55
+ }
56
+ .summary-card .value {
57
+ font-size: 32px;
58
+ font-weight: bold;
59
+ }
60
+ .critical .value { color: #e94560; }
61
+ .high .value { color: #ff6b35; }
62
+ .medium .value { color: #f9c74f; }
63
+ .total .value { color: #4ecdc4; }
64
+ table {
65
+ width: 100%;
66
+ border-collapse: collapse;
67
+ margin-top: 20px;
68
+ }
69
+ th, td {
70
+ padding: 12px;
71
+ text-align: left;
72
+ border-bottom: 1px solid #333;
73
+ }
74
+ th {
75
+ background: #16213e;
76
+ color: #e94560;
77
+ }
78
+ tr.critical { background: rgba(233, 69, 96, 0.2); }
79
+ tr.high { background: rgba(255, 107, 53, 0.2); }
80
+ tr.medium { background: rgba(249, 199, 79, 0.1); }
81
+ .meta {
82
+ color: #666;
83
+ font-size: 12px;
84
+ margin-top: 40px;
85
+ }
86
+ .ok {
87
+ background: #16213e;
88
+ padding: 40px;
89
+ border-radius: 8px;
90
+ text-align: center;
91
+ color: #4ecdc4;
92
+ font-size: 24px;
93
+ }
94
+ .sandbox-section {
95
+ background: #16213e;
96
+ padding: 20px;
97
+ border-radius: 8px;
98
+ margin-top: 30px;
99
+ border-left: 4px solid #9b59b6;
100
+ }
101
+ .sandbox-section h2 {
102
+ color: #9b59b6;
103
+ margin-top: 0;
104
+ }
105
+ .sandbox-meta {
106
+ display: flex;
107
+ gap: 30px;
108
+ margin-bottom: 15px;
109
+ }
110
+ .sandbox-meta span {
111
+ color: #aaa;
112
+ }
113
+ .sandbox-finding {
114
+ padding: 8px 12px;
115
+ margin: 4px 0;
116
+ border-radius: 4px;
117
+ background: rgba(155, 89, 182, 0.1);
118
+ }
119
+ </style>
120
+ </head>
121
+ <body>
122
+ <div class="container">
123
+ <h1>MUAD'DIB - Scan Report</h1>
124
+
125
+ <div class="summary">
126
+ <div class="summary-card total">
127
+ <h3>TOTAL</h3>
128
+ <div class="value">${escapeHtml(summary.total)}</div>
129
+ </div>
130
+ <div class="summary-card critical">
131
+ <h3>CRITICAL</h3>
132
+ <div class="value">${escapeHtml(summary.critical)}</div>
133
+ </div>
134
+ <div class="summary-card high">
135
+ <h3>HIGH</h3>
136
+ <div class="value">${escapeHtml(summary.high)}</div>
137
+ </div>
138
+ <div class="summary-card medium">
139
+ <h3>MEDIUM</h3>
140
+ <div class="value">${escapeHtml(summary.medium)}</div>
141
+ </div>
142
+ </div>
143
+
144
+ ${threats.length > 0 ? `
145
+ <table>
146
+ <thead>
147
+ <tr>
148
+ <th>Severity</th>
149
+ <th>Type</th>
150
+ <th>Message</th>
151
+ <th>File</th>
152
+ <th>Recommendation</th>
153
+ </tr>
154
+ </thead>
155
+ <tbody>
156
+ ${threatRows}
157
+ </tbody>
158
+ </table>
159
+ ` : '<div class="ok">No threats detected</div>'}
160
+
161
+ ${results.sandbox ? `
162
+ <div class="sandbox-section">
163
+ <h2>[SANDBOX] Dynamic Analysis</h2>
164
+ <div class="sandbox-meta">
165
+ <span>Package: <strong>${escapeHtml(results.sandbox.package)}</strong></span>
166
+ <span>Score: <strong>${escapeHtml(String(results.sandbox.score))}/100</strong></span>
167
+ <span>Severity: <strong>${escapeHtml(results.sandbox.severity)}</strong></span>
168
+ </div>
169
+ ${results.sandbox.findings.length === 0
170
+ ? '<p style="color: #4ecdc4;">No suspicious behavior detected.</p>'
171
+ : results.sandbox.findings.map(f => `
172
+ <div class="sandbox-finding">
173
+ <strong>[${escapeHtml(f.severity)}]</strong> ${escapeHtml(f.type)}: ${escapeHtml(f.detail)}
174
+ </div>
175
+ `).join('')}
176
+
177
+ ${results.sandbox.network ? `
178
+ <h3 style="color: #9b59b6; margin-top: 20px;">Network Activity</h3>
179
+ ${(results.sandbox.network.dns_resolutions || []).length > 0 ? `
180
+ <h4 style="color: #aaa;">DNS Resolutions</h4>
181
+ ${results.sandbox.network.dns_resolutions.map(r => `
182
+ <div class="sandbox-finding">${escapeHtml(r.domain)} &rarr; ${escapeHtml(r.ip)}</div>
183
+ `).join('')}
184
+ ` : ''}
185
+ ${(results.sandbox.network.http_requests || []).length > 0 ? `
186
+ <h4 style="color: #aaa;">HTTP Requests</h4>
187
+ ${results.sandbox.network.http_requests.map(r => `
188
+ <div class="sandbox-finding">${escapeHtml(r.method)} ${escapeHtml(r.host)}${escapeHtml(r.path)}</div>
189
+ `).join('')}
190
+ ` : ''}
191
+ ${(results.sandbox.network.tls_connections || []).length > 0 ? `
192
+ <h4 style="color: #aaa;">TLS Connections</h4>
193
+ ${results.sandbox.network.tls_connections.map(t => `
194
+ <div class="sandbox-finding">${escapeHtml(t.domain)} (${escapeHtml(t.ip)}:${escapeHtml(String(t.port))})</div>
195
+ `).join('')}
196
+ ` : ''}
197
+ ${(results.sandbox.network.blocked_connections || []).length > 0 ? `
198
+ <h4 style="color: #e94560;">Blocked Connections</h4>
199
+ ${results.sandbox.network.blocked_connections.map(b => `
200
+ <div class="sandbox-finding" style="background: rgba(233,69,96,0.2);">[BLOCKED] ${escapeHtml(b.ip)}:${escapeHtml(String(b.port))}</div>
201
+ `).join('')}
202
+ ` : ''}
203
+ ` : ''}
204
+ </div>
205
+ ` : ''}
206
+
207
+ <div class="meta">
208
+ <p>Target: ${escapeHtml(target)}</p>
209
+ <p>Date: ${escapeHtml(timestamp)}</p>
210
+ <p>Generated by MUAD'DIB</p>
211
+ </div>
212
+ </div>
213
+ </body>
214
+ </html>`;
215
+ }
216
+
217
+ function saveReport(results, outputPath) {
218
+ if (!outputPath || typeof outputPath !== 'string') {
219
+ throw new Error('Invalid output path for HTML report');
220
+ }
221
+ const html = generateHTML(results);
222
+ try {
223
+ fs.writeFileSync(outputPath, html);
224
+ } catch (e) {
225
+ throw new Error(`Failed to write HTML report to ${outputPath}: ${e.message}`);
226
+ }
227
+ return outputPath;
228
+ }
229
+
230
+ module.exports = { generateHTML, saveReport };
@@ -98,6 +98,9 @@ const PLAYBOOKS = {
98
98
  lifecycle_script_dependency:
99
99
  'Verifier le contenu du script dans le package. Reinstaller avec --ignore-scripts si suspect.',
100
100
 
101
+ dependency_url_suspicious:
102
+ 'Dependance avec URL suspecte. Verifier l\'URL. Les URLs ngrok/localhost/IP privee sont malveillantes. Ne pas installer.',
103
+
101
104
  suspicious_file:
102
105
  'Fichier typique de Shai-Hulud. Ne pas executer. Verifier le hash contre les IOCs connus.',
103
106
 
@@ -178,6 +178,17 @@ const RULES = {
178
178
  ],
179
179
  mitre: 'T1195.002'
180
180
  },
181
+ dependency_url_suspicious: {
182
+ id: 'MUADDIB-DEP-005',
183
+ name: 'Suspicious Dependency URL',
184
+ severity: 'HIGH',
185
+ confidence: 'high',
186
+ description: 'Dependance declaree avec une URL HTTP/HTTPS au lieu d\'une version npm. Les URLs ngrok/localhost/IP privee sont fortement suspectes.',
187
+ references: [
188
+ 'https://docs.npmjs.com/cli/v9/configuring-npm/package-json#urls-as-dependencies'
189
+ ],
190
+ mitre: 'T1195.002'
191
+ },
181
192
 
182
193
  // Hash detections
183
194
  known_malicious_hash: {