muaddib-scanner 2.6.0 → 2.6.2
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 +2 -2
- package/src/index.js +15 -4
- package/src/scanner/module-graph.js +726 -16
- package/src/scoring.js +20 -10
- package/src/temporal-runner.js +26 -2
- package/evaluate-output.json +0 -20766
- package/evaluate-text-output.txt +0 -2799
- package/evaluate-v2-output.json +0 -19390
package/README.md
CHANGED
|
@@ -642,7 +642,7 @@ Alerts appear in Security > Code scanning alerts.
|
|
|
642
642
|
## Architecture
|
|
643
643
|
|
|
644
644
|
```
|
|
645
|
-
MUAD'DIB 2.
|
|
645
|
+
MUAD'DIB 2.6.1 Scanner
|
|
646
646
|
|
|
|
647
647
|
+-- IOC Match (225,000+ packages, JSON DB)
|
|
648
648
|
| +-- OSV.dev npm dump (200K+ MAL-* entries)
|
|
@@ -773,7 +773,7 @@ All 2,077 misses lack Node.js malware patterns. MUAD'DIB performs AST-based Node
|
|
|
773
773
|
| Large (50-100 JS files) | 40 | 10 | 25.0% |
|
|
774
774
|
| Very large (100+ JS files) | 62 | 25 | 40.3% |
|
|
775
775
|
|
|
776
|
-
**FPR progression**: 0% (invalid, empty dirs, v2.2.0-v2.2.6) → 38% (first real measurement, v2.2.7) → 19.4% (v2.2.8) → 17.5% (v2.2.9) → ~13% (v2.2.11, per-file max scoring) → 8.9% (v2.3.0, P2) → 7.4% (v2.3.1, P3) → 6.0% (v2.5.8, P4 + IOC wildcard audit) → ~13.6% (v2.5.14, audit hardening added stricter detection) → **12.3%** (v2.5.16, P5 + P6) → **12.3%** (v2.6.0, intent graph v2 — zero FP added)
|
|
776
|
+
**FPR progression**: 0% (invalid, empty dirs, v2.2.0-v2.2.6) → 38% (first real measurement, v2.2.7) → 19.4% (v2.2.8) → 17.5% (v2.2.9) → ~13% (v2.2.11, per-file max scoring) → 8.9% (v2.3.0, P2) → 7.4% (v2.3.1, P3) → 6.0% (v2.5.8, P4 + IOC wildcard audit) → ~13.6% (v2.5.14, audit hardening added stricter detection) → **12.3%** (v2.5.16, P5 + P6) → **12.3%** (v2.6.0, intent graph v2 — zero FP added) → **12.3%** (v2.6.1, module-graph bounded path — zero FP added)
|
|
777
777
|
|
|
778
778
|
> **Note on FPR evolution:** The historic 6.0% FPR (v2.5.8) relied on a `BENIGN_PACKAGE_WHITELIST` that excluded certain known packages from scoring — a data leakage bias removed in v2.5.10. The current 12.3% FPR is an honest measurement without whitelisting, against 532 real benign packages. The intent graph (v2.6.0) adds zero false positives by using intra-file pairing only and excluding LOW-severity threats.
|
|
779
779
|
|
|
@@ -793,7 +793,7 @@ All 2,077 misses lack Node.js malware patterns. MUAD'DIB performs AST-based Node
|
|
|
793
793
|
- **ADR** (Adversarial Detection Rate): detection rate on 120 evasive malicious samples — 53 adversarial + 40 holdout (6 adversarial waves + 4 holdout batches). 75 available on disk. 2 misses on available samples: `require-cache-poison` (P3 trade-off), `getter-defineProperty-exfil`.
|
|
794
794
|
- **Holdout** (pre-tuning): detection rate on 10 unseen samples with rules frozen (measures generalization)
|
|
795
795
|
|
|
796
|
-
Datasets: 17,922 Datadog malware samples, 532 npm + 132 PyPI benign packages, 120 adversarial/holdout samples (75 available on disk), 51 ground-truth attacks (65 documented malware packages). **
|
|
796
|
+
Datasets: 17,922 Datadog malware samples, 532 npm + 132 PyPI benign packages, 120 adversarial/holdout samples (75 available on disk), 51 ground-truth attacks (65 documented malware packages). **1932 tests**, 86% code coverage.
|
|
797
797
|
|
|
798
798
|
See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) for the full experimental protocol.
|
|
799
799
|
|
|
@@ -829,7 +829,7 @@ npm test
|
|
|
829
829
|
|
|
830
830
|
### Testing
|
|
831
831
|
|
|
832
|
-
- **
|
|
832
|
+
- **1932 unit/integration tests** across 44 modular test files - 86% code coverage via [Codecov](https://codecov.io/gh/DNSZLSK/muad-dib)
|
|
833
833
|
- **56 fuzz tests** - Malformed YAML, invalid JSON, binary files, ReDoS, unicode, 10MB inputs
|
|
834
834
|
- **Datadog 17K benchmark** - 17,922 real malware samples, 88.2% raw TPR, ~100% on JS/Node.js malware (2,077 out-of-scope misses: phishing, binaries, corrected libs)
|
|
835
835
|
- **120 adversarial/holdout samples** - 53 adversarial + 40 holdout (75 available on disk), 73/75 detection rate (97.3% ADR). 2 misses: `require-cache-poison` (P3 trade-off), `getter-defineProperty-exfil`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muaddib-scanner",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "Supply-chain threat detection & response for npm & PyPI/Python",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@eslint/js": "10.0.1",
|
|
58
|
-
"eslint": "10.0.
|
|
58
|
+
"eslint": "10.0.3",
|
|
59
59
|
"eslint-plugin-security": "^4.0.0",
|
|
60
60
|
"globals": "17.4.0"
|
|
61
61
|
}
|
package/src/index.js
CHANGED
|
@@ -23,7 +23,7 @@ const { ensureIOCs } = require('./ioc/bootstrap.js');
|
|
|
23
23
|
const { scanEntropy } = require('./scanner/entropy.js');
|
|
24
24
|
const { scanAIConfig } = require('./scanner/ai-config.js');
|
|
25
25
|
const { deobfuscate } = require('./scanner/deobfuscate.js');
|
|
26
|
-
const { buildModuleGraph, annotateTaintedExports, detectCrossFileFlows, annotateSinkExports, detectCallbackCrossFileFlows } = require('./scanner/module-graph.js');
|
|
26
|
+
const { buildModuleGraph, annotateTaintedExports, detectCrossFileFlows, annotateSinkExports, detectCallbackCrossFileFlows, detectEventEmitterFlows } = require('./scanner/module-graph.js');
|
|
27
27
|
const { computeReachableFiles } = require('./scanner/reachability.js');
|
|
28
28
|
const { runTemporalAnalyses } = require('./temporal-runner.js');
|
|
29
29
|
const { formatOutput } = require('./output-formatter.js');
|
|
@@ -357,16 +357,27 @@ async function run(targetPath, options = {}) {
|
|
|
357
357
|
|
|
358
358
|
// Cross-file module graph analysis (before individual scanners)
|
|
359
359
|
// Wrapped in yieldThen to unblock spinner animation
|
|
360
|
+
// Bounded: 5s timeout to prevent DoS on large/adversarial packages
|
|
361
|
+
const MODULE_GRAPH_TIMEOUT_MS = 5000;
|
|
360
362
|
let crossFileFlows = [];
|
|
361
363
|
if (!options.noModuleGraph) {
|
|
362
|
-
|
|
364
|
+
const moduleGraphWork = async () => {
|
|
363
365
|
const graph = await yieldThen(() => buildModuleGraph(targetPath));
|
|
364
366
|
const tainted = await yieldThen(() => annotateTaintedExports(graph, targetPath));
|
|
365
|
-
crossFileFlows = await yieldThen(() => detectCrossFileFlows(graph, tainted, targetPath));
|
|
366
|
-
// Callback-based cross-file flow detection
|
|
367
367
|
const sinkAnnotations = await yieldThen(() => annotateSinkExports(graph, targetPath));
|
|
368
|
+
crossFileFlows = await yieldThen(() => detectCrossFileFlows(graph, tainted, sinkAnnotations, targetPath));
|
|
369
|
+
// Callback-based cross-file flow detection
|
|
368
370
|
const callbackFlows = await yieldThen(() => detectCallbackCrossFileFlows(graph, tainted, sinkAnnotations, targetPath));
|
|
369
371
|
crossFileFlows = crossFileFlows.concat(callbackFlows);
|
|
372
|
+
// EventEmitter cross-module flow detection
|
|
373
|
+
const emitterFlows = await yieldThen(() => detectEventEmitterFlows(graph, tainted, sinkAnnotations, targetPath));
|
|
374
|
+
crossFileFlows = crossFileFlows.concat(emitterFlows);
|
|
375
|
+
};
|
|
376
|
+
const timeout = new Promise((_, reject) =>
|
|
377
|
+
setTimeout(() => reject(new Error('Module graph timeout')), MODULE_GRAPH_TIMEOUT_MS)
|
|
378
|
+
);
|
|
379
|
+
try {
|
|
380
|
+
await Promise.race([moduleGraphWork(), timeout]);
|
|
370
381
|
} catch (e) {
|
|
371
382
|
// Graceful fallback — module graph is best-effort
|
|
372
383
|
debugLog('[MODULE-GRAPH] Error:', e && e.message);
|