muaddib-scanner 2.5.8 → 2.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,7 @@
30
30
 
31
31
  npm and PyPI supply-chain attacks are exploding. Shai-Hulud compromised 25K+ repos in 2025. Existing tools detect threats but don't help you respond.
32
32
 
33
- MUAD'DIB combines 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.3.1
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.3.1 Scanner
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 (102 rules)
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) | **7.4%** (39/525) | 529 npm packages (525 scanned), real source code via `npm pack`, threshold > 20 |
729
- | **ADR** (Adversarial + Holdout) | **98.7%** (77/78) | 38 adversarial + 40 holdout evasive samples. 1 documented miss: `require-cache-poison` (accepted trade-off) |
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) → **7.4%** (v2.3.1, P3)
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). The 6.2% on standard packages (<10 JS files, 290 packages) is the most representative metric for typical use — most npm packages are small.
765
- - **ADR** (Adversarial Detection Rate): detection rate on 78 evasive malicious samples — 38 adversarial + 40 holdout (5 batches of 10, testing obfuscation, inter-module dataflow, etc.). 1 documented miss: `require-cache-poison` (score 10 < threshold 20, accepted trade-off from FP reduction P3).
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, 78 adversarial/holdout samples, 51 ground-truth attacks (65 documented malware packages). **1387 tests**, 86% code coverage.
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
- - **1387 unit/integration tests** across 20 modular test files - 86% code coverage via [Codecov](https://codecov.io/gh/DNSZLSK/muad-dib)
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
- - **78 adversarial/holdout samples** - 38 adversarial + 40 holdout, 77/78 detection rate (98.7% ADR). 1 documented miss: `require-cache-poison` (accepted trade-off)
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** - 7.4% FPR global (39/525) on real npm source code via `npm pack`
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
  "target": "npm/evil-pkg@1.0.0",
3
- "timestamp": "2026-03-06T13:25:09.667Z",
3
+ "timestamp": "2026-03-06T20:13:43.891Z",
4
4
  "ecosystem": "npm",
5
5
  "summary": {
6
6
  "critical": 1,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "target": "npm/evil-pkg@1.0.0",
3
- "timestamp": "2026-03-06T13:25:09.668Z",
3
+ "timestamp": "2026-03-06T20:13:43.892Z",
4
4
  "ecosystem": "npm",
5
5
  "summary": {
6
6
  "critical": 1,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "target": "npm/suspect-pkg@1.0",
3
- "timestamp": "2026-03-06T13:25:09.668Z",
3
+ "timestamp": "2026-03-06T20:13:43.892Z",
4
4
  "ecosystem": "npm",
5
5
  "summary": {
6
6
  "critical": 0,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "target": "npm/evil-pkg@2.0.0",
3
- "timestamp": "2026-03-06T13:25:10.228Z",
3
+ "timestamp": "2026-03-06T20:13:44.264Z",
4
4
  "ecosystem": "npm",
5
5
  "summary": {
6
6
  "critical": 1,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "date": "2026-03-06",
3
- "timestamp": "2026-03-06T13:25:10.394Z",
3
+ "timestamp": "2026-03-06T20:13:44.379Z",
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-1772803509664@1.0.0** — 1 finding(s)",
37
+ "value": "1. **npm/test-dedup-detection-1772828023889@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 13:25:10 UTC"
42
+ "text": "MUAD'DIB - Daily summary | 2026-03-06 20:13:44 UTC"
43
43
  },
44
- "timestamp": "2026-03-06T13:25:10.394Z"
44
+ "timestamp": "2026-03-06T20:13:44.379Z"
45
45
  }
46
46
  ]
47
47
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.5.8",
3
+ "version": "2.5.10",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,31 +1,51 @@
1
1
  const crypto = require('crypto');
2
2
 
3
3
  /**
4
- * Canary token definitions.
5
- * Each key is an env var name, value is a prefix.
6
- * A random suffix is appended at generation time.
4
+ * Generate a base32-encoded string of the given length.
5
+ * Uses characters A-Z and 2-7 (RFC 4648 base32 alphabet).
7
6
  */
8
- const CANARY_PREFIXES = {
9
- GITHUB_TOKEN: 'ghp_MUADDIB_CANARY_',
10
- NPM_TOKEN: 'npm_MUADDIB_CANARY_',
11
- AWS_ACCESS_KEY_ID: 'AKIA_MUADDIB_CANARY_',
12
- AWS_SECRET_ACCESS_KEY: 'MUADDIB_CANARY_SECRET_',
13
- GITLAB_TOKEN: 'glpat-MUADDIB_CANARY_',
14
- DOCKER_PASSWORD: 'dckr_MUADDIB_CANARY_',
15
- NPM_AUTH_TOKEN: 'npm_MUADDIB_CANARY_AUTH_',
16
- GH_TOKEN: 'ghp_MUADDIB_CANARY_GH_'
7
+ function generateBase32(length) {
8
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
9
+ const bytes = crypto.randomBytes(length);
10
+ return Array.from(bytes).map(b => chars[b % 32]).join('');
11
+ }
12
+
13
+ /**
14
+ * Canary token generators.
15
+ * Each generator produces a format-valid token that matches the real service format.
16
+ * No common marker (like "MUADDIB_CANARY") — detection relies on exact value matching.
17
+ */
18
+ const CANARY_GENERATORS = {
19
+ // GitHub PAT: ghp_ + 36 alphanumeric chars
20
+ GITHUB_TOKEN: () => 'ghp_' + crypto.randomBytes(27).toString('base64url').substring(0, 36),
21
+ // npm token: npm_ + 36 hex chars
22
+ NPM_TOKEN: () => 'npm_' + crypto.randomBytes(18).toString('hex'),
23
+ // AWS Access Key: AKIA + 16 chars [A-Z2-7]
24
+ AWS_ACCESS_KEY_ID: () => 'AKIA' + generateBase32(16),
25
+ // AWS Secret: 40 chars base64
26
+ AWS_SECRET_ACCESS_KEY: () => crypto.randomBytes(30).toString('base64').substring(0, 40),
27
+ // GitLab PAT: glpat- + 20 alphanumeric
28
+ GITLAB_TOKEN: () => 'glpat-' + crypto.randomBytes(15).toString('base64url').substring(0, 20),
29
+ // Docker: dckr_pat_ + 56 alphanumeric
30
+ DOCKER_PASSWORD: () => 'dckr_pat_' + crypto.randomBytes(42).toString('base64url').substring(0, 56),
31
+ // npm auth token: same as NPM_TOKEN format
32
+ NPM_AUTH_TOKEN: () => 'npm_' + crypto.randomBytes(18).toString('hex'),
33
+ // GH_TOKEN: same as GITHUB_TOKEN format
34
+ GH_TOKEN: () => 'ghp_' + crypto.randomBytes(27).toString('base64url').substring(0, 36)
17
35
  };
18
36
 
19
37
  /**
20
- * Generate a unique set of canary tokens with random suffixes.
38
+ * Generate a unique set of canary tokens with format-valid values.
39
+ * Each token matches its real service format (ghp_, AKIA, npm_, etc.).
21
40
  * @returns {{ tokens: Record<string, string>, suffix: string }}
22
41
  */
23
42
  function generateCanaryTokens() {
24
- const suffix = crypto.randomBytes(8).toString('hex');
25
43
  const tokens = {};
26
- for (const [key, prefix] of Object.entries(CANARY_PREFIXES)) {
27
- tokens[key] = prefix + suffix;
44
+ for (const [key, generator] of Object.entries(CANARY_GENERATORS)) {
45
+ tokens[key] = generator();
28
46
  }
47
+ // Suffix retained for backward compatibility (used in some callers)
48
+ const suffix = crypto.randomBytes(8).toString('hex');
29
49
  return { tokens, suffix };
30
50
  }
31
51
 
@@ -175,7 +195,7 @@ function detectCanaryInOutput(stdout, stderr, tokens) {
175
195
  }
176
196
 
177
197
  module.exports = {
178
- CANARY_PREFIXES,
198
+ CANARY_GENERATORS,
179
199
  generateCanaryTokens,
180
200
  createCanaryEnvFile,
181
201
  createCanaryNpmrc,
@@ -51,14 +51,15 @@ const SAFE_SANDBOX_CMDS = new Set(['timeout', 'node', 'npm', 'npx', 'su', 'env']
51
51
 
52
52
  // Static canary tokens injected by sandbox-runner.sh (fallback honeypots).
53
53
  // These are searched in the sandbox report as a complement to the dynamic
54
- // tokens from canary-tokens.js (which use random suffixes per session).
54
+ // tokens from canary-tokens.js (which use random values per session).
55
+ // Format-valid: match real service token formats to resist format-based detection.
55
56
  const STATIC_CANARY_TOKENS = {
56
- GITHUB_TOKEN: 'MUADDIB_CANARY_GITHUB_f8k3t0k3n',
57
- NPM_TOKEN: 'MUADDIB_CANARY_NPM_s3cr3tt0k3n',
58
- AWS_ACCESS_KEY_ID: 'MUADDIB_CANARY_AKIAIOSFODNN7EXAMPLE',
59
- AWS_SECRET_ACCESS_KEY: 'MUADDIB_CANARY_wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
60
- SLACK_WEBHOOK_URL: 'MUADDIB_CANARY_SLACK',
61
- DISCORD_WEBHOOK_URL: 'MUADDIB_CANARY_DISCORD'
57
+ GITHUB_TOKEN: 'ghp_R8kLmN2pQ4vW7xY9aB3cD5eF6gH8jK0mN2pQ4vW',
58
+ NPM_TOKEN: 'npm_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8',
59
+ AWS_ACCESS_KEY_ID: 'AKIAIOSFODNN7EXAMPLE',
60
+ AWS_SECRET_ACCESS_KEY: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
61
+ SLACK_WEBHOOK_URL: 'https://hooks.example.com/services/TCANARY/BCANARY/canary-slack-token',
62
+ DISCORD_WEBHOOK_URL: 'https://discord.com/api/webhooks/000000000000000000/abcdefghijklmnopqrstuvwxyz'
62
63
  };
63
64
 
64
65
  // Patterns indicating data exfiltration in HTTP bodies
@@ -151,7 +152,7 @@ async function runSingleSandbox(packageName, options = {}) {
151
152
  let stdout = '';
152
153
  let stderr = '';
153
154
  let timedOut = false;
154
- const containerName = `muaddib-sandbox-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
155
+ const containerName = `npm-audit-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
155
156
 
156
157
  const dockerArgs = [
157
158
  'run',
@@ -175,7 +176,7 @@ async function runSingleSandbox(packageName, options = {}) {
175
176
  }
176
177
 
177
178
  // Inject time offset (preload.js deferred to entry point in sandbox-runner.sh)
178
- dockerArgs.push('-e', `MUADDIB_TIME_OFFSET_MS=${timeOffset}`);
179
+ dockerArgs.push('-e', `NODE_TIMING_OFFSET=${timeOffset}`);
179
180
 
180
181
  // Both modes need NET_RAW for tcpdump (runs as root in entrypoint).
181
182
  // Strict mode also needs NET_ADMIN for iptables network blocking.
package/src/sandbox.js CHANGED
@@ -41,12 +41,12 @@ const DANGEROUS_CMDS = ['curl', 'wget', 'nc', 'netcat', 'python', 'python3', 'ba
41
41
  // These are searched in the sandbox report as a complement to the dynamic
42
42
  // tokens from canary-tokens.js (which use random suffixes per session).
43
43
  const STATIC_CANARY_TOKENS = {
44
- GITHUB_TOKEN: 'MUADDIB_CANARY_GITHUB_f8k3t0k3n',
45
- NPM_TOKEN: 'MUADDIB_CANARY_NPM_s3cr3tt0k3n',
46
- AWS_ACCESS_KEY_ID: 'MUADDIB_CANARY_AKIAIOSFODNN7EXAMPLE',
47
- AWS_SECRET_ACCESS_KEY: 'MUADDIB_CANARY_wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
48
- SLACK_WEBHOOK_URL: 'MUADDIB_CANARY_SLACK',
49
- DISCORD_WEBHOOK_URL: 'MUADDIB_CANARY_DISCORD'
44
+ GITHUB_TOKEN: 'ghp_R8kLmN2pQ4vW7xY9aB3cD5eF6gH8jK0mN2pQ4vW',
45
+ NPM_TOKEN: 'npm_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8',
46
+ AWS_ACCESS_KEY_ID: 'AKIAIOSFODNN7EXAMPLE',
47
+ AWS_SECRET_ACCESS_KEY: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
48
+ SLACK_WEBHOOK_URL: 'https://hooks.example.com/services/TCANARY/BCANARY/canary-slack-token',
49
+ DISCORD_WEBHOOK_URL: 'https://discord.com/api/webhooks/000000000000000000/abcdefghijklmnopqrstuvwxyz'
50
50
  };
51
51
 
52
52
  // Patterns indicating data exfiltration in HTTP bodies
@@ -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: isConstant ? 'LOW' : 'HIGH',
864
- message: isConstant
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
  }
@@ -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 => SAFE_FETCH_DOMAINS.some(d => u.includes(d)))) {
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 and lifecycle scripts are always real
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
- ...DIST_EXEMPT_TYPES,
150
+ ...REACHABILITY_BASE_EXEMPT,
135
151
  'cross_file_dataflow',
136
152
  'typosquat_detected', 'pypi_typosquat_detected',
137
153
  'pypi_malicious_package',
@@ -145,41 +161,7 @@ const FRAMEWORK_PROTO_RE = new RegExp(
145
161
  '^(' + FRAMEWORK_PROTOTYPES.join('|') + ')\\.prototype\\.'
146
162
  );
147
163
 
148
- // ============================================
149
- // BENIGN PACKAGE WHITELIST (v2.3.5)
150
- // ============================================
151
- // Well-known npm packages whose legitimate code patterns trigger false positives.
152
- // For whitelisted packages, non-IOC threats are downgraded to LOW.
153
- // IOC matches, lifecycle_shell_pipe, and cross_file_dataflow are NEVER downgraded
154
- // — a compromised version of these packages would still be detected.
155
- const BENIGN_PACKAGE_WHITELIST = new Set([
156
- 'meteor', // powershell PATH setup in install.js (dangerous_exec FP)
157
- 'blessed', // module._compile for terminal capabilities (module_compile FP)
158
- 'sharp', // native bindings with dynamic require + postinstall (lifecycle FP)
159
- 'forever', // process manager: detached spawn + HOME config access (dataflow FP)
160
- 'start-server-and-test', // curl/wget in test scripts, not install hooks (lifecycle FP)
161
- 'ultra-runner', // aliased fs.readFileSync in pnp.js + dynamic require (taint-tracked dataflow FP)
162
- 'node-gyp', // aliased child_process.spawn in node-gyp.js + env access (taint-tracked dataflow FP)
163
- 'graceful-fs' // aliased fs.readFile/readdir/writeFile monkey-patching (taint-tracked credential_tampering FP)
164
- ]);
165
-
166
- // Threat types never affected by benign package whitelist (real compromise indicators)
167
- const WHITELIST_EXEMPT_TYPES = new Set([
168
- 'ioc_match', 'known_malicious_package', 'pypi_malicious_package', 'shai_hulud_marker',
169
- 'lifecycle_shell_pipe',
170
- 'cross_file_dataflow'
171
- ]);
172
-
173
164
  function applyFPReductions(threats, reachableFiles, packageName) {
174
- // Benign package whitelist: downgrade all non-IOC threats to LOW
175
- if (packageName && BENIGN_PACKAGE_WHITELIST.has(packageName)) {
176
- for (const t of threats) {
177
- if (!WHITELIST_EXEMPT_TYPES.has(t.type) && t.severity !== 'LOW') {
178
- t.severity = 'LOW';
179
- }
180
- }
181
- }
182
-
183
165
  // Count occurrences of each threat type (package-level, across all files)
184
166
  const typeCounts = {};
185
167
  for (const t of threats) {
@@ -188,11 +170,11 @@ function applyFPReductions(threats, reachableFiles, packageName) {
188
170
 
189
171
  const totalThreats = threats.length;
190
172
 
191
- // P4: Plugin loader pattern — packages with 2+ dynamic_require + dynamic_import combined
173
+ // P4: Plugin loader pattern — packages with 5+ dynamic_require + dynamic_import combined
192
174
  // are legitimate plugin systems (webpack, eslint, karma, knex, jasmine, gatsby).
193
- // Malware uses one pattern, not both. Bypass the per-type percentage guard.
175
+ // Threshold raised from >1 to >4 (audit fix: >1 was trivially exploitable).
194
176
  const pluginLoaderCount = (typeCounts.dynamic_require || 0) + (typeCounts.dynamic_import || 0);
195
- if (pluginLoaderCount > 1) {
177
+ if (pluginLoaderCount > 4) {
196
178
  for (const t of threats) {
197
179
  if ((t.type === 'dynamic_require' || t.type === 'dynamic_import') && t.severity === 'HIGH') {
198
180
  t.severity = 'LOW';
@@ -208,10 +190,11 @@ function applyFPReductions(threats, reachableFiles, packageName) {
208
190
  const rule = FP_COUNT_THRESHOLDS[t.type];
209
191
  if (rule && typeCounts[t.type] > rule.maxCount && (!rule.from || t.severity === rule.from)) {
210
192
  const typeRatio = typeCounts[t.type] / totalThreats;
211
- // P4: suspicious_dataflow bypasses the percentage guard multiple data flow paths
212
- // indicate a complex application (SMTP client, monitoring agent), not malware.
213
- // Malware has 1-2 targeted exfiltration flows, not 4+.
214
- if (typeRatio < 0.5 || t.type === 'suspicious_dataflow') {
193
+ // suspicious_dataflow: partial bypass of percentage guard up to 80%.
194
+ // Complex apps (SMTP, monitoring) have 50-80% dataflow findings — still downgrade.
195
+ // But if dataflow is >80% of ALL findings, it may be real targeted exfiltration.
196
+ // (Audit fix: full bypass was exploitable — 4+ dataflow patterns = all LOW.)
197
+ if (typeRatio < 0.5 || (t.type === 'suspicious_dataflow' && typeRatio < 0.8)) {
215
198
  t.severity = rule.to;
216
199
  }
217
200
  }
@@ -232,22 +215,22 @@ function applyFPReductions(threats, reachableFiles, packageName) {
232
215
  }
233
216
 
234
217
  // HTTP client prototype whitelist: packages with >20 prototype_hook hits
235
- // targeting HTTP objects (Request, Response, fetch, etc.) are legitimate HTTP clients
218
+ // targeting HTTP class names are legitimate HTTP clients/frameworks.
219
+ // Audit fix: narrowed regex — 'get','delete','command' matched getCredentials, deleteAccount.
236
220
  if (t.type === 'prototype_hook' && (t.severity === 'HIGH' || t.severity === 'CRITICAL') &&
237
221
  typeCounts.prototype_hook > 20) {
238
- const HTTP_PROTO_RE = /\b(Request|Response|fetch|get|post|put|delete|patch|head|options|query|command)\b/i;
222
+ const HTTP_PROTO_RE = /\b(Request|Response|IncomingMessage|ClientRequest|ServerResponse|fetch)\b/i;
239
223
  if (HTTP_PROTO_RE.test(t.message)) {
240
224
  t.severity = 'MEDIUM';
241
225
  }
242
226
  }
243
227
 
244
- // Dist/build/minified files: bundler artifacts get severity downgraded two notches.
245
- // Real malware injects payloads in source files, not in dist/ output.
246
- // Two-notch downgrade (P4): cross-file bonus amplifies dist/ noise in large packages.
247
- // IOC matches and lifecycle scripts are exempt (DIST_EXEMPT_TYPES).
228
+ // Dist/build/minified files: bundler artifacts get severity downgraded one notch.
229
+ // Reduced from two-notch (audit fix): 2-notch made dist/ attacks invisible (CRITICAL→MEDIUM=3pts).
230
+ // Compound detections are exempt (DIST_EXEMPT_TYPES).
248
231
  if (t.file && !DIST_EXEMPT_TYPES.has(t.type) && DIST_FILE_RE.test(t.file)) {
249
- if (t.severity === 'CRITICAL') t.severity = 'MEDIUM';
250
- else if (t.severity === 'HIGH') t.severity = 'LOW';
232
+ if (t.severity === 'CRITICAL') t.severity = 'HIGH';
233
+ else if (t.severity === 'HIGH') t.severity = 'MEDIUM';
251
234
  else if (t.severity === 'MEDIUM') t.severity = 'LOW';
252
235
  }
253
236
 
@@ -352,6 +335,5 @@ function calculateRiskScore(deduped) {
352
335
 
353
336
  module.exports = {
354
337
  SEVERITY_WEIGHTS, RISK_THRESHOLDS, MAX_RISK_SCORE,
355
- BENIGN_PACKAGE_WHITELIST, WHITELIST_EXEMPT_TYPES,
356
338
  isPackageLevelThreat, computeGroupScore, applyFPReductions, calculateRiskScore
357
339
  };
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
- })();
@@ -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
- })();
@@ -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
- })();
@@ -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'));
@@ -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
- }