muaddib-scanner 2.3.0 → 2.3.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 +17 -16
- package/package.json +6 -6
- package/src/commands/evaluate.js +1 -1
- package/src/monitor.js +3 -3
- package/src/scanner/dataflow.js +347 -336
- package/src/scanner/entropy.js +246 -242
- package/src/scanner/obfuscation.js +2 -1
- package/src/scanner/package.js +166 -161
- package/src/scoring.js +18 -0
package/README.md
CHANGED
|
@@ -285,7 +285,7 @@ Add to `.pre-commit-config.yaml`:
|
|
|
285
285
|
```yaml
|
|
286
286
|
repos:
|
|
287
287
|
- repo: https://github.com/DNSZLSK/muad-dib
|
|
288
|
-
rev: v2.
|
|
288
|
+
rev: v2.3.1
|
|
289
289
|
hooks:
|
|
290
290
|
- id: muaddib-scan # Scan all threats
|
|
291
291
|
# - id: muaddib-diff # Or: only new threats
|
|
@@ -641,7 +641,7 @@ Alerts appear in Security > Code scanning alerts.
|
|
|
641
641
|
## Architecture
|
|
642
642
|
|
|
643
643
|
```
|
|
644
|
-
MUAD'DIB 2.
|
|
644
|
+
MUAD'DIB 2.3.1 Scanner
|
|
645
645
|
|
|
|
646
646
|
+-- IOC Match (225,000+ packages, JSON DB)
|
|
647
647
|
| +-- OSV.dev npm dump (200K+ MAL-* entries)
|
|
@@ -663,7 +663,7 @@ MUAD'DIB 2.2.24 Scanner
|
|
|
663
663
|
| +-- 3-hop re-export chains, class method analysis
|
|
664
664
|
| +-- Cross-file credential read -> network sink detection
|
|
665
665
|
|
|
|
666
|
-
+-- 14 Parallel Scanners (
|
|
666
|
+
+-- 14 Parallel Scanners (102 rules)
|
|
667
667
|
| +-- AST Parse (acorn) — eval/Function, credential CLI theft, binary droppers, prototype hooks
|
|
668
668
|
| +-- Pattern Matching (shell, scripts)
|
|
669
669
|
| +-- Obfuscation Detection (skip .min.js, ignore hex/unicode alone)
|
|
@@ -689,11 +689,13 @@ MUAD'DIB 2.2.24 Scanner
|
|
|
689
689
|
| +-- Score Breakdown (explainable per-rule scoring)
|
|
690
690
|
| +-- Threat Feed API (HTTP server, JSON feed for SIEM)
|
|
691
691
|
|
|
|
692
|
-
+-- FP Reduction Post-processing (v2.2.8-v2.2.9)
|
|
693
|
-
| +-- Count-based severity downgrade (dynamic_require, dataflow, etc.)
|
|
694
|
-
| +-- Framework prototype scoring cap
|
|
695
|
-
| +-- Obfuscation in dist/build → LOW
|
|
692
|
+
+-- FP Reduction Post-processing (v2.2.8-v2.2.9, v2.3.0-v2.3.1)
|
|
693
|
+
| +-- Count-based severity downgrade (dynamic_require, dataflow, module_compile, etc.)
|
|
694
|
+
| +-- Framework prototype scoring cap + HTTP client whitelist
|
|
695
|
+
| +-- Obfuscation in dist/build/.cjs/.mjs → LOW
|
|
696
696
|
| +-- Safe env var + prefix filtering
|
|
697
|
+
| +-- Dataflow telemetry source categorization (os.platform/arch → telemetry_read)
|
|
698
|
+
| +-- DEP whitelist (es5-ext, bootstrap-sass) + npm alias skip
|
|
697
699
|
|
|
|
698
700
|
+-- Per-File Max Scoring (v2.2.11)
|
|
699
701
|
| +-- Score = max(file_scores) + package_level_score
|
|
@@ -721,9 +723,8 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
|
721
723
|
| Metric | Result | Details |
|
|
722
724
|
|--------|--------|---------|
|
|
723
725
|
| **TPR** (Ground Truth) | **91.8%** (45/49) | 51 real-world attacks (49 active). 4 out-of-scope: browser-only (3) + FP-risky (1) |
|
|
724
|
-
| **FPR** (
|
|
725
|
-
| **
|
|
726
|
-
| **ADR** (Adversarial + Holdout) | **100%** (78/78) | 38 adversarial + 40 holdout evasive samples across 5 red-team waves |
|
|
726
|
+
| **FPR** (Benign, global) | **7.4%** (39/525) | 529 npm packages (525 scanned), real source code via `npm pack`, threshold > 20 |
|
|
727
|
+
| **ADR** (Adversarial + Holdout) | **98.7%** (77/78) | 38 adversarial + 40 holdout evasive samples. 1 documented miss: `require-cache-poison` (accepted trade-off) |
|
|
727
728
|
|
|
728
729
|
**FPR by package size** — FPR correlates linearly with package size. Per-file max scoring (v2.2.11) significantly reduces FP on medium/large packages:
|
|
729
730
|
|
|
@@ -734,7 +735,7 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
|
734
735
|
| Large (50-100 JS files) | 40 | 10 | 25.0% |
|
|
735
736
|
| Very large (100+ JS files) | 62 | 25 | 40.3% |
|
|
736
737
|
|
|
737
|
-
**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) →
|
|
738
|
+
**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)
|
|
738
739
|
|
|
739
740
|
**Holdout progression** (pre-tuning scores, rules frozen):
|
|
740
741
|
|
|
@@ -748,10 +749,10 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
|
748
749
|
|
|
749
750
|
- **TPR** (True Positive Rate): detection rate on 49 real-world supply-chain attacks (event-stream, ua-parser-js, coa, flatmap-stream, eslint-scope, solana-web3js, and 43 more). 4 misses are browser-only (lottie-player, polyfill-io, trojanized-jquery) or risky to fix (websocket-rat) — see [Threat Model](docs/threat-model.md).
|
|
750
751
|
- **FPR** (False Positive Rate): packages scoring > 20 out of 529 real npm packages (source code scanned, not empty dirs). The 6.2% on standard packages (<10 JS files, 290 packages) is the most representative metric for typical use — most npm packages are small.
|
|
751
|
-
- **ADR** (Adversarial Detection Rate): detection rate on
|
|
752
|
+
- **ADR** (Adversarial Detection Rate): detection rate on 78 evasive malicious samples — 38 adversarial + 40 holdout (5 batches of 10, testing obfuscation, inter-module dataflow, etc.). 1 documented miss: `require-cache-poison` (score 10 < threshold 20, accepted trade-off from FP reduction P3).
|
|
752
753
|
- **Holdout** (pre-tuning): detection rate on 10 unseen samples with rules frozen (measures generalization)
|
|
753
754
|
|
|
754
|
-
Datasets: 529 npm + 132 PyPI benign packages, 78 adversarial/holdout samples, 51 ground-truth attacks (65 documented malware packages). **
|
|
755
|
+
Datasets: 529 npm + 132 PyPI benign packages, 78 adversarial/holdout samples, 51 ground-truth attacks (65 documented malware packages). **1387 tests**, 86% code coverage.
|
|
755
756
|
|
|
756
757
|
See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) for the full experimental protocol.
|
|
757
758
|
|
|
@@ -787,11 +788,11 @@ npm test
|
|
|
787
788
|
|
|
788
789
|
### Testing
|
|
789
790
|
|
|
790
|
-
- **
|
|
791
|
+
- **1387 unit/integration tests** across 20 modular test files - 86% code coverage via [Codecov](https://codecov.io/gh/DNSZLSK/muad-dib)
|
|
791
792
|
- **56 fuzz tests** - Malformed YAML, invalid JSON, binary files, ReDoS, unicode, 10MB inputs
|
|
792
|
-
- **78 adversarial/holdout samples** - 38 adversarial + 40 holdout,
|
|
793
|
+
- **78 adversarial/holdout samples** - 38 adversarial + 40 holdout, 77/78 detection rate (98.7% ADR). 1 documented miss: `require-cache-poison` (accepted trade-off)
|
|
793
794
|
- **Ground truth validation** - 51 real-world attacks (45/49 detected = 91.8% TPR). 4 out-of-scope: browser-only (3) + FP-risky (1)
|
|
794
|
-
- **False positive validation** -
|
|
795
|
+
- **False positive validation** - 7.4% FPR global (39/525) on real npm source code via `npm pack`
|
|
795
796
|
- **ESLint security audit** - `eslint-plugin-security` with 14 rules enabled
|
|
796
797
|
|
|
797
798
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muaddib-scanner",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Supply-chain threat detection & response for npm & PyPI/Python",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"node": ">=18.0.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@inquirer/prompts": "8.
|
|
48
|
-
"acorn": "8.
|
|
49
|
-
"acorn-walk": "8.3.
|
|
47
|
+
"@inquirer/prompts": "8.3.0",
|
|
48
|
+
"acorn": "8.16.0",
|
|
49
|
+
"acorn-walk": "8.3.5",
|
|
50
50
|
"adm-zip": "0.5.16",
|
|
51
51
|
"chalk": "5.6.2",
|
|
52
52
|
"js-yaml": "4.1.1",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@eslint/js": "10.0.1",
|
|
57
|
-
"eslint": "10.0.
|
|
58
|
-
"eslint-plugin-security": "^
|
|
57
|
+
"eslint": "10.0.2",
|
|
58
|
+
"eslint-plugin-security": "^4.0.0",
|
|
59
59
|
"globals": "17.3.0"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/src/commands/evaluate.js
CHANGED
|
@@ -80,7 +80,7 @@ const ADVERSARIAL_THRESHOLDS = {
|
|
|
80
80
|
|
|
81
81
|
const HOLDOUT_THRESHOLDS = {
|
|
82
82
|
// holdout-v2 (10 samples)
|
|
83
|
-
'conditional-os-payload':
|
|
83
|
+
'conditional-os-payload': 20, 'env-var-reconstruction': 25,
|
|
84
84
|
'github-workflow-inject': 20, 'homedir-ssh-key-steal': 25,
|
|
85
85
|
'npm-cache-poison': 20, 'npm-lifecycle-preinstall-curl': 25,
|
|
86
86
|
'process-env-proxy-getter': 20, 'readable-stream-hijack': 20,
|
package/src/monitor.js
CHANGED
|
@@ -1640,9 +1640,9 @@ function getTemporalMaxSeverity(temporalResult, astResult, publishResult, mainta
|
|
|
1640
1640
|
if (astResult && astResult.suspicious && astResult.findings) {
|
|
1641
1641
|
allFindings.push(...astResult.findings);
|
|
1642
1642
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1643
|
+
// publishResult deliberately excluded — publish anomalies alone (nightly builds,
|
|
1644
|
+
// burst releases) should not trigger temporal preservation. They are handled
|
|
1645
|
+
// separately by isPublishAnomalyOnly().
|
|
1646
1646
|
if (maintainerResult && maintainerResult.suspicious && maintainerResult.findings) {
|
|
1647
1647
|
allFindings.push(...maintainerResult.findings);
|
|
1648
1648
|
}
|