muaddib-scanner 2.6.1 → 2.6.3
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 +93 -599
- package/package.json +4 -3
- package/src/ioc/scraper.js +35 -26
- package/src/ioc/updater.js +14 -1
- package/src/scoring.js +20 -10
- package/src/temporal-runner.js +26 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<a href="#usage">Usage</a> |
|
|
18
18
|
<a href="#features">Features</a> |
|
|
19
19
|
<a href="#vs-code">VS Code</a> |
|
|
20
|
-
<a href="#
|
|
20
|
+
<a href="#cicd">CI/CD</a>
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
<p align="center">
|
|
@@ -30,21 +30,19 @@
|
|
|
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
|
|
33
|
+
MUAD'DIB combines **14 parallel scanners** (129 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
|
|
|
37
37
|
## Positioning
|
|
38
38
|
|
|
39
|
-
MUAD'DIB is an educational tool and a free first line of defense. It detects **known** npm and PyPI threats (225,000+ IOCs) and
|
|
39
|
+
MUAD'DIB is an educational tool and a free first line of defense. It detects **known** npm and PyPI threats (225,000+ IOCs) and suspicious behavioral patterns.
|
|
40
40
|
|
|
41
41
|
**For enterprise protection**, use:
|
|
42
42
|
- [Socket.dev](https://socket.dev) - ML behavioral analysis, cloud sandboxing
|
|
43
43
|
- [Snyk](https://snyk.io) - Massive vulnerability database, CI/CD integrations
|
|
44
44
|
- [Opengrep](https://opengrep.dev) - Advanced dataflow analysis, Semgrep rules
|
|
45
45
|
|
|
46
|
-
MUAD'DIB does not replace these tools. It complements them for devs who want a quick, free check before installing an unknown package.
|
|
47
|
-
|
|
48
46
|
---
|
|
49
47
|
|
|
50
48
|
## Installation
|
|
@@ -83,14 +81,11 @@ Scans both npm (package.json, node_modules) and Python (requirements.txt, setup.
|
|
|
83
81
|
muaddib
|
|
84
82
|
```
|
|
85
83
|
|
|
86
|
-
Launches an interactive menu to guide you through all features.
|
|
87
|
-
|
|
88
84
|
### Safe install
|
|
89
85
|
|
|
90
86
|
```bash
|
|
91
87
|
muaddib install <package>
|
|
92
88
|
muaddib install lodash axios --save-dev
|
|
93
|
-
muaddib i express -g
|
|
94
89
|
muaddib install suspicious-pkg --force # Force install despite threats
|
|
95
90
|
```
|
|
96
91
|
|
|
@@ -104,17 +99,13 @@ Each scan displays a 0-100 risk score:
|
|
|
104
99
|
[SCORE] 58/100 [***********---------] HIGH
|
|
105
100
|
```
|
|
106
101
|
|
|
107
|
-
### Explain mode
|
|
102
|
+
### Explain mode
|
|
108
103
|
|
|
109
104
|
```bash
|
|
110
105
|
muaddib scan . --explain
|
|
111
106
|
```
|
|
112
107
|
|
|
113
|
-
Shows for each detection
|
|
114
|
-
- Rule ID
|
|
115
|
-
- MITRE ATT&CK technique
|
|
116
|
-
- References (articles, CVEs)
|
|
117
|
-
- Response playbook
|
|
108
|
+
Shows rule ID, MITRE ATT&CK technique, references, and response playbook for each detection.
|
|
118
109
|
|
|
119
110
|
### Export
|
|
120
111
|
|
|
@@ -129,7 +120,6 @@ muaddib scan . --sarif results.sarif # SARIF (GitHub Security)
|
|
|
129
120
|
```bash
|
|
130
121
|
muaddib scan . --fail-on critical # Fail only on CRITICAL
|
|
131
122
|
muaddib scan . --fail-on high # Fail on HIGH and CRITICAL (default)
|
|
132
|
-
muaddib scan . --fail-on medium # Fail on MEDIUM, HIGH, CRITICAL
|
|
133
123
|
```
|
|
134
124
|
|
|
135
125
|
### Paranoid mode
|
|
@@ -138,431 +128,86 @@ muaddib scan . --fail-on medium # Fail on MEDIUM, HIGH, CRITICAL
|
|
|
138
128
|
muaddib scan . --paranoid
|
|
139
129
|
```
|
|
140
130
|
|
|
141
|
-
Ultra-strict detection with lower tolerance.
|
|
131
|
+
Ultra-strict detection with lower tolerance. Detects any network access, subprocess execution, dynamic code evaluation, and sensitive file access.
|
|
142
132
|
|
|
143
|
-
###
|
|
133
|
+
### Webhook alerts
|
|
144
134
|
|
|
145
135
|
```bash
|
|
146
136
|
muaddib scan . --webhook "https://discord.com/api/webhooks/..."
|
|
147
137
|
```
|
|
148
138
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
### Real-time monitoring
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
muaddib watch .
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Daemon mode
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
muaddib daemon
|
|
161
|
-
muaddib daemon --webhook "https://discord.com/api/webhooks/..."
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Automatically monitors all `npm install` commands and scans new packages.
|
|
165
|
-
|
|
166
|
-
### Update IOCs (fast, ~5 seconds)
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
muaddib update
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
Loads the 225,000+ IOCs shipped in the package, merges YAML IOCs and additional GitHub sources (GenSecAI, DataDog). Run this after `npm install` for an instant IOC refresh.
|
|
139
|
+
Strict filtering (v2.1.2): alerts only for IOC matches, sandbox-confirmed threats, or canary token exfiltration.
|
|
173
140
|
|
|
174
|
-
###
|
|
141
|
+
### Behavioral anomaly detection (v2.0)
|
|
175
142
|
|
|
176
143
|
```bash
|
|
177
|
-
muaddib
|
|
144
|
+
muaddib scan . --temporal-full # All 4 temporal features
|
|
145
|
+
muaddib scan . --temporal # Sudden lifecycle script detection
|
|
146
|
+
muaddib scan . --temporal-ast # AST diff between versions
|
|
147
|
+
muaddib scan . --temporal-publish # Publish frequency anomaly
|
|
148
|
+
muaddib scan . --temporal-maintainer # Maintainer change detection
|
|
178
149
|
```
|
|
179
150
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
Sources:
|
|
183
|
-
- **OSV.dev npm dump** - Bulk download of all MAL-* entries
|
|
184
|
-
- **OSV.dev PyPI dump** - Bulk download of all PyPI MAL-* entries
|
|
185
|
-
- **GenSecAI Shai-Hulud 2.0 Detector** - Consolidated list of 700+ Shai-Hulud packages
|
|
186
|
-
- **DataDog Security Labs** - Consolidated IOCs from multiple vendors
|
|
187
|
-
- **OSSF Malicious Packages** - OpenSSF database (8000+ reports via OSV.dev)
|
|
188
|
-
- **GitHub Advisory Database** - Malware-tagged advisories
|
|
189
|
-
- **Snyk Known Malware** - Historical malware packages
|
|
190
|
-
- **Static IOCs** - Socket.dev, Phylum, npm-removed packages
|
|
151
|
+
Detects supply-chain attacks **before** they appear in IOC databases by analyzing changes between package versions. See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) for details.
|
|
191
152
|
|
|
192
|
-
### Docker
|
|
153
|
+
### Docker sandbox
|
|
193
154
|
|
|
194
155
|
```bash
|
|
195
156
|
muaddib sandbox <package-name>
|
|
196
157
|
muaddib sandbox <package-name> --strict
|
|
197
158
|
```
|
|
198
159
|
|
|
199
|
-
Dynamic analysis
|
|
200
|
-
|
|
201
|
-
Multi-layer monitoring:
|
|
202
|
-
- **System tracing** (strace): file access, process spawns, syscall monitoring
|
|
203
|
-
- **Network capture** (tcpdump): DNS resolutions with resolved IPs, HTTP requests (method, host, path, body), TLS SNI detection
|
|
204
|
-
- **Filesystem diff**: snapshot before/after install, detects files created in suspicious locations
|
|
205
|
-
- **Data exfiltration detection**: 16 sensitive patterns (tokens, credentials, SSH keys, private keys, .env)
|
|
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
|
-
- **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)
|
|
209
|
-
- **Scoring engine**: 0-100 risk score based on behavioral severity
|
|
210
|
-
|
|
211
|
-
Use `--strict` to block all non-essential outbound network traffic via iptables.
|
|
212
|
-
|
|
213
|
-
Requires Docker Desktop installed.
|
|
214
|
-
|
|
215
|
-
```bash
|
|
216
|
-
muaddib sandbox lodash # Safe package
|
|
217
|
-
muaddib sandbox suspicious-pkg # Analyze unknown package
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Sandbox network report
|
|
160
|
+
Dynamic analysis in an isolated Docker container: strace, tcpdump, filesystem diff, canary tokens, CI-aware environment, and monkey-patching preload for time-bomb detection (multi-run at [0h, 72h, 7d] offsets).
|
|
221
161
|
|
|
222
|
-
|
|
223
|
-
muaddib sandbox-report <package-name>
|
|
224
|
-
muaddib sandbox-report <package-name> --strict
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
Same as `sandbox` but displays a detailed network report: DNS resolutions, HTTP requests, TLS connections, blocked connections (strict mode), and data exfiltration alerts.
|
|
228
|
-
|
|
229
|
-
### Diff (compare versions)
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
muaddib diff <ref> [path]
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Compare threats between the current version and a previous commit/tag. Shows only **NEW** threats introduced since the reference point.
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
muaddib diff HEAD~1 # Compare with previous commit
|
|
239
|
-
muaddib diff v1.2.0 # Compare with tag
|
|
240
|
-
muaddib diff main # Compare with branch
|
|
241
|
-
muaddib diff abc1234 # Compare with specific commit
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
Example output:
|
|
245
|
-
```
|
|
246
|
-
[MUADDIB DIFF] Comparing abc1234 -> def5678
|
|
247
|
-
|
|
248
|
-
Risk Score: 25 -> 45 (+20 worse)
|
|
249
|
-
Threats: 3 -> 5
|
|
250
|
-
|
|
251
|
-
NEW threats: 2
|
|
252
|
-
REMOVED threats: 0
|
|
253
|
-
Unchanged: 3
|
|
254
|
-
|
|
255
|
-
NEW THREATS (introduced since v1.2.0)
|
|
256
|
-
------------------------------------
|
|
257
|
-
1. [HIGH] suspicious_dependency
|
|
258
|
-
Known malicious package detected
|
|
259
|
-
File: package.json
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Use in CI to only fail on **new** threats, not existing technical debt:
|
|
263
|
-
```yaml
|
|
264
|
-
- run: muaddib diff ${{ github.event.pull_request.base.sha }} --fail-on high
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
### Pre-commit hooks
|
|
162
|
+
### Other commands
|
|
268
163
|
|
|
269
164
|
```bash
|
|
270
|
-
muaddib
|
|
165
|
+
muaddib watch . # Real-time monitoring
|
|
166
|
+
muaddib daemon # Daemon mode (auto-scan npm install)
|
|
167
|
+
muaddib update # Update IOCs (fast, ~5s)
|
|
168
|
+
muaddib scrape # Full IOC refresh (~5min)
|
|
169
|
+
muaddib diff HEAD~1 # Compare threats with previous commit
|
|
170
|
+
muaddib init-hooks # Pre-commit hooks (husky/pre-commit/git)
|
|
171
|
+
muaddib scan . --breakdown # Explainable score decomposition
|
|
172
|
+
muaddib replay # Ground truth validation (46/49 TPR)
|
|
271
173
|
```
|
|
272
174
|
|
|
273
|
-
Automatically scan before each commit. Supports multiple hook systems:
|
|
274
|
-
|
|
275
|
-
```bash
|
|
276
|
-
muaddib init-hooks # Auto-detect (husky/pre-commit/git)
|
|
277
|
-
muaddib init-hooks --type husky # Force husky
|
|
278
|
-
muaddib init-hooks --type pre-commit # Force pre-commit framework
|
|
279
|
-
muaddib init-hooks --type git # Force native git hooks
|
|
280
|
-
muaddib init-hooks --mode diff # Only block NEW threats
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
#### With pre-commit framework
|
|
284
|
-
|
|
285
|
-
Add to `.pre-commit-config.yaml`:
|
|
286
|
-
```yaml
|
|
287
|
-
repos:
|
|
288
|
-
- repo: https://github.com/DNSZLSK/muad-dib
|
|
289
|
-
rev: v2.5.17
|
|
290
|
-
hooks:
|
|
291
|
-
- id: muaddib-scan # Scan all threats
|
|
292
|
-
# - id: muaddib-diff # Or: only new threats
|
|
293
|
-
# - id: muaddib-paranoid # Or: ultra-strict mode
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
#### With husky
|
|
297
|
-
|
|
298
|
-
```bash
|
|
299
|
-
npx husky add .husky/pre-commit "npx muaddib scan . --fail-on high"
|
|
300
|
-
# Or for diff mode:
|
|
301
|
-
npx husky add .husky/pre-commit "npx muaddib diff HEAD --fail-on high"
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
#### Remove hooks
|
|
305
|
-
|
|
306
|
-
```bash
|
|
307
|
-
muaddib remove-hooks [path]
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
Removes all MUAD'DIB hooks (husky and git native).
|
|
311
|
-
|
|
312
|
-
#### Native git hooks
|
|
313
|
-
|
|
314
|
-
```bash
|
|
315
|
-
muaddib init-hooks --type git
|
|
316
|
-
# Creates .git/hooks/pre-commit
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Zero-Day Monitor
|
|
320
|
-
|
|
321
|
-
MUAD'DIB continuously monitors npm and PyPI registries for new packages in real-time, scanning each one automatically with Docker sandbox analysis and webhook alerting. This runs internally on our infrastructure — detected threats feed into the IOC database and threat feed API.
|
|
322
|
-
|
|
323
|
-
### Score breakdown
|
|
324
|
-
|
|
325
|
-
```bash
|
|
326
|
-
muaddib scan . --breakdown
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
Shows explainable score breakdown: how each finding contributes to the final risk score, with per-rule weights and severity multipliers.
|
|
330
|
-
|
|
331
|
-
### Ground truth replay
|
|
332
|
-
|
|
333
|
-
```bash
|
|
334
|
-
muaddib replay
|
|
335
|
-
muaddib ground-truth
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
Replay real-world supply-chain attacks against the scanner to validate detection coverage. Current results: **46/49 detected (93.9% TPR)** from 51 samples (49 active).
|
|
339
|
-
|
|
340
|
-
4 out-of-scope misses: lottie-player, polyfill-io, trojanized-jquery (browser-only DOM attacks), websocket-rat (FP-risky pattern).
|
|
341
|
-
|
|
342
|
-
### Version check
|
|
343
|
-
|
|
344
|
-
MUAD'DIB automatically checks for new versions on startup and notifies you if an update is available.
|
|
345
|
-
|
|
346
175
|
---
|
|
347
176
|
|
|
348
177
|
## Features
|
|
349
178
|
|
|
350
|
-
###
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
```
|
|
383
|
-
[CRITICAL] Suspicious flow: credential read (readFileSync, GITHUB_TOKEN) + network send (fetch)
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
### GitHub Actions scanning
|
|
387
|
-
|
|
388
|
-
Detects malicious patterns in `.github/workflows/` YAML files, including Shai-Hulud 2.0 backdoor indicators.
|
|
389
|
-
|
|
390
|
-
### Detected attacks
|
|
391
|
-
|
|
392
|
-
| Campaign | Packages | Status |
|
|
393
|
-
|----------|----------|--------|
|
|
394
|
-
| Shai-Hulud v1 (Sept 2025) | @ctrl/tinycolor, ng2-file-upload | Detected |
|
|
395
|
-
| Shai-Hulud v2 (Nov 2025) | @asyncapi/specs, posthog-node, kill-port | Detected |
|
|
396
|
-
| Shai-Hulud v3 (Dec 2025) | @vietmoney/react-big-calendar | Detected |
|
|
397
|
-
| event-stream (2018) | flatmap-stream, event-stream | Detected |
|
|
398
|
-
| eslint-scope (2018) | eslint-scope | Detected |
|
|
399
|
-
| Protestware | node-ipc, colors, faker | Detected |
|
|
400
|
-
| Typosquats | crossenv, mongose, babelcli | Detected |
|
|
401
|
-
|
|
402
|
-
### Detected techniques
|
|
403
|
-
|
|
404
|
-
| Technique | MITRE | Detection |
|
|
405
|
-
|-----------|-------|-----------|
|
|
406
|
-
| Credential theft (.npmrc, .ssh) | T1552.001 | AST |
|
|
407
|
-
| Env var exfiltration | T1552.001 | AST |
|
|
408
|
-
| Remote code execution | T1105 | Pattern |
|
|
409
|
-
| Reverse shell | T1059.004 | Pattern |
|
|
410
|
-
| Dead man's switch | T1485 | Pattern |
|
|
411
|
-
| Obfuscated code | T1027 | Heuristics |
|
|
412
|
-
| JS obfuscation patterns | T1027.002 | Pattern detection |
|
|
413
|
-
| Shannon entropy (strings) | T1027 | Entropy calculation |
|
|
414
|
-
| Typosquatting (npm + PyPI) | T1195.002 | Levenshtein |
|
|
415
|
-
| Supply chain compromise | T1195.002 | IOC matching |
|
|
416
|
-
| PyPI malicious package | T1195.002 | IOC matching |
|
|
417
|
-
| Sandbox dynamic analysis | Multiple | Docker + strace + tcpdump |
|
|
418
|
-
| Sudden lifecycle script addition | T1195.002 | Temporal analysis |
|
|
419
|
-
| Dangerous API injection between versions | T1195.002 | Temporal AST diff |
|
|
420
|
-
| Publish frequency anomaly | T1195.002 | Registry metadata |
|
|
421
|
-
| Maintainer/publisher change | T1195.002 | Registry metadata |
|
|
422
|
-
| Canary token exfiltration | T1552.001 | Sandbox honey tokens |
|
|
423
|
-
| AI agent weaponization | T1059.004 | AST (s1ngularity/Nx flags) |
|
|
424
|
-
| AI config prompt injection | T1059.004 | File scanning (.cursorrules, CLAUDE.md) |
|
|
425
|
-
| Credential CLI theft (gh, gcloud, aws) | T1552.001 | AST |
|
|
426
|
-
| Binary dropper (chmod + exec /tmp) | T1105 | AST |
|
|
427
|
-
| Prototype hooking (fetch, XMLHttpRequest) | T1557 | AST |
|
|
428
|
-
| Workflow injection (.github/workflows) | T1195.002 | AST |
|
|
429
|
-
| Crypto wallet harvesting | T1005 | Dataflow |
|
|
430
|
-
| Require cache poisoning | T1574.001 | AST |
|
|
431
|
-
| Staged eval decode (eval+atob/Buffer) | T1140 | AST |
|
|
432
|
-
| Deobfuscation (string concat, charcode, base64, hex) | T1140 | AST pre-processing |
|
|
433
|
-
| Cross-file dataflow (inter-module exfiltration) | T1041 | Module graph |
|
|
434
|
-
|
|
435
|
-
---
|
|
436
|
-
|
|
437
|
-
## Supply Chain Anomaly Detection (v2.0)
|
|
438
|
-
|
|
439
|
-
MUAD'DIB 2.0 introduces a paradigm shift: from **IOC-based detection** (reactive, requires known threats) to **behavioral anomaly detection** (proactive, detects unknown threats by spotting suspicious changes).
|
|
440
|
-
|
|
441
|
-
Traditional supply-chain scanners rely on blocklists of known malicious packages. The problem: they can only detect threats AFTER they've been identified and reported. Attacks like **ua-parser-js** (2021), **event-stream** (2018), and **Shai-Hulud** (2025) went undetected for hours or days because no IOC existed yet.
|
|
442
|
-
|
|
443
|
-
MUAD'DIB 2.0 adds 5 behavioral detection features that can catch these attacks **before** they appear in any IOC database, by analyzing what changed between package versions.
|
|
444
|
-
|
|
445
|
-
### New features
|
|
446
|
-
|
|
447
|
-
#### 1. Sudden Lifecycle Script Detection (`--temporal`)
|
|
448
|
-
|
|
449
|
-
Detects when `preinstall`, `install`, or `postinstall` scripts suddenly appear in a new version of a package that never had them before. This is the #1 attack vector for supply-chain attacks.
|
|
450
|
-
|
|
451
|
-
```bash
|
|
452
|
-
muaddib scan . --temporal
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
#### 2. Temporal AST Diff (`--temporal-ast`)
|
|
456
|
-
|
|
457
|
-
Downloads the two latest versions of each dependency and compares their AST (Abstract Syntax Tree) to detect newly added dangerous APIs: `child_process`, `eval`, `Function`, `net.connect`, `process.env`, `fetch`, etc.
|
|
458
|
-
|
|
459
|
-
```bash
|
|
460
|
-
muaddib scan . --temporal-ast
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
#### 3. Publish Frequency Anomaly (`--temporal-publish`)
|
|
464
|
-
|
|
465
|
-
Detects abnormal publishing patterns: burst of versions in 24h, dormant package suddenly updated after 6+ months, rapid version succession (multiple releases in under 1h).
|
|
466
|
-
|
|
467
|
-
```bash
|
|
468
|
-
muaddib scan . --temporal-publish
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
#### 4. Maintainer Change Detection (`--temporal-maintainer`)
|
|
472
|
-
|
|
473
|
-
Detects changes in package maintainers between versions: new maintainer added, sole maintainer replaced (event-stream pattern), suspicious maintainer names, new publisher.
|
|
474
|
-
|
|
475
|
-
```bash
|
|
476
|
-
muaddib scan . --temporal-maintainer
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
#### 5. Canary Tokens / Honey Tokens (sandbox)
|
|
480
|
-
|
|
481
|
-
Injects fake credentials into the sandbox environment before installing a package. If the package attempts to exfiltrate these honey tokens via HTTP, DNS, filesystem, or stdout, it's flagged as confirmed malicious.
|
|
482
|
-
|
|
483
|
-
6 honeypot credentials are injected:
|
|
484
|
-
- `GITHUB_TOKEN` / `NPM_TOKEN` — Package registry tokens
|
|
485
|
-
- `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` — Cloud credentials
|
|
486
|
-
- `SLACK_WEBHOOK_URL` / `DISCORD_WEBHOOK_URL` — Messaging webhooks
|
|
487
|
-
|
|
488
|
-
Both dynamic tokens (random per session, from `canary-tokens.js`) and static fallback tokens (in `sandbox-runner.sh`) are used for defense in depth.
|
|
489
|
-
|
|
490
|
-
```bash
|
|
491
|
-
muaddib sandbox suspicious-package
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
### Full temporal scan
|
|
495
|
-
|
|
496
|
-
Enable all temporal analysis features at once:
|
|
497
|
-
|
|
498
|
-
```bash
|
|
499
|
-
muaddib scan . --temporal-full
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
### Usage examples
|
|
503
|
-
|
|
504
|
-
```bash
|
|
505
|
-
# Full behavioral scan (all 5 features)
|
|
506
|
-
muaddib scan . --temporal-full
|
|
507
|
-
|
|
508
|
-
# Only lifecycle script detection
|
|
509
|
-
muaddib scan . --temporal
|
|
510
|
-
|
|
511
|
-
# AST diff + maintainer change
|
|
512
|
-
muaddib scan . --temporal-ast --temporal-maintainer
|
|
513
|
-
|
|
514
|
-
# Sandbox with canary tokens (enabled by default)
|
|
515
|
-
muaddib sandbox suspicious-package
|
|
516
|
-
|
|
517
|
-
# Sandbox without canary tokens
|
|
518
|
-
muaddib sandbox suspicious-package --no-canary
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
### New detection rules (v2.0)
|
|
522
|
-
|
|
523
|
-
| Rule ID | Name | Severity | Feature |
|
|
524
|
-
|---------|------|----------|---------|
|
|
525
|
-
| MUADDIB-TEMPORAL-001 | Sudden Lifecycle Script Added (Critical) | CRITICAL | `--temporal` |
|
|
526
|
-
| MUADDIB-TEMPORAL-002 | Sudden Lifecycle Script Added | HIGH | `--temporal` |
|
|
527
|
-
| MUADDIB-TEMPORAL-003 | Lifecycle Script Modified | MEDIUM | `--temporal` |
|
|
528
|
-
| MUADDIB-TEMPORAL-AST-001 | Dangerous API Added (Critical) | CRITICAL | `--temporal-ast` |
|
|
529
|
-
| MUADDIB-TEMPORAL-AST-002 | Dangerous API Added (High) | HIGH | `--temporal-ast` |
|
|
530
|
-
| MUADDIB-TEMPORAL-AST-003 | Dangerous API Added (Medium) | MEDIUM | `--temporal-ast` |
|
|
531
|
-
| MUADDIB-PUBLISH-001 | Publish Burst Detected | HIGH | `--temporal-publish` |
|
|
532
|
-
| MUADDIB-PUBLISH-002 | Dormant Package Spike | HIGH | `--temporal-publish` |
|
|
533
|
-
| MUADDIB-PUBLISH-003 | Rapid Version Succession | MEDIUM | `--temporal-publish` |
|
|
534
|
-
| MUADDIB-MAINTAINER-001 | New Maintainer Added | HIGH | `--temporal-maintainer` |
|
|
535
|
-
| MUADDIB-MAINTAINER-002 | Suspicious Maintainer Detected | CRITICAL | `--temporal-maintainer` |
|
|
536
|
-
| MUADDIB-MAINTAINER-003 | Sole Maintainer Changed | HIGH | `--temporal-maintainer` |
|
|
537
|
-
| MUADDIB-MAINTAINER-004 | New Publisher Detected | MEDIUM | `--temporal-maintainer` |
|
|
538
|
-
| MUADDIB-CANARY-001 | Canary Token Exfiltration | CRITICAL | sandbox |
|
|
539
|
-
|
|
540
|
-
### Why it matters
|
|
541
|
-
|
|
542
|
-
These features detect attacks like:
|
|
543
|
-
- **Shai-Hulud** (2025): Would be caught by temporal lifecycle + AST diff (sudden `postinstall` + `child_process` added)
|
|
544
|
-
- **ua-parser-js** (2021): Would be caught by maintainer change + lifecycle script detection
|
|
545
|
-
- **event-stream** (2018): Would be caught by sole maintainer change + AST diff (new `flatmap-stream` dependency with `eval`)
|
|
546
|
-
- **coa/rc** (2021): Would be caught by publish burst + lifecycle script detection
|
|
547
|
-
|
|
548
|
-
All without needing a single IOC entry.
|
|
549
|
-
|
|
550
|
-
---
|
|
551
|
-
|
|
552
|
-
## IOC Sources
|
|
553
|
-
|
|
554
|
-
MUAD'DIB aggregates threat intelligence from verified sources only:
|
|
555
|
-
|
|
556
|
-
| Source | Type | Coverage |
|
|
557
|
-
|--------|------|----------|
|
|
558
|
-
| [OSV.dev npm dump](https://osv.dev) | Bulk zip | 200,000+ npm MAL-* entries |
|
|
559
|
-
| [OSV.dev PyPI dump](https://osv.dev) | Bulk zip | 14,000+ PyPI MAL-* entries |
|
|
560
|
-
| [GenSecAI Shai-Hulud Detector](https://github.com/gensecaihq/Shai-Hulud-2.0-Detector) | GitHub | 700+ Shai-Hulud packages |
|
|
561
|
-
| [DataDog Security Labs](https://github.com/DataDog/indicators-of-compromise) | GitHub | Consolidated IOCs from 7 vendors |
|
|
562
|
-
| [OSSF Malicious Packages](https://github.com/ossf/malicious-packages) | OSV API | 8000+ malware reports |
|
|
563
|
-
| [GitHub Advisory](https://github.com/advisories?query=type%3Amalware) | OSV API | Malware-tagged advisories |
|
|
564
|
-
| Snyk Known Malware | Static | Historical attacks |
|
|
565
|
-
| Socket.dev / Phylum | Static | Manual additions |
|
|
179
|
+
### 14 parallel scanners
|
|
180
|
+
|
|
181
|
+
| Scanner | Detection |
|
|
182
|
+
|---------|-----------|
|
|
183
|
+
| AST Parse (acorn) | eval, Function, credential theft, binary droppers, prototype hooks |
|
|
184
|
+
| Pattern Matching | Shell commands, reverse shells, dead man's switch |
|
|
185
|
+
| Dataflow Analysis | Credential read + network send (intra-file and cross-file) |
|
|
186
|
+
| Obfuscation Detection | JS obfuscation patterns (skip .min.js) |
|
|
187
|
+
| Deobfuscation Pre-processing | String concat, charcode, base64, hex array, const propagation |
|
|
188
|
+
| Inter-module Dataflow | Cross-file taint propagation (3-hop chains, class methods) |
|
|
189
|
+
| Intent Coherence | Intra-file source-sink pairing (credential + eval/network) |
|
|
190
|
+
| Typosquatting | npm + PyPI (Levenshtein distance) |
|
|
191
|
+
| Python Scanner | requirements.txt, setup.py, pyproject.toml, 14K+ PyPI IOCs |
|
|
192
|
+
| Shannon Entropy | High-entropy strings (5.5 bits + 50 chars min) |
|
|
193
|
+
| AI Config Scanner | .cursorrules, CLAUDE.md, copilot-instructions.md injection |
|
|
194
|
+
| Package/Dependencies | Lifecycle scripts, IOC matching (225K+ packages) |
|
|
195
|
+
| GitHub Actions | Shai-Hulud backdoor detection |
|
|
196
|
+
| Hash Scanner | Known malicious file hashes |
|
|
197
|
+
|
|
198
|
+
### 129 detection rules
|
|
199
|
+
|
|
200
|
+
All rules are mapped to MITRE ATT&CK techniques. See [SECURITY.md](SECURITY.md#detection-rules-v262) for the complete rules reference.
|
|
201
|
+
|
|
202
|
+
### Detected campaigns
|
|
203
|
+
|
|
204
|
+
| Campaign | Status |
|
|
205
|
+
|----------|--------|
|
|
206
|
+
| Shai-Hulud v1/v2/v3 (2025) | Detected |
|
|
207
|
+
| event-stream (2018) | Detected |
|
|
208
|
+
| eslint-scope (2018) | Detected |
|
|
209
|
+
| Protestware (node-ipc, colors, faker) | Detected |
|
|
210
|
+
| Typosquats (crossenv, mongose, babelcli) | Detected |
|
|
566
211
|
|
|
567
212
|
---
|
|
568
213
|
|
|
@@ -570,24 +215,15 @@ MUAD'DIB aggregates threat intelligence from verified sources only:
|
|
|
570
215
|
|
|
571
216
|
The VS Code extension automatically scans your npm projects.
|
|
572
217
|
|
|
573
|
-
### Installation
|
|
574
|
-
|
|
575
|
-
Search "MUAD'DIB" in VS Code Extensions, or:
|
|
576
|
-
|
|
577
218
|
```bash
|
|
578
219
|
code --install-extension dnszlsk.muaddib-vscode
|
|
579
220
|
```
|
|
580
221
|
|
|
581
|
-
### Commands
|
|
582
|
-
|
|
583
222
|
- `MUAD'DIB: Scan Project` - Scan entire project
|
|
584
223
|
- `MUAD'DIB: Scan Current File` - Scan current file
|
|
224
|
+
- Settings: `muaddib.autoScan`, `muaddib.webhookUrl`, `muaddib.failLevel`
|
|
585
225
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
- `muaddib.autoScan` - Auto-scan on project open (default: true)
|
|
589
|
-
- `muaddib.webhookUrl` - Discord/Slack webhook URL
|
|
590
|
-
- `muaddib.failLevel` - Alert level (critical/high/medium/low)
|
|
226
|
+
See [vscode-extension/README.md](vscode-extension/README.md) for full documentation.
|
|
591
227
|
|
|
592
228
|
---
|
|
593
229
|
|
|
@@ -595,8 +231,6 @@ code --install-extension dnszlsk.muaddib-vscode
|
|
|
595
231
|
|
|
596
232
|
### GitHub Actions (Marketplace)
|
|
597
233
|
|
|
598
|
-
Use the official MUAD'DIB action from the GitHub Marketplace:
|
|
599
|
-
|
|
600
234
|
```yaml
|
|
601
235
|
name: Security Scan
|
|
602
236
|
|
|
@@ -617,130 +251,28 @@ jobs:
|
|
|
617
251
|
sarif: 'results.sarif'
|
|
618
252
|
```
|
|
619
253
|
|
|
620
|
-
#### Action Inputs
|
|
621
|
-
|
|
622
254
|
| Input | Description | Default |
|
|
623
255
|
|-------|-------------|---------|
|
|
624
256
|
| `path` | Path to scan | `.` |
|
|
625
|
-
| `fail-on` | Minimum severity to fail
|
|
626
|
-
| `sarif` |
|
|
627
|
-
| `paranoid` |
|
|
628
|
-
|
|
629
|
-
#### Action Outputs
|
|
630
|
-
|
|
631
|
-
| Output | Description |
|
|
632
|
-
|--------|-------------|
|
|
633
|
-
| `sarif-file` | Path to generated SARIF file |
|
|
634
|
-
| `risk-score` | Risk score (0-100) |
|
|
635
|
-
| `threats-count` | Number of threats detected |
|
|
636
|
-
| `exit-code` | Exit code (0 = clean) |
|
|
257
|
+
| `fail-on` | Minimum severity to fail | `high` |
|
|
258
|
+
| `sarif` | SARIF output file path | |
|
|
259
|
+
| `paranoid` | Ultra-strict detection | `false` |
|
|
637
260
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
---
|
|
641
|
-
|
|
642
|
-
## Architecture
|
|
261
|
+
### Pre-commit hooks
|
|
643
262
|
|
|
263
|
+
```bash
|
|
264
|
+
muaddib init-hooks # Auto-detect (husky/pre-commit/git)
|
|
265
|
+
muaddib init-hooks --type husky # Force husky
|
|
266
|
+
muaddib init-hooks --mode diff # Only block NEW threats
|
|
644
267
|
```
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
| +-- GitHub Advisory (malware)
|
|
654
|
-
| +-- Snyk Known Malware
|
|
655
|
-
| +-- Static IOCs (Socket, Phylum)
|
|
656
|
-
|
|
|
657
|
-
+-- Deobfuscation Pre-processing (v2.2.5, --no-deobfuscate to disable)
|
|
658
|
-
| +-- String concat folding, CharCode reconstruction
|
|
659
|
-
| +-- Base64 decode, Hex array resolution
|
|
660
|
-
| +-- Const propagation (Phase 2)
|
|
661
|
-
|
|
|
662
|
-
+-- Inter-module Dataflow (v2.2.6, --no-module-graph to disable)
|
|
663
|
-
| +-- Module dependency graph, tainted export annotation
|
|
664
|
-
| +-- 3-hop re-export chains, class method analysis
|
|
665
|
-
| +-- Cross-file credential read -> network sink detection
|
|
666
|
-
|
|
|
667
|
-
+-- Intent Coherence Analysis (v2.6.0)
|
|
668
|
-
| +-- Intra-file source-sink pairing (credential read + eval/network in same file)
|
|
669
|
-
| +-- Cross-file detection delegated to module-graph (proven taint paths only)
|
|
670
|
-
| +-- LOW severity threats excluded (respects FP reductions)
|
|
671
|
-
|
|
|
672
|
-
+-- 14 Parallel Scanners (129 rules)
|
|
673
|
-
| +-- AST Parse (acorn) — eval/Function, credential CLI theft, binary droppers, prototype hooks
|
|
674
|
-
| +-- Pattern Matching (shell, scripts)
|
|
675
|
-
| +-- Obfuscation Detection (skip .min.js, ignore hex/unicode alone)
|
|
676
|
-
| +-- Typosquat Detection (npm + PyPI, Levenshtein)
|
|
677
|
-
| +-- Python Scanner (requirements.txt, setup.py, pyproject.toml)
|
|
678
|
-
| +-- Shannon Entropy (string-level, 5.5 bits + 50 chars min)
|
|
679
|
-
| +-- JS Obfuscation Patterns (_0x* vars, encoded arrays, eval+entropy)
|
|
680
|
-
| +-- GitHub Actions Scanner
|
|
681
|
-
| +-- AI Config Scanner (.cursorrules, CLAUDE.md, copilot-instructions.md)
|
|
682
|
-
| +-- Package, Dependencies, Hash, npm-registry, Dataflow scanners
|
|
683
|
-
|
|
|
684
|
-
+-- Supply Chain Anomaly Detection (v2.0)
|
|
685
|
-
| +-- Temporal Lifecycle Script Detection (--temporal)
|
|
686
|
-
| +-- Temporal AST Diff (--temporal-ast)
|
|
687
|
-
| +-- Publish Frequency Anomaly (--temporal-publish)
|
|
688
|
-
| +-- Maintainer Change Detection (--temporal-maintainer)
|
|
689
|
-
| +-- Canary Tokens / Honey Tokens (sandbox)
|
|
690
|
-
|
|
|
691
|
-
+-- Validation & Observability (v2.1)
|
|
692
|
-
| +-- Datadog 17K Benchmark (88.2% raw, ~100% JS/Node.js adjusted)
|
|
693
|
-
| +-- Ground Truth Dataset (51 real-world attacks, 93.9% TPR)
|
|
694
|
-
| +-- Detection Time Logging (first_seen tracking, lead time metrics)
|
|
695
|
-
| +-- FP Rate Tracking (daily stats, false positive rate)
|
|
696
|
-
| +-- Score Breakdown (explainable per-rule scoring)
|
|
697
|
-
| +-- Threat Feed API (HTTP server, JSON feed for SIEM)
|
|
698
|
-
|
|
|
699
|
-
+-- FP Reduction Post-processing (v2.2.8-v2.3.1, v2.5.7-v2.5.8, v2.5.15-v2.5.16)
|
|
700
|
-
| +-- Count-based severity downgrade (dynamic_require, dataflow, module_compile, etc.)
|
|
701
|
-
| +-- Framework prototype scoring cap + HTTP client whitelist
|
|
702
|
-
| +-- Obfuscation in dist/build/.cjs/.mjs/.js >100KB → LOW
|
|
703
|
-
| +-- Safe env var + prefix filtering + DATAFLOW_SAFE_ENV_VARS
|
|
704
|
-
| +-- Dataflow telemetry source categorization (os.platform/arch → telemetry_read)
|
|
705
|
-
| +-- DEP whitelist (es5-ext, bootstrap-sass) + npm alias skip
|
|
706
|
-
| +-- IOC wildcard audit (v2.5.8): FPR 10.8% → 6.0%
|
|
707
|
-
| +-- P5 heuristic precision (v2.5.15): 7 fixes
|
|
708
|
-
| +-- P6 compound detection precision (v2.5.16): 6 fixes
|
|
709
|
-
|
|
|
710
|
-
+-- Per-File Max Scoring (v2.2.11)
|
|
711
|
-
| +-- Score = max(file_scores) + package_level_score
|
|
712
|
-
| +-- Eliminates score accumulation across many files
|
|
713
|
-
| +-- Package-level threats (lifecycle, typosquat, IOC) scored separately
|
|
714
|
-
|
|
|
715
|
-
+-- Sandbox Monkey-Patching Preload (v2.4.9)
|
|
716
|
-
| +-- Runtime time manipulation (Date.now, setTimeout→0, setInterval→immediate)
|
|
717
|
-
| +-- Network/filesystem/process/env interception and logging
|
|
718
|
-
| +-- Multi-run [0h, 72h, 7d] for time-bomb detection (T1497.003)
|
|
719
|
-
|
|
|
720
|
-
+-- Security Audit (v2.5.0-v2.5.6)
|
|
721
|
-
| +-- 41 issues remediated (14 CRITICAL, 18 HIGH, 9 MEDIUM)
|
|
722
|
-
| +-- Native addon path traversal, atomic writes, AST bypasses
|
|
723
|
-
|
|
|
724
|
-
+-- Audit Hardening (v2.5.13-v2.5.14)
|
|
725
|
-
| +-- Scoring: plugin loader threshold, lifecycle CRITICAL floor, percentage guard 40%
|
|
726
|
-
| +-- AST: eval alias, globalThis indirect, require(obj.prop), variable reassignment
|
|
727
|
-
| +-- Dataflow: Promise .then() tainting, JSON taint propagation
|
|
728
|
-
| +-- Shell: mkfifo+nc, base64|bash, wget+base64 (3 new patterns)
|
|
729
|
-
| +-- Entropy: fragment cluster, windowed analysis
|
|
730
|
-
| +-- 8 new rules (SHELL-013 to 015, ENTROPY-004, +4 audit fixes)
|
|
731
|
-
|
|
|
732
|
-
+-- Paranoid Mode (ultra-strict)
|
|
733
|
-
+-- Docker Sandbox (behavioral analysis, network capture, canary tokens, CI-aware, preload)
|
|
734
|
-
+-- Zero-Day Monitor (internal: npm + PyPI RSS polling, Discord alerts, daily report)
|
|
735
|
-
|
|
|
736
|
-
v
|
|
737
|
-
Dataflow Analysis (credential read -> network send)
|
|
738
|
-
|
|
|
739
|
-
v
|
|
740
|
-
Threat Enrichment (rules, MITRE ATT&CK, playbooks)
|
|
741
|
-
|
|
|
742
|
-
v
|
|
743
|
-
Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
268
|
+
|
|
269
|
+
With pre-commit framework:
|
|
270
|
+
```yaml
|
|
271
|
+
repos:
|
|
272
|
+
- repo: https://github.com/DNSZLSK/muad-dib
|
|
273
|
+
rev: v2.6.2
|
|
274
|
+
hooks:
|
|
275
|
+
- id: muaddib-scan
|
|
744
276
|
```
|
|
745
277
|
|
|
746
278
|
---
|
|
@@ -749,53 +281,14 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
|
749
281
|
|
|
750
282
|
| Metric | Result | Details |
|
|
751
283
|
|--------|--------|---------|
|
|
752
|
-
| **Wild TPR** (Datadog 17K) | **88.2%** raw
|
|
753
|
-
| **TPR** (Ground Truth) | **93.9%** (46/49) | 51 real
|
|
754
|
-
| **FPR** (Benign
|
|
755
|
-
| **ADR** (Adversarial + Holdout) | **
|
|
756
|
-
|
|
757
|
-
**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:
|
|
758
|
-
|
|
759
|
-
| Category | Count | Reason |
|
|
760
|
-
|----------|-------|--------|
|
|
761
|
-
| Phishing pages (HTML/CSS/JS frontend) | 1,233 | No Node.js APIs (no `require`, `child_process`, `fs`, `process.env`). Fake login pages, redirects, captchas. |
|
|
762
|
-
| Native binaries (no JS files) | 824 | Platform-specific binaries (darwin-arm64, linux-x64, etc.). 201 from @42ailab alone. |
|
|
763
|
-
| Corrected libraries | 20 | Temporarily compromised then fixed. Malicious code removed before scan. |
|
|
764
|
-
|
|
765
|
-
All 2,077 misses lack Node.js malware patterns. MUAD'DIB performs AST-based Node.js static analysis — phishing HTML and native binaries are out of scope. Adjusted TPR on JS/Node.js malware: **~100%** (15,810/~15,845). See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md#14-datadog-17k-benchmark).
|
|
766
|
-
|
|
767
|
-
**FPR by package size** — FPR correlates linearly with package size. Per-file max scoring (v2.2.11) significantly reduces FP on medium/large packages:
|
|
768
|
-
|
|
769
|
-
| Category | Packages | FP | FPR |
|
|
770
|
-
|----------|----------|-----|-----|
|
|
771
|
-
| Small (<10 JS files) | 290 | 18 | **6.2%** |
|
|
772
|
-
| Medium (10-50 JS files) | 135 | 16 | 11.9% |
|
|
773
|
-
| Large (50-100 JS files) | 40 | 10 | 25.0% |
|
|
774
|
-
| Very large (100+ JS files) | 62 | 25 | 40.3% |
|
|
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) → **12.3%** (v2.6.1, module-graph bounded path — zero FP added)
|
|
777
|
-
|
|
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
|
-
|
|
780
|
-
**Holdout progression** (pre-tuning scores, rules frozen):
|
|
781
|
-
|
|
782
|
-
| Holdout | Score | Focus |
|
|
783
|
-
|---------|-------|-------|
|
|
784
|
-
| v1 | 30% (3/10) | General patterns |
|
|
785
|
-
| v2 | 40% (4/10) | Env charcode, lifecycle, prototype |
|
|
786
|
-
| v3 | 60% (6/10) | Require cache, DNS TXT, reverse shell |
|
|
787
|
-
| v4 | **80%** (8/10) | Deobfuscation effectiveness |
|
|
788
|
-
| v5 | 50% (5/10) | Inter-module dataflow (new scanner) |
|
|
789
|
-
|
|
790
|
-
- **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).
|
|
791
|
-
- **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). 3 misses are browser-only (lottie-player, polyfill-io, trojanized-jquery) — see [Threat Model](docs/threat-model.md).
|
|
792
|
-
- **FPR** (False Positive Rate): packages scoring > 20 out of 529 real npm packages (source code scanned, not empty dirs).
|
|
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
|
-
- **Holdout** (pre-tuning): detection rate on 10 unseen samples with rules frozen (measures generalization)
|
|
284
|
+
| **Wild TPR** (Datadog 17K) | **88.2%** raw / **~100%** adjusted | 17,922 real malware. 2,077 out-of-scope (phishing, binaries, corrected) |
|
|
285
|
+
| **TPR** (Ground Truth) | **93.9%** (46/49) | 51 real attacks. 3 out-of-scope: browser-only |
|
|
286
|
+
| **FPR** (Benign) | **12.1%** (64/529) | 529 npm packages, real source via `npm pack` |
|
|
287
|
+
| **ADR** (Adversarial + Holdout) | **94.8%** (73/77) | 53 adversarial + 40 holdout (77 available on disk) |
|
|
795
288
|
|
|
796
|
-
|
|
289
|
+
**1940 tests** across 44 files, 86% code coverage. **129 rules** (124 RULES + 5 PARANOID).
|
|
797
290
|
|
|
798
|
-
See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) for the full experimental protocol.
|
|
291
|
+
See [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) for the full experimental protocol, holdout history, and Datadog benchmark details.
|
|
799
292
|
|
|
800
293
|
---
|
|
801
294
|
|
|
@@ -829,13 +322,11 @@ npm test
|
|
|
829
322
|
|
|
830
323
|
### Testing
|
|
831
324
|
|
|
832
|
-
- **
|
|
833
|
-
- **56 fuzz tests** - Malformed
|
|
834
|
-
- **Datadog 17K benchmark** - 17,922 real malware samples
|
|
835
|
-
- **
|
|
836
|
-
- **
|
|
837
|
-
- **False positive validation** - 12.3% FPR global (65/532) on real npm source code via `npm pack`
|
|
838
|
-
- **ESLint security audit** - `eslint-plugin-security` with 14 rules enabled
|
|
325
|
+
- **1940 tests** across 44 modular test files - 86% code coverage
|
|
326
|
+
- **56 fuzz tests** - Malformed inputs, ReDoS, unicode, binary
|
|
327
|
+
- **Datadog 17K benchmark** - 17,922 real malware samples
|
|
328
|
+
- **Ground truth validation** - 51 real-world attacks (93.9% TPR)
|
|
329
|
+
- **False positive validation** - 12.1% FPR on 529 real npm packages
|
|
839
330
|
|
|
840
331
|
---
|
|
841
332
|
|
|
@@ -847,10 +338,13 @@ npm test
|
|
|
847
338
|
|
|
848
339
|
## Documentation
|
|
849
340
|
|
|
850
|
-
- [
|
|
341
|
+
- [Documentation Index](docs/INDEX.md) - All documentation in one place
|
|
342
|
+
- [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) - Experimental protocol, holdout scores
|
|
851
343
|
- [Threat Model](docs/threat-model.md) - What MUAD'DIB detects and doesn't detect
|
|
852
|
-
- [
|
|
853
|
-
- [
|
|
344
|
+
- [Adversarial Evaluation](ADVERSARIAL.md) - Red team samples and ADR results
|
|
345
|
+
- [Security Policy](SECURITY.md) - Detection rules reference (129 rules)
|
|
346
|
+
- [Security Audit](docs/SECURITY_AUDIT.md) - Bypass validation report
|
|
347
|
+
- [FP Analysis](docs/EVALUATION.md) - Historical false positive analysis
|
|
854
348
|
|
|
855
349
|
---
|
|
856
350
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muaddib-scanner",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.3",
|
|
4
4
|
"description": "Supply-chain threat detection & response for npm & PyPI/Python",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -48,14 +48,15 @@
|
|
|
48
48
|
"acorn": "8.16.0",
|
|
49
49
|
"acorn-walk": "8.3.5",
|
|
50
50
|
"adm-zip": "0.5.16",
|
|
51
|
-
"js-yaml": "4.1.1"
|
|
51
|
+
"js-yaml": "4.1.1",
|
|
52
|
+
"muaddib-scanner": "^2.6.2"
|
|
52
53
|
},
|
|
53
54
|
"overrides": {
|
|
54
55
|
"loadash": "0.0.0-security"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@eslint/js": "10.0.1",
|
|
58
|
-
"eslint": "10.0.
|
|
59
|
+
"eslint": "10.0.3",
|
|
59
60
|
"eslint-plugin-security": "^4.0.0",
|
|
60
61
|
"globals": "17.4.0"
|
|
61
62
|
}
|
package/src/ioc/scraper.js
CHANGED
|
@@ -545,25 +545,31 @@ async function scrapeDatadogIOCs() {
|
|
|
545
545
|
|
|
546
546
|
for (const parts of rows) {
|
|
547
547
|
const name = parts[0] || '';
|
|
548
|
-
const
|
|
548
|
+
const versionsStr = parts[1] || '*';
|
|
549
549
|
const vendors = parts[2] || 'datadog';
|
|
550
550
|
|
|
551
551
|
if (name && name !== 'package_name' && name !== 'name') {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
552
|
+
// Split comma-separated multi-version strings (e.g. "4.18.1, 5.11.3")
|
|
553
|
+
const versionList = versionsStr.includes(',')
|
|
554
|
+
? versionsStr.split(',').map(v => v.trim()).filter(Boolean)
|
|
555
|
+
: [versionsStr];
|
|
556
|
+
for (const ver of versionList) {
|
|
557
|
+
packages.push({
|
|
558
|
+
id: `DATADOG-${name}`,
|
|
559
|
+
name: name,
|
|
560
|
+
version: ver || '*',
|
|
561
|
+
severity: 'critical',
|
|
562
|
+
confidence: 'high',
|
|
563
|
+
source: 'datadog-consolidated',
|
|
564
|
+
description: `Compromised package (sources: ${vendors})`,
|
|
565
|
+
references: ['https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/'],
|
|
566
|
+
mitre: 'T1195.002',
|
|
567
|
+
freshness: createFreshness('datadog', 'high')
|
|
568
|
+
});
|
|
569
|
+
}
|
|
564
570
|
}
|
|
565
571
|
}
|
|
566
|
-
console.log(`[SCRAPER] ${packages.length}
|
|
572
|
+
console.log(`[SCRAPER] ${packages.length} IOC entries (consolidated)`);
|
|
567
573
|
}
|
|
568
574
|
|
|
569
575
|
// DataDog specific file
|
|
@@ -858,18 +864,21 @@ async function scrapeGitHubAdvisory() {
|
|
|
858
864
|
if (isMalware) {
|
|
859
865
|
for (const affected of vuln.affected || []) {
|
|
860
866
|
if (affected.package && affected.package.ecosystem === 'npm') {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
867
|
+
const versions = extractVersions(affected);
|
|
868
|
+
for (const ver of versions) {
|
|
869
|
+
packages.push({
|
|
870
|
+
id: vuln.id,
|
|
871
|
+
name: affected.package.name,
|
|
872
|
+
version: ver,
|
|
873
|
+
severity: 'critical',
|
|
874
|
+
confidence: 'high',
|
|
875
|
+
source: 'github-advisory',
|
|
876
|
+
description: (vuln.summary || 'Malicious package').slice(0, 200),
|
|
877
|
+
references: ['https://github.com/advisories/' + vuln.id],
|
|
878
|
+
mitre: 'T1195.002',
|
|
879
|
+
freshness: createFreshness('github-advisory', 'high')
|
|
880
|
+
});
|
|
881
|
+
}
|
|
873
882
|
}
|
|
874
883
|
}
|
|
875
884
|
}
|
package/src/ioc/updater.js
CHANGED
|
@@ -339,6 +339,15 @@ function createOptimizedIOCs(iocs) {
|
|
|
339
339
|
* @param {Object} fullIOCs - Full IOCs object with packages array
|
|
340
340
|
* @returns {Object} Compact IOCs
|
|
341
341
|
*/
|
|
342
|
+
// Legitimate packages with version-specific compromises only.
|
|
343
|
+
// These must never become wildcards (all-version flags) because only
|
|
344
|
+
// specific versions were malicious — flagging all versions is a false positive.
|
|
345
|
+
const NEVER_WILDCARD = new Set([
|
|
346
|
+
'event-stream', 'ua-parser-js', 'coa', 'rc',
|
|
347
|
+
'colors', 'faker', 'node-ipc',
|
|
348
|
+
'posthog-node', 'ngx-bootstrap', '@asyncapi/specs'
|
|
349
|
+
]);
|
|
350
|
+
|
|
342
351
|
function generateCompactIOCs(fullIOCs) {
|
|
343
352
|
const wildcards = [];
|
|
344
353
|
const versioned = Object.create(null);
|
|
@@ -353,6 +362,10 @@ function generateCompactIOCs(fullIOCs) {
|
|
|
353
362
|
}
|
|
354
363
|
|
|
355
364
|
if (p.version === '*') {
|
|
365
|
+
if (NEVER_WILDCARD.has(p.name)) {
|
|
366
|
+
// Legitimate package — skip wildcard, treat as version-unknown
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
356
369
|
wildcards.push(p.name);
|
|
357
370
|
} else {
|
|
358
371
|
if (!versioned[p.name]) versioned[p.name] = [];
|
|
@@ -497,4 +510,4 @@ function verifyIOCHMAC(data, hmac) {
|
|
|
497
510
|
}
|
|
498
511
|
}
|
|
499
512
|
|
|
500
|
-
module.exports = { updateIOCs, loadCachedIOCs, invalidateCache, generateCompactIOCs, expandCompactIOCs, mergeIOCs, createOptimizedIOCs, generateIOCHMAC, verifyIOCHMAC };
|
|
513
|
+
module.exports = { updateIOCs, loadCachedIOCs, invalidateCache, generateCompactIOCs, expandCompactIOCs, mergeIOCs, createOptimizedIOCs, generateIOCHMAC, verifyIOCHMAC, NEVER_WILDCARD };
|
package/src/scoring.js
CHANGED
|
@@ -122,8 +122,14 @@ const FP_COUNT_THRESHOLDS = {
|
|
|
122
122
|
// B1 FP reduction: bundled code aliases eval/Function (sinon, storybook, vitest)
|
|
123
123
|
dangerous_call_eval: { maxCount: 3, from: 'MEDIUM', to: 'LOW' },
|
|
124
124
|
// P6: HTTP client libraries (undici, aws-sdk, nodemailer, jsdom) parse Authorization/Bearer headers
|
|
125
|
-
// with
|
|
126
|
-
credential_regex_harvest: { maxCount:
|
|
125
|
+
// with 3+ credential regexes. Real harvesters use 1-2 targeted regexes.
|
|
126
|
+
credential_regex_harvest: { maxCount: 2, from: 'HIGH', to: 'LOW' },
|
|
127
|
+
// P7: Config frameworks (pm2, nx, dotenv, aws-sdk) read 10+ env vars — not credential theft.
|
|
128
|
+
// Real stealers access 1-5 targeted env vars. Count >10 = config loader pattern.
|
|
129
|
+
env_access: { maxCount: 10, from: 'HIGH', to: 'LOW' },
|
|
130
|
+
// P7: Bundled files with 5+ high-entropy strings are data files, not malware payloads.
|
|
131
|
+
// Real payloads use 1-2 targeted encoded strings. Count >5 = bundled assets/data.
|
|
132
|
+
high_entropy_string: { maxCount: 5, to: 'LOW' }
|
|
127
133
|
};
|
|
128
134
|
|
|
129
135
|
// Types exempt from dist/ downgrade — IOC matches, lifecycle scripts, and
|
|
@@ -144,8 +150,9 @@ const DIST_EXEMPT_TYPES = new Set([
|
|
|
144
150
|
// fetch_decrypt_exec (fetch+decrypt+eval triple) remains exempt — never coincidental.
|
|
145
151
|
]);
|
|
146
152
|
|
|
147
|
-
// Regex matching dist/build/minified/bundled file paths
|
|
148
|
-
|
|
153
|
+
// Regex matching dist/build/out/output/minified/bundled file paths
|
|
154
|
+
// P7: added out/ and output/ — common build output directories (esbuild, custom build scripts)
|
|
155
|
+
const DIST_FILE_RE = /(?:^|[/\\])(?:dist|build|out|output)[/\\]|\.min\.js$|\.bundle\.js$/i;
|
|
149
156
|
|
|
150
157
|
// Bundler artifact types: get two-notch downgrade in dist/ files (CRITICAL→MEDIUM, HIGH→LOW).
|
|
151
158
|
// These are individual pattern signals that bundlers routinely produce (eval for globalThis,
|
|
@@ -155,7 +162,9 @@ const DIST_BUNDLER_ARTIFACT_TYPES = new Set([
|
|
|
155
162
|
'dynamic_require', 'dynamic_import',
|
|
156
163
|
'obfuscation_detected', 'high_entropy_string', 'possible_obfuscation',
|
|
157
164
|
'js_obfuscation_pattern', 'vm_code_execution',
|
|
158
|
-
'module_compile', 'module_compile_dynamic'
|
|
165
|
+
'module_compile', 'module_compile_dynamic',
|
|
166
|
+
// P7: env_access in dist/ is bundled SDK config reading, not credential theft
|
|
167
|
+
'env_access'
|
|
159
168
|
]);
|
|
160
169
|
|
|
161
170
|
// Types exempt from reachability downgrade — IOC matches, lifecycle, and package-level types.
|
|
@@ -223,15 +232,16 @@ function applyFPReductions(threats, reachableFiles, packageName) {
|
|
|
223
232
|
const rule = FP_COUNT_THRESHOLDS[t.type];
|
|
224
233
|
if (rule && typeCounts[t.type] > rule.maxCount && (!rule.from || t.severity === rule.from)) {
|
|
225
234
|
const typeRatio = typeCounts[t.type] / totalThreats;
|
|
226
|
-
// suspicious_dataflow:
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
235
|
+
// suspicious_dataflow: full bypass of percentage guard. Packages with >3 suspicious_dataflow
|
|
236
|
+
// findings are always legitimate SDKs (SMTP, monitoring, analytics). Real malware has 1-2
|
|
237
|
+
// targeted source→sink pairs. The count >3 threshold is sufficient protection.
|
|
238
|
+
// P7: removed 80% ratio cap — it caused ~30k FP hits in production on SDK packages
|
|
239
|
+
// where dataflow was the dominant finding type (e.g. @darajs/core, addio-admin-sdk).
|
|
230
240
|
// vm_code_execution: full bypass — packages with only vm.Script calls (cassandra-driver,
|
|
231
241
|
// webpack, jest) are legitimate. Real malware using vm always has other signals
|
|
232
242
|
// (network, fs, obfuscation). The >3 count threshold is sufficient protection.
|
|
233
243
|
if (typeRatio < 0.4 ||
|
|
234
|
-
|
|
244
|
+
t.type === 'suspicious_dataflow' ||
|
|
235
245
|
t.type === 'vm_code_execution') {
|
|
236
246
|
t.severity = rule.to;
|
|
237
247
|
}
|
package/src/temporal-runner.js
CHANGED
|
@@ -84,6 +84,7 @@ async function runTemporalAnalyses(targetPath, options, pkgNames) {
|
|
|
84
84
|
}
|
|
85
85
|
{
|
|
86
86
|
const PUBLISH_CONCURRENCY = 5;
|
|
87
|
+
const publishThreats = [];
|
|
87
88
|
for (let i = 0; i < pkgNames.length; i += PUBLISH_CONCURRENCY) {
|
|
88
89
|
const batch = pkgNames.slice(i, i + PUBLISH_CONCURRENCY);
|
|
89
90
|
const results = await Promise.allSettled(
|
|
@@ -93,15 +94,38 @@ async function runTemporalAnalyses(targetPath, options, pkgNames) {
|
|
|
93
94
|
if (r.status !== 'fulfilled' || !r.value.suspicious) continue;
|
|
94
95
|
const det = r.value;
|
|
95
96
|
for (const a of det.anomalies) {
|
|
96
|
-
|
|
97
|
+
publishThreats.push({
|
|
97
98
|
type: a.type,
|
|
98
99
|
severity: a.severity,
|
|
99
100
|
message: a.description,
|
|
100
|
-
file: `node_modules/${det.packageName}/package.json
|
|
101
|
+
file: `node_modules/${det.packageName}/package.json`,
|
|
102
|
+
_scope: det.packageName.startsWith('@') ? det.packageName.split('/')[0] : null
|
|
101
103
|
});
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
}
|
|
107
|
+
|
|
108
|
+
// P7: Scope-aware deduplication for monorepo releases.
|
|
109
|
+
// When 3+ packages from the same @scope trigger publish_burst or rapid_succession,
|
|
110
|
+
// it's a coordinated monorepo release (lerna, nx, turbo), not an attack.
|
|
111
|
+
// Downgrade all findings for that scope to LOW severity.
|
|
112
|
+
const MONOREPO_SCOPE_THRESHOLD = 3;
|
|
113
|
+
const scopeTypeCounts = new Map(); // key: `${scope}:${type}` → count
|
|
114
|
+
for (const t of publishThreats) {
|
|
115
|
+
if (!t._scope) continue;
|
|
116
|
+
const key = `${t._scope}:${t.type}`;
|
|
117
|
+
scopeTypeCounts.set(key, (scopeTypeCounts.get(key) || 0) + 1);
|
|
118
|
+
}
|
|
119
|
+
for (const t of publishThreats) {
|
|
120
|
+
if (t._scope) {
|
|
121
|
+
const key = `${t._scope}:${t.type}`;
|
|
122
|
+
if ((scopeTypeCounts.get(key) || 0) >= MONOREPO_SCOPE_THRESHOLD) {
|
|
123
|
+
t.severity = 'LOW';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
delete t._scope; // clean up internal field
|
|
127
|
+
threats.push(t);
|
|
128
|
+
}
|
|
105
129
|
}
|
|
106
130
|
}
|
|
107
131
|
|