muaddib-scanner 2.5.8 → 2.5.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 +26 -15
- package/logs/alerts/{2026-03-06T13-25-09-667-evil-pkg.json → 2026-03-06T19-26-22-192-evil-pkg.json} +1 -1
- package/logs/alerts/{2026-03-06T13-25-09-668-suspect-pkg.json → 2026-03-06T19-26-22-192-suspect-pkg.json} +1 -1
- package/logs/alerts/{2026-03-06T13-25-09-668-evil-pkg.json → 2026-03-06T19-26-22-193-evil-pkg.json} +1 -1
- package/logs/alerts/{2026-03-06T13-25-10-228-evil-pkg.json → 2026-03-06T19-26-22-572-evil-pkg.json} +1 -1
- package/logs/daily-reports/2026-03-06.json +4 -4
- package/package.json +1 -1
- package/src/scanner/ast-detectors.js +16 -4
- package/src/scanner/ast.js +6 -1
- package/src/scoring.js +36 -19
- package/_test_aiweapon.js +0 -12
- package/_test_fp.js +0 -11
- package/_test_fp2.js +0 -37
- package/_test_fp3.js +0 -19
- package/_test_nodemailer.js +0 -17
- package/_test_p4_detail.js +0 -45
- package/_test_p4_detail2.js +0 -32
- package/_test_p4_quick.js +0 -33
- package/_test_regex.js +0 -5
- package/_vitest_result.json +0 -395
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 static analysis + **deobfuscation engine** (v2.2.5) + **inter-module dataflow** (v2.2.6) + **per-file max scoring** (v2.2.11) + dynamic analysis (Docker sandbox) + **behavioral anomaly detection** (v2.0) + **ground truth validation** (v2.1) to detect threats AND guide your response — even before they appear in any IOC database.
|
|
33
|
+
MUAD'DIB combines static analysis + **deobfuscation engine** (v2.2.5) + **inter-module dataflow** (v2.2.6) + **per-file max scoring** (v2.2.11) + dynamic analysis (Docker sandbox with **monkey-patching preload** for time-bomb detection, v2.4.9) + **behavioral anomaly detection** (v2.0) + **ground truth validation** (v2.1) + **security audit** (41 issues remediated, v2.5.0–v2.5.6) to detect threats AND guide your response — even before they appear in any IOC database.
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
@@ -205,6 +205,7 @@ Multi-layer monitoring:
|
|
|
205
205
|
- **Data exfiltration detection**: 16 sensitive patterns (tokens, credentials, SSH keys, private keys, .env)
|
|
206
206
|
- **CI-aware environment** (v2.1.2): simulates CI environments (GITHUB_ACTIONS, GITLAB_CI, TRAVIS, CIRCLECI, JENKINS) to trigger CI-aware malware that would otherwise stay dormant
|
|
207
207
|
- **Enriched canary tokens** (v2.1.2): 6 honeypot credentials injected as env vars (GITHUB_TOKEN, NPM_TOKEN, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, SLACK_WEBHOOK_URL, DISCORD_WEBHOOK_URL). If exfiltrated via network, DNS, or filesystem, triggers CRITICAL alert with +50 score
|
|
208
|
+
- **Monkey-patching preload** (v2.4.9): Runtime instrumentation via `NODE_OPTIONS=--require /opt/preload.js`. Patches time APIs (Date.now, setTimeout→0, setInterval→immediate), intercepts network/filesystem/process/env calls. Multi-run mode at [0h, 72h, 7d] offsets to detect time-bomb malware (MITRE T1497.003)
|
|
208
209
|
- **Scoring engine**: 0-100 risk score based on behavioral severity
|
|
209
210
|
|
|
210
211
|
Use `--strict` to block all non-essential outbound network traffic via iptables.
|
|
@@ -285,7 +286,7 @@ Add to `.pre-commit-config.yaml`:
|
|
|
285
286
|
```yaml
|
|
286
287
|
repos:
|
|
287
288
|
- repo: https://github.com/DNSZLSK/muad-dib
|
|
288
|
-
rev: v2.
|
|
289
|
+
rev: v2.5.8
|
|
289
290
|
hooks:
|
|
290
291
|
- id: muaddib-scan # Scan all threats
|
|
291
292
|
# - id: muaddib-diff # Or: only new threats
|
|
@@ -641,7 +642,7 @@ Alerts appear in Security > Code scanning alerts.
|
|
|
641
642
|
## Architecture
|
|
642
643
|
|
|
643
644
|
```
|
|
644
|
-
MUAD'DIB 2.
|
|
645
|
+
MUAD'DIB 2.5.8 Scanner
|
|
645
646
|
|
|
|
646
647
|
+-- IOC Match (225,000+ packages, JSON DB)
|
|
647
648
|
| +-- OSV.dev npm dump (200K+ MAL-* entries)
|
|
@@ -663,7 +664,7 @@ MUAD'DIB 2.3.1 Scanner
|
|
|
663
664
|
| +-- 3-hop re-export chains, class method analysis
|
|
664
665
|
| +-- Cross-file credential read -> network sink detection
|
|
665
666
|
|
|
|
666
|
-
+-- 14 Parallel Scanners (
|
|
667
|
+
+-- 14 Parallel Scanners (113 rules)
|
|
667
668
|
| +-- AST Parse (acorn) — eval/Function, credential CLI theft, binary droppers, prototype hooks
|
|
668
669
|
| +-- Pattern Matching (shell, scripts)
|
|
669
670
|
| +-- Obfuscation Detection (skip .min.js, ignore hex/unicode alone)
|
|
@@ -690,21 +691,31 @@ MUAD'DIB 2.3.1 Scanner
|
|
|
690
691
|
| +-- Score Breakdown (explainable per-rule scoring)
|
|
691
692
|
| +-- Threat Feed API (HTTP server, JSON feed for SIEM)
|
|
692
693
|
|
|
|
693
|
-
+-- FP Reduction Post-processing (v2.2.8-v2.2.9, v2.3.0-v2.3.1)
|
|
694
|
+
+-- FP Reduction Post-processing (v2.2.8-v2.2.9, v2.3.0-v2.3.1, v2.5.7-v2.5.8)
|
|
694
695
|
| +-- Count-based severity downgrade (dynamic_require, dataflow, module_compile, etc.)
|
|
695
696
|
| +-- Framework prototype scoring cap + HTTP client whitelist
|
|
696
697
|
| +-- Obfuscation in dist/build/.cjs/.mjs → LOW
|
|
697
698
|
| +-- Safe env var + prefix filtering
|
|
698
699
|
| +-- Dataflow telemetry source categorization (os.platform/arch → telemetry_read)
|
|
699
700
|
| +-- DEP whitelist (es5-ext, bootstrap-sass) + npm alias skip
|
|
701
|
+
| +-- IOC wildcard audit (v2.5.8): FPR 10.8% → 6.0%
|
|
700
702
|
|
|
|
701
703
|
+-- Per-File Max Scoring (v2.2.11)
|
|
702
704
|
| +-- Score = max(file_scores) + package_level_score
|
|
703
705
|
| +-- Eliminates score accumulation across many files
|
|
704
706
|
| +-- Package-level threats (lifecycle, typosquat, IOC) scored separately
|
|
705
707
|
|
|
|
708
|
+
+-- Sandbox Monkey-Patching Preload (v2.4.9)
|
|
709
|
+
| +-- Runtime time manipulation (Date.now, setTimeout→0, setInterval→immediate)
|
|
710
|
+
| +-- Network/filesystem/process/env interception and logging
|
|
711
|
+
| +-- Multi-run [0h, 72h, 7d] for time-bomb detection (T1497.003)
|
|
712
|
+
|
|
|
713
|
+
+-- Security Audit (v2.5.0-v2.5.6)
|
|
714
|
+
| +-- 41 issues remediated (14 CRITICAL, 18 HIGH, 9 MEDIUM)
|
|
715
|
+
| +-- Native addon path traversal, atomic writes, AST bypasses
|
|
716
|
+
|
|
|
706
717
|
+-- Paranoid Mode (ultra-strict)
|
|
707
|
-
+-- Docker Sandbox (behavioral analysis, network capture, canary tokens, CI-aware)
|
|
718
|
+
+-- Docker Sandbox (behavioral analysis, network capture, canary tokens, CI-aware, preload)
|
|
708
719
|
+-- Zero-Day Monitor (internal: npm + PyPI RSS polling, Discord alerts, daily report)
|
|
709
720
|
|
|
|
710
721
|
v
|
|
@@ -725,8 +736,8 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
|
725
736
|
|--------|--------|---------|
|
|
726
737
|
| **Wild TPR** (Datadog 17K) | **88.2%** raw · **~100%** adjusted | 17,922 real malware samples. 2,077 misses are all out-of-scope (see below) |
|
|
727
738
|
| **TPR** (Ground Truth) | **91.8%** (45/49) | 51 real-world attacks (49 active). 4 out-of-scope: browser-only (3) + FP-risky (1) |
|
|
728
|
-
| **FPR** (Benign, global) | **
|
|
729
|
-
| **ADR** (Adversarial + Holdout) | **98.
|
|
739
|
+
| **FPR** (Benign, global) | **6.0%** (32/529) | 529 npm packages, real source code via `npm pack`, threshold > 20 |
|
|
740
|
+
| **ADR** (Adversarial + Holdout) | **98.8%** (82/83) | 43 adversarial + 40 holdout evasive samples. 1 documented miss: `require-cache-poison` (accepted trade-off) |
|
|
730
741
|
|
|
731
742
|
**Datadog 17K benchmark** — [DataDog Malicious Software Packages Dataset](https://github.com/DataDog/malicious-software-packages-dataset), 17,922 real malware samples (npm). Raw TPR: 88.2% (15,810/17,922). The 2,077 misses (score=0) were manually categorized:
|
|
732
743
|
|
|
@@ -747,7 +758,7 @@ All 2,077 misses lack Node.js malware patterns. MUAD'DIB performs AST-based Node
|
|
|
747
758
|
| Large (50-100 JS files) | 40 | 10 | 25.0% |
|
|
748
759
|
| Very large (100+ JS files) | 62 | 25 | 40.3% |
|
|
749
760
|
|
|
750
|
-
**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) →
|
|
761
|
+
**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)
|
|
751
762
|
|
|
752
763
|
**Holdout progression** (pre-tuning scores, rules frozen):
|
|
753
764
|
|
|
@@ -761,11 +772,11 @@ All 2,077 misses lack Node.js malware patterns. MUAD'DIB performs AST-based Node
|
|
|
761
772
|
|
|
762
773
|
- **Wild TPR** (Datadog Benchmark): detection rate on 17,922 real malware packages from the [DataDog Malicious Software Packages Dataset](https://github.com/DataDog/malicious-software-packages-dataset). Raw 88.2% (15,810/17,922). Adjusted ~100% on JS/Node.js malware when excluding out-of-scope samples (1,233 phishing HTML pages, 824 native binaries, 20 corrected libraries). See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md#14-datadog-17k-benchmark).
|
|
763
774
|
- **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).
|
|
764
|
-
- **FPR** (False Positive Rate): packages scoring > 20 out of 529 real npm packages (source code scanned, not empty dirs).
|
|
765
|
-
- **ADR** (Adversarial Detection Rate): detection rate on
|
|
775
|
+
- **FPR** (False Positive Rate): packages scoring > 20 out of 529 real npm packages (source code scanned, not empty dirs).
|
|
776
|
+
- **ADR** (Adversarial Detection Rate): detection rate on 83 evasive malicious samples — 43 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).
|
|
766
777
|
- **Holdout** (pre-tuning): detection rate on 10 unseen samples with rules frozen (measures generalization)
|
|
767
778
|
|
|
768
|
-
Datasets: 17,922 Datadog malware samples, 529 npm + 132 PyPI benign packages,
|
|
779
|
+
Datasets: 17,922 Datadog malware samples, 529 npm + 132 PyPI benign packages, 83 adversarial/holdout samples, 51 ground-truth attacks (65 documented malware packages). **1656 tests**, 86% code coverage.
|
|
769
780
|
|
|
770
781
|
See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) for the full experimental protocol.
|
|
771
782
|
|
|
@@ -801,12 +812,12 @@ npm test
|
|
|
801
812
|
|
|
802
813
|
### Testing
|
|
803
814
|
|
|
804
|
-
- **
|
|
815
|
+
- **1656 unit/integration tests** across 42 modular test files - 86% code coverage via [Codecov](https://codecov.io/gh/DNSZLSK/muad-dib)
|
|
805
816
|
- **56 fuzz tests** - Malformed YAML, invalid JSON, binary files, ReDoS, unicode, 10MB inputs
|
|
806
817
|
- **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)
|
|
807
|
-
- **
|
|
818
|
+
- **83 adversarial/holdout samples** - 43 adversarial + 40 holdout, 82/83 detection rate (98.8% ADR). 1 documented miss: `require-cache-poison` (accepted trade-off)
|
|
808
819
|
- **Ground truth validation** - 51 real-world attacks (45/49 detected = 91.8% TPR). 4 out-of-scope: browser-only (3) + FP-risky (1)
|
|
809
|
-
- **False positive validation** -
|
|
820
|
+
- **False positive validation** - 6.0% FPR global (32/529) on real npm source code via `npm pack`
|
|
810
821
|
- **ESLint security audit** - `eslint-plugin-security` with 14 rules enabled
|
|
811
822
|
|
|
812
823
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"date": "2026-03-06",
|
|
3
|
-
"timestamp": "2026-03-
|
|
3
|
+
"timestamp": "2026-03-06T19:26:22.664Z",
|
|
4
4
|
"embed": {
|
|
5
5
|
"embeds": [
|
|
6
6
|
{
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"name": "Top Suspects",
|
|
37
|
-
"value": "1. **npm/test-dedup-detection-
|
|
37
|
+
"value": "1. **npm/test-dedup-detection-1772825182189@1.0.0** — 1 finding(s)",
|
|
38
38
|
"inline": false
|
|
39
39
|
}
|
|
40
40
|
],
|
|
41
41
|
"footer": {
|
|
42
|
-
"text": "MUAD'DIB - Daily summary | 2026-03-06
|
|
42
|
+
"text": "MUAD'DIB - Daily summary | 2026-03-06 19:26:22 UTC"
|
|
43
43
|
},
|
|
44
|
-
"timestamp": "2026-03-
|
|
44
|
+
"timestamp": "2026-03-06T19:26:22.664Z"
|
|
45
45
|
}
|
|
46
46
|
]
|
|
47
47
|
},
|
package/package.json
CHANGED
|
@@ -858,12 +858,24 @@ function handleCallExpression(node, ctx) {
|
|
|
858
858
|
});
|
|
859
859
|
} else {
|
|
860
860
|
const isConstant = hasOnlyStringLiteralArgs(node);
|
|
861
|
+
let severity = isConstant ? 'LOW' : 'HIGH';
|
|
862
|
+
let message = isConstant
|
|
863
|
+
? 'eval() with constant string literal (low risk, globalThis polyfill pattern).'
|
|
864
|
+
: 'Dangerous call "eval" with dynamic expression detected.';
|
|
865
|
+
|
|
866
|
+
// Audit fix: even string-literal eval is dangerous if content contains dangerous APIs
|
|
867
|
+
if (isConstant && node.arguments[0]?.value) {
|
|
868
|
+
const val = node.arguments[0].value;
|
|
869
|
+
if (/\b(require|import|exec|execSync|spawn|child_process|\.readFile|\.writeFile|process\.env|\.homedir)\b/.test(val)) {
|
|
870
|
+
severity = 'HIGH';
|
|
871
|
+
message = `eval() with dangerous API in string literal: "${val.substring(0, 100)}"`;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
861
875
|
ctx.threats.push({
|
|
862
876
|
type: 'dangerous_call_eval',
|
|
863
|
-
severity
|
|
864
|
-
message
|
|
865
|
-
? 'eval() with constant string literal (low risk, globalThis polyfill pattern).'
|
|
866
|
-
: 'Dangerous call "eval" with dynamic expression detected.',
|
|
877
|
+
severity,
|
|
878
|
+
message,
|
|
867
879
|
file: ctx.relFile
|
|
868
880
|
});
|
|
869
881
|
}
|
package/src/scanner/ast.js
CHANGED
|
@@ -130,7 +130,12 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
130
130
|
'nodejs.org', 'yarnpkg.com',
|
|
131
131
|
'pypi.org', 'files.pythonhosted.org'
|
|
132
132
|
];
|
|
133
|
-
if (urlMatches.length > 0 && urlMatches.every(u =>
|
|
133
|
+
if (urlMatches.length > 0 && urlMatches.every(u => {
|
|
134
|
+
try {
|
|
135
|
+
const hostname = new URL(u).hostname;
|
|
136
|
+
return SAFE_FETCH_DOMAINS.some(d => hostname === d || hostname.endsWith('.' + d));
|
|
137
|
+
} catch { return false; }
|
|
138
|
+
})) {
|
|
134
139
|
ctx.fetchOnlySafeDomains = true;
|
|
135
140
|
}
|
|
136
141
|
}
|
package/src/scoring.js
CHANGED
|
@@ -119,19 +119,35 @@ const FP_COUNT_THRESHOLDS = {
|
|
|
119
119
|
credential_tampering: { maxCount: 5, to: 'LOW' }
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
-
// Types exempt from dist/ downgrade — IOC matches
|
|
122
|
+
// Types exempt from dist/ downgrade — IOC matches, lifecycle scripts, and
|
|
123
|
+
// high-confidence compound detections are always real even in dist/ files
|
|
123
124
|
const DIST_EXEMPT_TYPES = new Set([
|
|
124
125
|
'ioc_match', 'known_malicious_package', 'pypi_malicious_package', 'shai_hulud_marker',
|
|
125
126
|
'lifecycle_script', 'lifecycle_shell_pipe',
|
|
126
|
-
'lifecycle_added_critical', 'lifecycle_added_high', 'lifecycle_modified'
|
|
127
|
+
'lifecycle_added_critical', 'lifecycle_added_high', 'lifecycle_modified',
|
|
128
|
+
// Compound detections — require multiple correlated signals, not single-pattern FPs
|
|
129
|
+
'zlib_inflate_eval', // zlib + base64 + eval (event-stream pattern)
|
|
130
|
+
'fetch_decrypt_exec', // fetch + decrypt + eval (steganographic chain)
|
|
131
|
+
'download_exec_binary', // download + chmod + exec (binary dropper)
|
|
132
|
+
'cross_file_dataflow', // credential read → network exfil across files
|
|
133
|
+
'staged_eval_decode', // eval(atob(...)) (explicit payload staging)
|
|
134
|
+
'reverse_shell' // net.Socket + connect + pipe (always malicious)
|
|
127
135
|
]);
|
|
128
136
|
|
|
129
137
|
// Regex matching dist/build/minified/bundled file paths
|
|
130
138
|
const DIST_FILE_RE = /(?:^|[/\\])(?:dist|build)[/\\]|\.min\.js$|\.bundle\.js$/i;
|
|
131
139
|
|
|
132
|
-
// Types exempt from reachability downgrade — IOC matches, lifecycle, and package-level types
|
|
140
|
+
// Types exempt from reachability downgrade — IOC matches, lifecycle, and package-level types.
|
|
141
|
+
// NOTE: Uses the base IOC/lifecycle exempt set, NOT full DIST_EXEMPT_TYPES.
|
|
142
|
+
// Compound detections (zlib_inflate_eval, staged_eval_decode, etc.) should still be
|
|
143
|
+
// downgraded if the file is truly unreachable, since unreachable code cannot execute.
|
|
144
|
+
const REACHABILITY_BASE_EXEMPT = new Set([
|
|
145
|
+
'ioc_match', 'known_malicious_package', 'pypi_malicious_package', 'shai_hulud_marker',
|
|
146
|
+
'lifecycle_script', 'lifecycle_shell_pipe',
|
|
147
|
+
'lifecycle_added_critical', 'lifecycle_added_high', 'lifecycle_modified'
|
|
148
|
+
]);
|
|
133
149
|
const REACHABILITY_EXEMPT_TYPES = new Set([
|
|
134
|
-
...
|
|
150
|
+
...REACHABILITY_BASE_EXEMPT,
|
|
135
151
|
'cross_file_dataflow',
|
|
136
152
|
'typosquat_detected', 'pypi_typosquat_detected',
|
|
137
153
|
'pypi_malicious_package',
|
|
@@ -188,11 +204,11 @@ function applyFPReductions(threats, reachableFiles, packageName) {
|
|
|
188
204
|
|
|
189
205
|
const totalThreats = threats.length;
|
|
190
206
|
|
|
191
|
-
// P4: Plugin loader pattern — packages with
|
|
207
|
+
// P4: Plugin loader pattern — packages with 5+ dynamic_require + dynamic_import combined
|
|
192
208
|
// are legitimate plugin systems (webpack, eslint, karma, knex, jasmine, gatsby).
|
|
193
|
-
//
|
|
209
|
+
// Threshold raised from >1 to >4 (audit fix: >1 was trivially exploitable).
|
|
194
210
|
const pluginLoaderCount = (typeCounts.dynamic_require || 0) + (typeCounts.dynamic_import || 0);
|
|
195
|
-
if (pluginLoaderCount >
|
|
211
|
+
if (pluginLoaderCount > 4) {
|
|
196
212
|
for (const t of threats) {
|
|
197
213
|
if ((t.type === 'dynamic_require' || t.type === 'dynamic_import') && t.severity === 'HIGH') {
|
|
198
214
|
t.severity = 'LOW';
|
|
@@ -208,10 +224,11 @@ function applyFPReductions(threats, reachableFiles, packageName) {
|
|
|
208
224
|
const rule = FP_COUNT_THRESHOLDS[t.type];
|
|
209
225
|
if (rule && typeCounts[t.type] > rule.maxCount && (!rule.from || t.severity === rule.from)) {
|
|
210
226
|
const typeRatio = typeCounts[t.type] / totalThreats;
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
227
|
+
// suspicious_dataflow: partial bypass of percentage guard up to 80%.
|
|
228
|
+
// Complex apps (SMTP, monitoring) have 50-80% dataflow findings — still downgrade.
|
|
229
|
+
// But if dataflow is >80% of ALL findings, it may be real targeted exfiltration.
|
|
230
|
+
// (Audit fix: full bypass was exploitable — 4+ dataflow patterns = all LOW.)
|
|
231
|
+
if (typeRatio < 0.5 || (t.type === 'suspicious_dataflow' && typeRatio < 0.8)) {
|
|
215
232
|
t.severity = rule.to;
|
|
216
233
|
}
|
|
217
234
|
}
|
|
@@ -232,22 +249,22 @@ function applyFPReductions(threats, reachableFiles, packageName) {
|
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
// HTTP client prototype whitelist: packages with >20 prototype_hook hits
|
|
235
|
-
// targeting HTTP
|
|
252
|
+
// targeting HTTP class names are legitimate HTTP clients/frameworks.
|
|
253
|
+
// Audit fix: narrowed regex — 'get','delete','command' matched getCredentials, deleteAccount.
|
|
236
254
|
if (t.type === 'prototype_hook' && (t.severity === 'HIGH' || t.severity === 'CRITICAL') &&
|
|
237
255
|
typeCounts.prototype_hook > 20) {
|
|
238
|
-
const HTTP_PROTO_RE = /\b(Request|Response|
|
|
256
|
+
const HTTP_PROTO_RE = /\b(Request|Response|IncomingMessage|ClientRequest|ServerResponse|fetch)\b/i;
|
|
239
257
|
if (HTTP_PROTO_RE.test(t.message)) {
|
|
240
258
|
t.severity = 'MEDIUM';
|
|
241
259
|
}
|
|
242
260
|
}
|
|
243
261
|
|
|
244
|
-
// Dist/build/minified files: bundler artifacts get severity downgraded
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
// IOC matches and lifecycle scripts are exempt (DIST_EXEMPT_TYPES).
|
|
262
|
+
// Dist/build/minified files: bundler artifacts get severity downgraded one notch.
|
|
263
|
+
// Reduced from two-notch (audit fix): 2-notch made dist/ attacks invisible (CRITICAL→MEDIUM=3pts).
|
|
264
|
+
// Compound detections are exempt (DIST_EXEMPT_TYPES).
|
|
248
265
|
if (t.file && !DIST_EXEMPT_TYPES.has(t.type) && DIST_FILE_RE.test(t.file)) {
|
|
249
|
-
if (t.severity === 'CRITICAL') t.severity = '
|
|
250
|
-
else if (t.severity === 'HIGH') t.severity = '
|
|
266
|
+
if (t.severity === 'CRITICAL') t.severity = 'HIGH';
|
|
267
|
+
else if (t.severity === 'HIGH') t.severity = 'MEDIUM';
|
|
251
268
|
else if (t.severity === 'MEDIUM') t.severity = 'LOW';
|
|
252
269
|
}
|
|
253
270
|
|
package/_test_aiweapon.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
const { run } = require('./src/index.js');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
(async () => {
|
|
5
|
-
const dir = path.join('datasets/adversarial/ai-agent-weaponization');
|
|
6
|
-
const result = await run(dir, { _capture: true });
|
|
7
|
-
console.log('Score:', result.summary.riskScore);
|
|
8
|
-
console.log('MaxFile:', result.summary.maxFileScore, 'Pkg:', result.summary.packageScore);
|
|
9
|
-
for (const t of result.threats) {
|
|
10
|
-
console.log(` ${t.severity.padEnd(8)} ${t.type.padEnd(30)} ${(t.file || '').substring(0, 50)}`);
|
|
11
|
-
}
|
|
12
|
-
})();
|
package/_test_fp.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const { applyFPReductions } = require('./src/scoring.js');
|
|
2
|
-
|
|
3
|
-
const threats = [
|
|
4
|
-
{ type: 'reverse_shell', severity: 'HIGH', message: 'JS reverse shell', file: 'dist\\chunks\\cli-api.js' },
|
|
5
|
-
{ type: 'env_proxy_intercept', severity: 'HIGH', message: 'new Proxy(process.env)', file: 'dist\\module-evaluator.js' },
|
|
6
|
-
{ type: 'prototype_hook', severity: 'MEDIUM', message: 'WebSocket.prototype.addEventListener', file: 'dist\\chunks\\cli-api.js' }
|
|
7
|
-
];
|
|
8
|
-
|
|
9
|
-
console.log('BEFORE:', threats.map(t => `${t.type}: ${t.severity}`));
|
|
10
|
-
applyFPReductions(threats, null, null);
|
|
11
|
-
console.log('AFTER:', threats.map(t => `${t.type}: ${t.severity}`));
|
package/_test_fp2.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const { applyFPReductions } = require('./src/scoring.js');
|
|
3
|
-
|
|
4
|
-
// Simulate the exact vitest threats from the scan
|
|
5
|
-
const threats = [
|
|
6
|
-
{ type: 'prototype_hook', severity: 'MEDIUM', message: 'WebSocket.prototype.addEventListener overridden — native API hooking for traffic interception.', file: 'dist\\chunks\\cli-api.B7PN_QUv.js', count: 1 },
|
|
7
|
-
{ type: 'prototype_hook', severity: 'MEDIUM', message: 'WebSocket.prototype.removeEventListener overridden — native API hooking for traffic interception.', file: 'dist\\chunks\\cli-api.B7PN_QUv.js', count: 1 },
|
|
8
|
-
{ type: 'env_access', severity: 'LOW', message: 'Dynamic access to process.env (variable key).', file: 'dist\\chunks\\cli-api.B7PN_QUv.js', count: 1 },
|
|
9
|
-
{ type: 'reverse_shell', severity: 'HIGH', message: 'JavaScript reverse shell: net.Socket + connect() + pipe to shell process stdin/stdout.', file: 'dist\\chunks\\cli-api.B7PN_QUv.js', count: 1 },
|
|
10
|
-
{ type: 'env_access', severity: 'LOW', message: 'Dynamic access to process.env (variable key).', file: 'dist\\chunks\\init.B6MLFIaN.js', count: 1 },
|
|
11
|
-
{ type: 'dynamic_import', severity: 'MEDIUM', message: 'Dynamic import() with computed argument (possible obfuscation).', file: 'dist\\chunks\\traces.CCmnQaNT.js', count: 1 },
|
|
12
|
-
{ type: 'module_compile', severity: 'LOW', message: 'module._compile() detected.', file: 'dist\\chunks\\vm.D3epNOPZ.js', count: 1 },
|
|
13
|
-
{ type: 'module_compile_dynamic', severity: 'LOW', message: 'In-memory code execution.', file: 'dist\\chunks\\vm.D3epNOPZ.js', count: 1 },
|
|
14
|
-
{ type: 'require_cache_poison', severity: 'LOW', message: 'require.cache accessed.', file: 'dist\\chunks\\vm.D3epNOPZ.js', count: 1 },
|
|
15
|
-
{ type: 'dynamic_require', severity: 'LOW', message: 'Dynamic require() with variable argument.', file: 'dist\\chunks\\vm.D3epNOPZ.js', count: 1 },
|
|
16
|
-
{ type: 'dynamic_import', severity: 'MEDIUM', message: 'Dynamic import() with computed argument (possible obfuscation).', file: 'dist\\module-evaluator.js', count: 1 },
|
|
17
|
-
{ type: 'env_access', severity: 'LOW', message: 'Dynamic access to process.env (variable key).', file: 'dist\\module-evaluator.js', count: 1 },
|
|
18
|
-
{ type: 'env_proxy_intercept', severity: 'HIGH', message: 'new Proxy(process.env) detected — intercepts all environment variable access.', file: 'dist\\module-evaluator.js', count: 1 },
|
|
19
|
-
{ type: 'suspicious_dataflow', severity: 'MEDIUM', message: 'Suspicious flow: credentials read (stuff) + network send (request)', file: 'dist\\chunks\\cli-api.B7PN_QUv.js', count: 1 },
|
|
20
|
-
{ type: 'suspicious_dataflow', severity: 'LOW', message: 'Suspicious flow: credentials read (stuff) + network send (get)', file: 'dist\\chunks\\init.B6MLFIaN.js', count: 1 },
|
|
21
|
-
{ type: 'suspicious_dataflow', severity: 'MEDIUM', message: 'Suspicious flow: credentials read (stuff) + network send (get)', file: 'dist\\module-evaluator.js', count: 1 }
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
console.log('BEFORE:');
|
|
25
|
-
threats.forEach(t => console.log(` ${t.severity.padEnd(8)} ${t.type.padEnd(25)} ${t.file}`));
|
|
26
|
-
|
|
27
|
-
applyFPReductions(threats, null, 'vitest');
|
|
28
|
-
|
|
29
|
-
console.log('\nAFTER:');
|
|
30
|
-
threats.forEach(t => console.log(` ${t.severity.padEnd(8)} ${t.type.padEnd(25)} ${t.file}`));
|
|
31
|
-
|
|
32
|
-
// Now check scoring
|
|
33
|
-
const { calculateRiskScore } = require('./src/scoring.js');
|
|
34
|
-
const result = calculateRiskScore(threats);
|
|
35
|
-
console.log('\nScore:', result.riskScore, 'Level:', result.riskLevel);
|
|
36
|
-
console.log('MaxFile:', result.maxFileScore, 'Cross:', result.crossFileBonus, 'Pkg:', result.packageScore);
|
|
37
|
-
console.log('FileScores:', result.fileScores);
|
package/_test_fp3.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
const { run } = require('./src/index.js');
|
|
2
|
-
|
|
3
|
-
(async () => {
|
|
4
|
-
const result = await run(
|
|
5
|
-
'C:/Users/kposz/PyCharmMiscProject/CDA 2025 - 2026/muad-dib/.muaddib-cache/benign-tarballs/vitest/package',
|
|
6
|
-
{ _capture: true }
|
|
7
|
-
);
|
|
8
|
-
|
|
9
|
-
console.log('Score:', result.summary.riskScore);
|
|
10
|
-
const rs = result.threats.find(t => t.type === 'reverse_shell');
|
|
11
|
-
const ep = result.threats.find(t => t.type === 'env_proxy_intercept');
|
|
12
|
-
console.log('reverse_shell:', rs ? rs.severity : 'not found');
|
|
13
|
-
console.log('env_proxy_intercept:', ep ? ep.severity : 'not found');
|
|
14
|
-
|
|
15
|
-
// Show all threats
|
|
16
|
-
result.threats.forEach(t => {
|
|
17
|
-
console.log(` ${t.severity.padEnd(8)} ${t.type.padEnd(25)} ${t.file}`);
|
|
18
|
-
});
|
|
19
|
-
})();
|
package/_test_nodemailer.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const { run } = require('./src/index.js');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
(async () => {
|
|
5
|
-
const dir = path.join('.muaddib-cache/benign-tarballs/nodemailer/package');
|
|
6
|
-
const result = await run(dir, { _capture: true });
|
|
7
|
-
|
|
8
|
-
console.log('Score:', result.summary.riskScore);
|
|
9
|
-
|
|
10
|
-
// Show all suspicious_dataflow threats with full message
|
|
11
|
-
for (const t of result.threats) {
|
|
12
|
-
if (t.severity !== 'LOW') {
|
|
13
|
-
console.log(`\n${t.severity} ${t.type} [${t.file}]`);
|
|
14
|
-
console.log(` ${t.message}`);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
})();
|
package/_test_p4_detail.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
const { run } = require('./src/index.js');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
|
|
5
|
-
const packages = [
|
|
6
|
-
'next', 'gatsby', 'sails', 'webpack', 'jasmine', 'karma', 'knex',
|
|
7
|
-
'eslint', 'lerna', 'recoil', 'mathjs', 'nodemailer', 'ses'
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
const CACHE_DIR = '.muaddib-cache/benign-tarballs';
|
|
11
|
-
const SEVERITY_WEIGHTS = { CRITICAL: 25, HIGH: 10, MEDIUM: 3, LOW: 1 };
|
|
12
|
-
|
|
13
|
-
(async () => {
|
|
14
|
-
for (const pkg of packages) {
|
|
15
|
-
const cacheName = pkg.replace(/\//g, '-').replace(/^@/, '');
|
|
16
|
-
const dir = path.join(CACHE_DIR, cacheName, 'package');
|
|
17
|
-
if (!fs.existsSync(dir)) continue;
|
|
18
|
-
try {
|
|
19
|
-
const result = await run(dir, { _capture: true });
|
|
20
|
-
const score = result.summary.riskScore;
|
|
21
|
-
if (score <= 20) continue; // Only show FPs
|
|
22
|
-
console.log(`\n=== ${pkg} (score: ${score}) ===`);
|
|
23
|
-
console.log(` maxFile: ${result.summary.maxFileScore}, crossBonus: ${result.summary.crossFileBonus || 0}, pkgScore: ${result.summary.packageScore}`);
|
|
24
|
-
console.log(` mostSuspicious: ${result.summary.mostSuspiciousFile}`);
|
|
25
|
-
|
|
26
|
-
// Group by severity
|
|
27
|
-
const byType = {};
|
|
28
|
-
for (const t of result.threats) {
|
|
29
|
-
const key = `${t.severity}:${t.type}`;
|
|
30
|
-
if (!byType[key]) byType[key] = { severity: t.severity, type: t.type, count: 0, files: new Set(), points: 0 };
|
|
31
|
-
byType[key].count++;
|
|
32
|
-
byType[key].files.add(t.file);
|
|
33
|
-
byType[key].points += SEVERITY_WEIGHTS[t.severity] || 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Sort by points descending
|
|
37
|
-
const sorted = Object.values(byType).sort((a, b) => b.points - a.points);
|
|
38
|
-
for (const entry of sorted.slice(0, 8)) {
|
|
39
|
-
console.log(` ${entry.severity.padEnd(8)} ${entry.type.padEnd(30)} x${entry.count} = ${entry.points}pts files: ${[...entry.files].slice(0, 2).join(', ')}`);
|
|
40
|
-
}
|
|
41
|
-
} catch (e) {
|
|
42
|
-
console.log(`ERR ${pkg}: ${e.message}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
})();
|
package/_test_p4_detail2.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const { run } = require('./src/index.js');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
|
|
5
|
-
const packages = ['next', 'webpack', 'jasmine', 'knex', 'eslint', 'lerna', 'nodemailer'];
|
|
6
|
-
const CACHE_DIR = '.muaddib-cache/benign-tarballs';
|
|
7
|
-
|
|
8
|
-
(async () => {
|
|
9
|
-
for (const pkg of packages) {
|
|
10
|
-
const cacheName = pkg.replace(/\//g, '-').replace(/^@/, '');
|
|
11
|
-
const dir = path.join(CACHE_DIR, cacheName, 'package');
|
|
12
|
-
if (!fs.existsSync(dir)) continue;
|
|
13
|
-
try {
|
|
14
|
-
const result = await run(dir, { _capture: true });
|
|
15
|
-
const score = result.summary.riskScore;
|
|
16
|
-
console.log(`\n=== ${pkg} (score: ${score}) ===`);
|
|
17
|
-
console.log(` maxFile: ${result.summary.maxFileScore}, crossBonus: ${result.summary.crossFileBonus || '(not in summary)'}, pkgScore: ${result.summary.packageScore}`);
|
|
18
|
-
console.log(` mostSuspicious: ${result.summary.mostSuspiciousFile}`);
|
|
19
|
-
|
|
20
|
-
// Show high-value threats (MEDIUM+)
|
|
21
|
-
const highValue = result.threats.filter(t => t.severity !== 'LOW');
|
|
22
|
-
for (const t of highValue) {
|
|
23
|
-
console.log(` ${t.severity.padEnd(8)} ${t.type.padEnd(30)} ${(t.file || '').substring(0, 60)}`);
|
|
24
|
-
}
|
|
25
|
-
// Count LOWs
|
|
26
|
-
const lowCount = result.threats.filter(t => t.severity === 'LOW').length;
|
|
27
|
-
console.log(` + ${lowCount} LOW threats`);
|
|
28
|
-
} catch (e) {
|
|
29
|
-
console.log(`ERR ${pkg}: ${e.message}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
})();
|
package/_test_p4_quick.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
const { run } = require('./src/index.js');
|
|
2
|
-
|
|
3
|
-
const packages = [
|
|
4
|
-
'next', 'gatsby', 'sails', 'stencil', 'webpack', '@swc/core',
|
|
5
|
-
'vitest', 'jasmine', 'karma', 'knex', 'eslint', 'lerna',
|
|
6
|
-
'@changesets/cli', 'recoil', 'mathjs', 'nodemailer', 'ses', 'jspdf'
|
|
7
|
-
];
|
|
8
|
-
|
|
9
|
-
const CACHE_DIR = '.muaddib-cache/benign-tarballs';
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
|
|
13
|
-
(async () => {
|
|
14
|
-
const results = [];
|
|
15
|
-
for (const pkg of packages) {
|
|
16
|
-
const cacheName = pkg.replace(/\//g, '-').replace(/^@/, '');
|
|
17
|
-
const dir = path.join(CACHE_DIR, cacheName, 'package');
|
|
18
|
-
if (!fs.existsSync(dir)) {
|
|
19
|
-
console.log(`SKIP ${pkg} (not cached)`);
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
const result = await run(dir, { _capture: true });
|
|
24
|
-
const score = result.summary.riskScore;
|
|
25
|
-
const flagged = score > 20;
|
|
26
|
-
console.log(`${flagged ? 'FP' : 'OK'} ${String(score).padStart(3)} ${pkg}`);
|
|
27
|
-
if (flagged) results.push(pkg);
|
|
28
|
-
} catch (e) {
|
|
29
|
-
console.log(`ERR ${pkg}: ${e.message}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
console.log(`\n${results.length} FPs remaining: ${results.join(', ')}`);
|
|
33
|
-
})();
|
package/_test_regex.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
const DIST_FILE_RE = /(?:^|[/\\])(?:dist|build)[/\\]|\.min\.js$|\.bundle\.js$/i;
|
|
2
|
-
console.log('dist\\chunks\\file.js:', DIST_FILE_RE.test('dist\\chunks\\file.js'));
|
|
3
|
-
console.log('dist/chunks/file.js:', DIST_FILE_RE.test('dist/chunks/file.js'));
|
|
4
|
-
console.log('package\\dist\\file.js:', DIST_FILE_RE.test('package\\dist\\file.js'));
|
|
5
|
-
console.log('src\\index.js:', DIST_FILE_RE.test('src\\index.js'));
|
package/_vitest_result.json
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"target": "C:/Users/kposz/PyCharmMiscProject/CDA 2025 - 2026/muad-dib/.muaddib-cache/benign-tarballs/vitest/package",
|
|
3
|
-
"timestamp": "2026-03-06T10:35:36.370Z",
|
|
4
|
-
"threats": [
|
|
5
|
-
{
|
|
6
|
-
"type": "prototype_hook",
|
|
7
|
-
"severity": "MEDIUM",
|
|
8
|
-
"message": "WebSocket.prototype.addEventListener overridden — native API hooking for traffic interception.",
|
|
9
|
-
"file": "dist\\chunks\\cli-api.B7PN_QUv.js",
|
|
10
|
-
"count": 1,
|
|
11
|
-
"rule_id": "MUADDIB-AST-017",
|
|
12
|
-
"rule_name": "Native API Prototype Hooking",
|
|
13
|
-
"confidence": "high",
|
|
14
|
-
"references": [
|
|
15
|
-
"https://www.sygnia.co/blog/malicious-chalk-debug-npm-packages/",
|
|
16
|
-
"https://attack.mitre.org/techniques/T1557/"
|
|
17
|
-
],
|
|
18
|
-
"mitre": "T1557",
|
|
19
|
-
"playbook": "Prototype de fonction native modifie (fetch, XMLHttpRequest, http.request). Technique d'interception de trafic pour voler des donnees en transit. Supprimer le package. Auditer le trafic reseau recent.",
|
|
20
|
-
"points": 3
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"type": "prototype_hook",
|
|
24
|
-
"severity": "MEDIUM",
|
|
25
|
-
"message": "WebSocket.prototype.removeEventListener overridden — native API hooking for traffic interception.",
|
|
26
|
-
"file": "dist\\chunks\\cli-api.B7PN_QUv.js",
|
|
27
|
-
"count": 1,
|
|
28
|
-
"rule_id": "MUADDIB-AST-017",
|
|
29
|
-
"rule_name": "Native API Prototype Hooking",
|
|
30
|
-
"confidence": "high",
|
|
31
|
-
"references": [
|
|
32
|
-
"https://www.sygnia.co/blog/malicious-chalk-debug-npm-packages/",
|
|
33
|
-
"https://attack.mitre.org/techniques/T1557/"
|
|
34
|
-
],
|
|
35
|
-
"mitre": "T1557",
|
|
36
|
-
"playbook": "Prototype de fonction native modifie (fetch, XMLHttpRequest, http.request). Technique d'interception de trafic pour voler des donnees en transit. Supprimer le package. Auditer le trafic reseau recent.",
|
|
37
|
-
"points": 3
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"type": "env_access",
|
|
41
|
-
"severity": "LOW",
|
|
42
|
-
"message": "Dynamic access to process.env (variable key).",
|
|
43
|
-
"file": "dist\\chunks\\cli-api.B7PN_QUv.js",
|
|
44
|
-
"count": 3,
|
|
45
|
-
"rule_id": "MUADDIB-AST-002",
|
|
46
|
-
"rule_name": "Sensitive Environment Variable Access",
|
|
47
|
-
"confidence": "high",
|
|
48
|
-
"references": [
|
|
49
|
-
"https://blog.phylum.io/shai-hulud-npm-worm",
|
|
50
|
-
"https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions"
|
|
51
|
-
],
|
|
52
|
-
"mitre": "T1552.001",
|
|
53
|
-
"playbook": "Acces a une variable d'environnement sensible. Verifier si les donnees sont exfiltrees.",
|
|
54
|
-
"points": 1
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"type": "reverse_shell",
|
|
58
|
-
"severity": "HIGH",
|
|
59
|
-
"message": "JavaScript reverse shell: net.Socket + connect() + pipe to shell process stdin/stdout.",
|
|
60
|
-
"file": "dist\\chunks\\cli-api.B7PN_QUv.js",
|
|
61
|
-
"count": 1,
|
|
62
|
-
"rule_id": "MUADDIB-SHELL-002",
|
|
63
|
-
"rule_name": "Reverse Shell",
|
|
64
|
-
"confidence": "high",
|
|
65
|
-
"references": [
|
|
66
|
-
"https://attack.mitre.org/techniques/T1059/004/"
|
|
67
|
-
],
|
|
68
|
-
"mitre": "T1059.004",
|
|
69
|
-
"playbook": "CRITIQUE: Reverse shell detecte. Machine potentiellement compromise. Isoler immediatement.",
|
|
70
|
-
"points": 10
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"type": "env_access",
|
|
74
|
-
"severity": "LOW",
|
|
75
|
-
"message": "Dynamic access to process.env (variable key).",
|
|
76
|
-
"file": "dist\\chunks\\init.B6MLFIaN.js",
|
|
77
|
-
"count": 4,
|
|
78
|
-
"unreachable": true,
|
|
79
|
-
"rule_id": "MUADDIB-AST-002",
|
|
80
|
-
"rule_name": "Sensitive Environment Variable Access",
|
|
81
|
-
"confidence": "high",
|
|
82
|
-
"references": [
|
|
83
|
-
"https://blog.phylum.io/shai-hulud-npm-worm",
|
|
84
|
-
"https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions"
|
|
85
|
-
],
|
|
86
|
-
"mitre": "T1552.001",
|
|
87
|
-
"playbook": "Acces a une variable d'environnement sensible. Verifier si les donnees sont exfiltrees.",
|
|
88
|
-
"points": 1
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"type": "dynamic_import",
|
|
92
|
-
"severity": "MEDIUM",
|
|
93
|
-
"message": "Dynamic import() with computed argument (possible obfuscation).",
|
|
94
|
-
"file": "dist\\chunks\\traces.CCmnQaNT.js",
|
|
95
|
-
"count": 1,
|
|
96
|
-
"rule_id": "MUADDIB-AST-008",
|
|
97
|
-
"rule_name": "Dynamic import() of Dangerous Module",
|
|
98
|
-
"confidence": "high",
|
|
99
|
-
"references": [
|
|
100
|
-
"https://attack.mitre.org/techniques/T1027/"
|
|
101
|
-
],
|
|
102
|
-
"mitre": "T1027",
|
|
103
|
-
"playbook": "import() dynamique detecte. Technique d'evasion pour eviter la detection de require(). Verifier quel module est charge et son usage.",
|
|
104
|
-
"points": 3
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"type": "module_compile",
|
|
108
|
-
"severity": "LOW",
|
|
109
|
-
"message": "module._compile() detected — executes arbitrary code from string in module context (flatmap-stream pattern).",
|
|
110
|
-
"file": "dist\\chunks\\vm.D3epNOPZ.js",
|
|
111
|
-
"count": 1,
|
|
112
|
-
"unreachable": true,
|
|
113
|
-
"rule_id": "MUADDIB-AST-023",
|
|
114
|
-
"rule_name": "Module Compile Execution",
|
|
115
|
-
"confidence": "high",
|
|
116
|
-
"references": [
|
|
117
|
-
"https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident",
|
|
118
|
-
"https://attack.mitre.org/techniques/T1059/007/"
|
|
119
|
-
],
|
|
120
|
-
"mitre": "T1059",
|
|
121
|
-
"playbook": "CRITIQUE: module._compile() detecte. Cette API Node.js interne execute du code arbitraire a partir d'une chaine dans le contexte d'un module. Utilisee dans flatmap-stream pour executer un payload dechiffre sans ecrire sur disque. Isoler immediatement. Analyser la source de la chaine compilee.",
|
|
122
|
-
"points": 1
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
"type": "module_compile_dynamic",
|
|
126
|
-
"severity": "LOW",
|
|
127
|
-
"message": "In-memory code execution via Module._compile(). Common malware evasion technique.",
|
|
128
|
-
"file": "dist\\chunks\\vm.D3epNOPZ.js",
|
|
129
|
-
"count": 1,
|
|
130
|
-
"unreachable": true,
|
|
131
|
-
"rule_id": "MUADDIB-AST-025",
|
|
132
|
-
"rule_name": "Dynamic Module Compile Execution",
|
|
133
|
-
"confidence": "high",
|
|
134
|
-
"references": [
|
|
135
|
-
"https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident",
|
|
136
|
-
"https://attack.mitre.org/techniques/T1059/007/"
|
|
137
|
-
],
|
|
138
|
-
"mitre": "T1059",
|
|
139
|
-
"playbook": "CRITIQUE: Module._compile() avec argument dynamique (variable, expression). Execution de code en memoire sans ecriture sur disque. Technique d'evasion utilisee dans flatmap-stream et SANDWORM_MODE. Isoler immediatement. Tracer la source de la chaine compilee pour extraire le payload.",
|
|
140
|
-
"points": 1
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
"type": "require_cache_poison",
|
|
144
|
-
"severity": "LOW",
|
|
145
|
-
"message": "require.cache accessed — module cache poisoning to hijack or replace core Node.js modules.",
|
|
146
|
-
"file": "dist\\chunks\\vm.D3epNOPZ.js",
|
|
147
|
-
"count": 1,
|
|
148
|
-
"unreachable": true,
|
|
149
|
-
"rule_id": "MUADDIB-AST-019",
|
|
150
|
-
"rule_name": "Require Cache Poisoning",
|
|
151
|
-
"confidence": "high",
|
|
152
|
-
"references": [
|
|
153
|
-
"https://attack.mitre.org/techniques/T1574/006/"
|
|
154
|
-
],
|
|
155
|
-
"mitre": "T1574.006",
|
|
156
|
-
"playbook": "CRITIQUE: require.cache modifie pour hijacker des modules Node.js. Le code remplace les exports de modules charges (https, http, fs) pour intercepter toutes les requetes. Supprimer le package. Redemarrer le processus Node.js. Auditer le trafic reseau recent.",
|
|
157
|
-
"points": 1
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
"type": "dynamic_require",
|
|
161
|
-
"severity": "LOW",
|
|
162
|
-
"message": "Dynamic require() with variable argument (module name obfuscation).",
|
|
163
|
-
"file": "dist\\chunks\\vm.D3epNOPZ.js",
|
|
164
|
-
"count": 3,
|
|
165
|
-
"unreachable": true,
|
|
166
|
-
"rule_id": "MUADDIB-AST-006",
|
|
167
|
-
"rule_name": "Dynamic Require with Concatenation",
|
|
168
|
-
"confidence": "high",
|
|
169
|
-
"references": [
|
|
170
|
-
"https://attack.mitre.org/techniques/T1027/"
|
|
171
|
-
],
|
|
172
|
-
"mitre": "T1027",
|
|
173
|
-
"playbook": "require() avec concatenation detecte. Technique d'obfuscation pour masquer le module charge. Analyser les variables concatenees.",
|
|
174
|
-
"points": 1
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
"type": "dynamic_import",
|
|
178
|
-
"severity": "MEDIUM",
|
|
179
|
-
"message": "Dynamic import() with computed argument (possible obfuscation).",
|
|
180
|
-
"file": "dist\\module-evaluator.js",
|
|
181
|
-
"count": 1,
|
|
182
|
-
"rule_id": "MUADDIB-AST-008",
|
|
183
|
-
"rule_name": "Dynamic import() of Dangerous Module",
|
|
184
|
-
"confidence": "high",
|
|
185
|
-
"references": [
|
|
186
|
-
"https://attack.mitre.org/techniques/T1027/"
|
|
187
|
-
],
|
|
188
|
-
"mitre": "T1027",
|
|
189
|
-
"playbook": "import() dynamique detecte. Technique d'evasion pour eviter la detection de require(). Verifier quel module est charge et son usage.",
|
|
190
|
-
"points": 3
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
"type": "env_access",
|
|
194
|
-
"severity": "LOW",
|
|
195
|
-
"message": "Dynamic access to process.env (variable key).",
|
|
196
|
-
"file": "dist\\module-evaluator.js",
|
|
197
|
-
"count": 4,
|
|
198
|
-
"rule_id": "MUADDIB-AST-002",
|
|
199
|
-
"rule_name": "Sensitive Environment Variable Access",
|
|
200
|
-
"confidence": "high",
|
|
201
|
-
"references": [
|
|
202
|
-
"https://blog.phylum.io/shai-hulud-npm-worm",
|
|
203
|
-
"https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions"
|
|
204
|
-
],
|
|
205
|
-
"mitre": "T1552.001",
|
|
206
|
-
"playbook": "Acces a une variable d'environnement sensible. Verifier si les donnees sont exfiltrees.",
|
|
207
|
-
"points": 1
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
"type": "env_proxy_intercept",
|
|
211
|
-
"severity": "HIGH",
|
|
212
|
-
"message": "new Proxy(process.env) detected — intercepts all environment variable access.",
|
|
213
|
-
"file": "dist\\module-evaluator.js",
|
|
214
|
-
"count": 1,
|
|
215
|
-
"rule_id": "MUADDIB-AST-009",
|
|
216
|
-
"rule_name": "Environment Variable Proxy Interception",
|
|
217
|
-
"confidence": "high",
|
|
218
|
-
"references": [
|
|
219
|
-
"https://attack.mitre.org/techniques/T1552/001/"
|
|
220
|
-
],
|
|
221
|
-
"mitre": "T1552.001",
|
|
222
|
-
"playbook": "CRITIQUE: new Proxy(process.env) intercepte tous les acces aux variables d'environnement. Technique d'exfiltration silencieuse. Isoler la machine, regenerer tous les secrets.",
|
|
223
|
-
"points": 10
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
"type": "suspicious_dataflow",
|
|
227
|
-
"severity": "MEDIUM",
|
|
228
|
-
"message": "Suspicious flow: credentials read (npm_config_user_agent, process.env[dynamic], process.env[dynamic], npm_config_VITEST_MODULE_DIRECTORIES, process.env[dynamic]) + network send (request, request, net.connect, tls.connect, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, fetch, get, get, get, get, get, get, get, get, get, get, get, get, get, get, request, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get)",
|
|
229
|
-
"file": "dist\\chunks\\cli-api.B7PN_QUv.js",
|
|
230
|
-
"count": 1,
|
|
231
|
-
"rule_id": "MUADDIB-FLOW-001",
|
|
232
|
-
"rule_name": "Suspicious Data Flow",
|
|
233
|
-
"confidence": "high",
|
|
234
|
-
"references": [
|
|
235
|
-
"https://blog.phylum.io/shai-hulud-npm-worm"
|
|
236
|
-
],
|
|
237
|
-
"mitre": "T1041",
|
|
238
|
-
"playbook": "CRITIQUE: Code lit des credentials et les envoie sur le reseau. Exfiltration probable. Isoler la machine, regenerer tous les secrets.",
|
|
239
|
-
"points": 3
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
"type": "suspicious_dataflow",
|
|
243
|
-
"severity": "LOW",
|
|
244
|
-
"message": "Suspicious flow: credentials read (process.env[dynamic], process.env[dynamic], process.env[dynamic], process.env[dynamic]) + network send (get, fetch, get, post)",
|
|
245
|
-
"file": "dist\\chunks\\init.B6MLFIaN.js",
|
|
246
|
-
"count": 1,
|
|
247
|
-
"unreachable": true,
|
|
248
|
-
"rule_id": "MUADDIB-FLOW-001",
|
|
249
|
-
"rule_name": "Suspicious Data Flow",
|
|
250
|
-
"confidence": "high",
|
|
251
|
-
"references": [
|
|
252
|
-
"https://blog.phylum.io/shai-hulud-npm-worm"
|
|
253
|
-
],
|
|
254
|
-
"mitre": "T1041",
|
|
255
|
-
"playbook": "CRITIQUE: Code lit des credentials et les envoie sur le reseau. Exfiltration probable. Isoler la machine, regenerer tous les secrets.",
|
|
256
|
-
"points": 1
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
"type": "suspicious_dataflow",
|
|
260
|
-
"severity": "MEDIUM",
|
|
261
|
-
"message": "Suspicious flow: credentials read (process.env[dynamic], process.env[dynamic], process.env[dynamic], process.env[dynamic]) + network send (get, get)",
|
|
262
|
-
"file": "dist\\module-evaluator.js",
|
|
263
|
-
"count": 1,
|
|
264
|
-
"rule_id": "MUADDIB-FLOW-001",
|
|
265
|
-
"rule_name": "Suspicious Data Flow",
|
|
266
|
-
"confidence": "high",
|
|
267
|
-
"references": [
|
|
268
|
-
"https://blog.phylum.io/shai-hulud-npm-worm"
|
|
269
|
-
],
|
|
270
|
-
"mitre": "T1041",
|
|
271
|
-
"playbook": "CRITIQUE: Code lit des credentials et les envoie sur le reseau. Exfiltration probable. Isoler la machine, regenerer tous les secrets.",
|
|
272
|
-
"points": 3
|
|
273
|
-
}
|
|
274
|
-
],
|
|
275
|
-
"python": null,
|
|
276
|
-
"summary": {
|
|
277
|
-
"total": 16,
|
|
278
|
-
"critical": 0,
|
|
279
|
-
"high": 2,
|
|
280
|
-
"medium": 6,
|
|
281
|
-
"low": 8,
|
|
282
|
-
"riskScore": 28,
|
|
283
|
-
"riskLevel": "MEDIUM",
|
|
284
|
-
"globalRiskScore": 46,
|
|
285
|
-
"maxFileScore": 20,
|
|
286
|
-
"packageScore": 0,
|
|
287
|
-
"mostSuspiciousFile": "dist\\chunks\\cli-api.B7PN_QUv.js",
|
|
288
|
-
"fileScores": {
|
|
289
|
-
"dist\\chunks\\cli-api.B7PN_QUv.js": 20,
|
|
290
|
-
"dist\\chunks\\init.B6MLFIaN.js": 2,
|
|
291
|
-
"dist\\chunks\\traces.CCmnQaNT.js": 3,
|
|
292
|
-
"dist\\chunks\\vm.D3epNOPZ.js": 4,
|
|
293
|
-
"dist\\module-evaluator.js": 17
|
|
294
|
-
},
|
|
295
|
-
"breakdown": [
|
|
296
|
-
{
|
|
297
|
-
"rule": "MUADDIB-SHELL-002",
|
|
298
|
-
"type": "reverse_shell",
|
|
299
|
-
"points": 10,
|
|
300
|
-
"reason": "JavaScript reverse shell: net.Socket + connect() + pipe to shell process stdin/stdout."
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
"rule": "MUADDIB-AST-009",
|
|
304
|
-
"type": "env_proxy_intercept",
|
|
305
|
-
"points": 10,
|
|
306
|
-
"reason": "new Proxy(process.env) detected — intercepts all environment variable access."
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
"rule": "MUADDIB-AST-017",
|
|
310
|
-
"type": "prototype_hook",
|
|
311
|
-
"points": 3,
|
|
312
|
-
"reason": "WebSocket.prototype.addEventListener overridden — native API hooking for traffic interception."
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
"rule": "MUADDIB-AST-017",
|
|
316
|
-
"type": "prototype_hook",
|
|
317
|
-
"points": 3,
|
|
318
|
-
"reason": "WebSocket.prototype.removeEventListener overridden — native API hooking for traffic interception."
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
"rule": "MUADDIB-AST-008",
|
|
322
|
-
"type": "dynamic_import",
|
|
323
|
-
"points": 3,
|
|
324
|
-
"reason": "Dynamic import() with computed argument (possible obfuscation)."
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
"rule": "MUADDIB-AST-008",
|
|
328
|
-
"type": "dynamic_import",
|
|
329
|
-
"points": 3,
|
|
330
|
-
"reason": "Dynamic import() with computed argument (possible obfuscation)."
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
"rule": "MUADDIB-FLOW-001",
|
|
334
|
-
"type": "suspicious_dataflow",
|
|
335
|
-
"points": 3,
|
|
336
|
-
"reason": "Suspicious flow: credentials read (npm_config_user_agent, process.env[dynamic], process.env[dynamic], npm_config_VITEST_MODULE_DIRECTORIES, process.env[dynamic]) + network send (request, request, net.connect, tls.connect, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, fetch, get, get, get, get, get, get, get, get, get, get, get, get, get, get, request, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get, get)"
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
"rule": "MUADDIB-FLOW-001",
|
|
340
|
-
"type": "suspicious_dataflow",
|
|
341
|
-
"points": 3,
|
|
342
|
-
"reason": "Suspicious flow: credentials read (process.env[dynamic], process.env[dynamic], process.env[dynamic], process.env[dynamic]) + network send (get, get)"
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
"rule": "MUADDIB-AST-002",
|
|
346
|
-
"type": "env_access",
|
|
347
|
-
"points": 1,
|
|
348
|
-
"reason": "Dynamic access to process.env (variable key)."
|
|
349
|
-
},
|
|
350
|
-
{
|
|
351
|
-
"rule": "MUADDIB-AST-002",
|
|
352
|
-
"type": "env_access",
|
|
353
|
-
"points": 1,
|
|
354
|
-
"reason": "Dynamic access to process.env (variable key)."
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
"rule": "MUADDIB-AST-023",
|
|
358
|
-
"type": "module_compile",
|
|
359
|
-
"points": 1,
|
|
360
|
-
"reason": "module._compile() detected — executes arbitrary code from string in module context (flatmap-stream pattern)."
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
"rule": "MUADDIB-AST-025",
|
|
364
|
-
"type": "module_compile_dynamic",
|
|
365
|
-
"points": 1,
|
|
366
|
-
"reason": "In-memory code execution via Module._compile(). Common malware evasion technique."
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
"rule": "MUADDIB-AST-019",
|
|
370
|
-
"type": "require_cache_poison",
|
|
371
|
-
"points": 1,
|
|
372
|
-
"reason": "require.cache accessed — module cache poisoning to hijack or replace core Node.js modules."
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
"rule": "MUADDIB-AST-006",
|
|
376
|
-
"type": "dynamic_require",
|
|
377
|
-
"points": 1,
|
|
378
|
-
"reason": "Dynamic require() with variable argument (module name obfuscation)."
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
"rule": "MUADDIB-AST-002",
|
|
382
|
-
"type": "env_access",
|
|
383
|
-
"points": 1,
|
|
384
|
-
"reason": "Dynamic access to process.env (variable key)."
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
"rule": "MUADDIB-FLOW-001",
|
|
388
|
-
"type": "suspicious_dataflow",
|
|
389
|
-
"points": 1,
|
|
390
|
-
"reason": "Suspicious flow: credentials read (process.env[dynamic], process.env[dynamic], process.env[dynamic], process.env[dynamic]) + network send (get, fetch, get, post)"
|
|
391
|
-
}
|
|
392
|
-
]
|
|
393
|
-
},
|
|
394
|
-
"sandbox": null
|
|
395
|
-
}
|