muaddib-scanner 2.7.8 → 2.7.9
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 +4 -4
- package/package.json +1 -1
- package/src/scoring.js +16 -0
- package/src/shared/download.js +5 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
npm and PyPI supply-chain attacks are exploding. Shai-Hulud compromised 25K+ repos in 2025. Existing tools detect threats but don't help you respond.
|
|
32
32
|
|
|
33
|
-
MUAD'DIB combines **14 parallel scanners** (
|
|
33
|
+
MUAD'DIB combines **14 parallel scanners** (134 detection rules), a **deobfuscation engine**, **inter-module dataflow analysis**, **per-file max scoring**, Docker sandbox with **monkey-patching preload** for time-bomb detection, **behavioral anomaly detection**, and **ground truth validation** to detect threats AND guide your response — even before they appear in any IOC database.
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
@@ -195,7 +195,7 @@ muaddib replay # Ground truth validation (46/49 TPR)
|
|
|
195
195
|
| GitHub Actions | Shai-Hulud backdoor detection |
|
|
196
196
|
| Hash Scanner | Known malicious file hashes |
|
|
197
197
|
|
|
198
|
-
###
|
|
198
|
+
### 134 detection rules
|
|
199
199
|
|
|
200
200
|
All rules are mapped to MITRE ATT&CK techniques. See [SECURITY.md](SECURITY.md#detection-rules-v262) for the complete rules reference.
|
|
201
201
|
|
|
@@ -286,7 +286,7 @@ repos:
|
|
|
286
286
|
| **FPR** (Benign) | **12.1%** (64/529) | 529 npm packages, real source via `npm pack` |
|
|
287
287
|
| **ADR** (Adversarial + Holdout) | **92.2%** (71/77) | 53 adversarial + 40 holdout (77 available on disk), global threshold=20 |
|
|
288
288
|
|
|
289
|
-
**
|
|
289
|
+
**2166 tests** across 49 files. **134 rules** (129 RULES + 5 PARANOID).
|
|
290
290
|
|
|
291
291
|
> **Methodology caveats:**
|
|
292
292
|
> - TPR measured on 49 Node.js attack samples (3 browser-only excluded from 51 total)
|
|
@@ -327,7 +327,7 @@ npm test
|
|
|
327
327
|
|
|
328
328
|
### Testing
|
|
329
329
|
|
|
330
|
-
- **
|
|
330
|
+
- **2166 tests** across 49 modular test files
|
|
331
331
|
- **56 fuzz tests** - Malformed inputs, ReDoS, unicode, binary
|
|
332
332
|
- **Datadog 17K benchmark** - 17,922 real malware samples
|
|
333
333
|
- **Ground truth validation** - 51 real-world attacks (93.9% TPR)
|
package/package.json
CHANGED
package/src/scoring.js
CHANGED
|
@@ -198,6 +198,11 @@ const FRAMEWORK_PROTO_RE = new RegExp(
|
|
|
198
198
|
);
|
|
199
199
|
|
|
200
200
|
function applyFPReductions(threats, reachableFiles, packageName, packageDeps) {
|
|
201
|
+
// Initialize reductions audit trail on each threat
|
|
202
|
+
for (const t of threats) {
|
|
203
|
+
t.reductions = [];
|
|
204
|
+
}
|
|
205
|
+
|
|
201
206
|
// Count occurrences of each threat type (package-level, across all files)
|
|
202
207
|
const typeCounts = {};
|
|
203
208
|
for (const t of threats) {
|
|
@@ -224,6 +229,7 @@ function applyFPReductions(threats, reachableFiles, packageName, packageDeps) {
|
|
|
224
229
|
if ((t.type === 'dynamic_require' || t.type === 'dynamic_import') && t.severity === 'HIGH') {
|
|
225
230
|
const f = t.file || '(unknown)';
|
|
226
231
|
if (perFilePluginCount[f] > 4) {
|
|
232
|
+
t.reductions.push({ rule: 'plugin_loader_per_file', from: 'HIGH', to: 'LOW' });
|
|
227
233
|
t.severity = 'LOW';
|
|
228
234
|
}
|
|
229
235
|
}
|
|
@@ -245,6 +251,7 @@ function applyFPReductions(threats, reachableFiles, packageName, packageDeps) {
|
|
|
245
251
|
if (typeRatio < 0.4 ||
|
|
246
252
|
(t.type === 'suspicious_dataflow' && typeCounts[t.type] > rule.maxCount) ||
|
|
247
253
|
(t.type === 'vm_code_execution' && typeCounts[t.type] > rule.maxCount)) {
|
|
254
|
+
t.reductions.push({ rule: 'count_threshold', from: t.severity, to: rule.to });
|
|
248
255
|
t.severity = rule.to;
|
|
249
256
|
}
|
|
250
257
|
}
|
|
@@ -253,6 +260,7 @@ function applyFPReductions(threats, reachableFiles, packageName, packageDeps) {
|
|
|
253
260
|
// Malware poisons cache repeatedly; a single access is framework behavior
|
|
254
261
|
if (t.type === 'require_cache_poison' && t.severity === 'CRITICAL' &&
|
|
255
262
|
typeCounts.require_cache_poison === 1) {
|
|
263
|
+
t.reductions.push({ rule: 'cache_poison_single', from: 'CRITICAL', to: 'HIGH' });
|
|
256
264
|
t.severity = 'HIGH';
|
|
257
265
|
}
|
|
258
266
|
|
|
@@ -261,6 +269,7 @@ function applyFPReductions(threats, reachableFiles, packageName, packageDeps) {
|
|
|
261
269
|
// Browser/native APIs (globalThis.fetch, XMLHttpRequest) stay HIGH
|
|
262
270
|
if (t.type === 'prototype_hook' && t.severity === 'HIGH' &&
|
|
263
271
|
FRAMEWORK_PROTO_RE.test(t.message)) {
|
|
272
|
+
t.reductions.push({ rule: 'framework_prototype', from: 'HIGH', to: 'MEDIUM' });
|
|
264
273
|
t.severity = 'MEDIUM';
|
|
265
274
|
}
|
|
266
275
|
|
|
@@ -271,6 +280,7 @@ function applyFPReductions(threats, reachableFiles, packageName, packageDeps) {
|
|
|
271
280
|
typeCounts.prototype_hook > 20) {
|
|
272
281
|
const HTTP_PROTO_RE = /\b(Request|Response|IncomingMessage|ClientRequest|ServerResponse|fetch)\b/i;
|
|
273
282
|
if (HTTP_PROTO_RE.test(t.message)) {
|
|
283
|
+
t.reductions.push({ rule: 'http_client_whitelist', from: t.severity, to: 'MEDIUM' });
|
|
274
284
|
t.severity = 'MEDIUM';
|
|
275
285
|
}
|
|
276
286
|
}
|
|
@@ -283,14 +293,18 @@ function applyFPReductions(threats, reachableFiles, packageName, packageDeps) {
|
|
|
283
293
|
if (t.file && !DIST_EXEMPT_TYPES.has(t.type) && DIST_FILE_RE.test(t.file)) {
|
|
284
294
|
if (DIST_BUNDLER_ARTIFACT_TYPES.has(t.type)) {
|
|
285
295
|
// Two-notch downgrade for bundler artifacts
|
|
296
|
+
const fromSev = t.severity;
|
|
286
297
|
if (t.severity === 'CRITICAL') t.severity = 'MEDIUM';
|
|
287
298
|
else if (t.severity === 'HIGH') t.severity = 'LOW';
|
|
288
299
|
else if (t.severity === 'MEDIUM') t.severity = 'LOW';
|
|
300
|
+
if (t.severity !== fromSev) t.reductions.push({ rule: 'dist_file', from: fromSev, to: t.severity });
|
|
289
301
|
} else {
|
|
290
302
|
// One-notch downgrade for other non-exempt types
|
|
303
|
+
const fromSev = t.severity;
|
|
291
304
|
if (t.severity === 'CRITICAL') t.severity = 'HIGH';
|
|
292
305
|
else if (t.severity === 'HIGH') t.severity = 'MEDIUM';
|
|
293
306
|
else if (t.severity === 'MEDIUM') t.severity = 'LOW';
|
|
307
|
+
if (t.severity !== fromSev) t.reductions.push({ rule: 'dist_file', from: fromSev, to: t.severity });
|
|
294
308
|
}
|
|
295
309
|
}
|
|
296
310
|
|
|
@@ -300,6 +314,7 @@ function applyFPReductions(threats, reachableFiles, packageName, packageDeps) {
|
|
|
300
314
|
!isPackageLevelThreat(t)) {
|
|
301
315
|
const normalizedFile = t.file.replace(/\\/g, '/');
|
|
302
316
|
if (!reachableFiles.has(normalizedFile)) {
|
|
317
|
+
t.reductions.push({ rule: 'unreachable', from: t.severity, to: 'LOW' });
|
|
303
318
|
t.severity = 'LOW';
|
|
304
319
|
t.unreachable = true;
|
|
305
320
|
}
|
|
@@ -312,6 +327,7 @@ function applyFPReductions(threats, reachableFiles, packageName, packageDeps) {
|
|
|
312
327
|
if (t.type === 'mcp_config_injection' && t.severity === 'CRITICAL' &&
|
|
313
328
|
packageDeps && typeof packageDeps === 'object' &&
|
|
314
329
|
packageDeps['@modelcontextprotocol/sdk']) {
|
|
330
|
+
t.reductions.push({ rule: 'mcp_sdk', from: 'CRITICAL', to: 'MEDIUM' });
|
|
315
331
|
t.severity = 'MEDIUM';
|
|
316
332
|
t.mcpSdkDowngrade = true;
|
|
317
333
|
}
|
package/src/shared/download.js
CHANGED
|
@@ -23,7 +23,11 @@ const PRIVATE_IP_PATTERNS = [
|
|
|
23
23
|
/^::1$/,
|
|
24
24
|
/^::ffff:127\./,
|
|
25
25
|
/^fc00:/,
|
|
26
|
-
/^fe80
|
|
26
|
+
/^fe80:/,
|
|
27
|
+
/^ff/i, // IPv6 multicast (RFC 4291 ff00::/8)
|
|
28
|
+
/^2001:0?db8:/i, // IPv6 documentation (RFC 3849 2001:db8::/32)
|
|
29
|
+
/^100:/, // IPv6 discard (RFC 6666 100::/64)
|
|
30
|
+
/^64:ff9b:/i // IPv6 NAT64 well-known (RFC 6052 64:ff9b::/96)
|
|
27
31
|
];
|
|
28
32
|
|
|
29
33
|
/**
|