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 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** (133 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.
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
- ### 133 detection rules
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
- **2093 tests** across 49 files. **134 rules** (129 RULES + 5 PARANOID).
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
- - **2093 tests** across 49 modular test files
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.7.8",
3
+ "version": "2.7.9",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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
  }
@@ -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
  /**