muaddib-scanner 2.6.8 → 2.7.0

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 **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.
33
+ MUAD'DIB combines **14 parallel scanners** (133 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
 
@@ -195,7 +195,7 @@ muaddib replay # Ground truth validation (46/49 TPR)
195
195
  | GitHub Actions | Shai-Hulud backdoor detection |
196
196
  | Hash Scanner | Known malicious file hashes |
197
197
 
198
- ### 129 detection rules
198
+ ### 133 detection rules
199
199
 
200
200
  All rules are mapped to MITRE ATT&CK techniques. See [SECURITY.md](SECURITY.md#detection-rules-v262) for the complete rules reference.
201
201
 
@@ -286,7 +286,7 @@ repos:
286
286
  | **FPR** (Benign) | **12.1%** (64/529) | 529 npm packages, real source via `npm pack` |
287
287
  | **ADR** (Adversarial + Holdout) | **92.2%** (71/77) | 53 adversarial + 40 holdout (77 available on disk), global threshold=20 |
288
288
 
289
- **2009 tests** across 46 files, 86% code coverage. **130 rules** (125 RULES + 5 PARANOID).
289
+ **2042 tests** across 49 files. **133 rules** (128 RULES + 5 PARANOID).
290
290
 
291
291
  > **Methodology caveats:**
292
292
  > - TPR measured on 49 Node.js attack samples (3 browser-only excluded from 51 total)
@@ -327,7 +327,7 @@ npm test
327
327
 
328
328
  ### Testing
329
329
 
330
- - **2009 tests** across 46 modular test files - 86% code coverage
330
+ - **2042 tests** across 49 modular test files
331
331
  - **56 fuzz tests** - Malformed inputs, ReDoS, unicode, binary
332
332
  - **Datadog 17K benchmark** - 17,922 real malware samples
333
333
  - **Ground truth validation** - 51 real-world attacks (93.9% TPR)
@@ -347,7 +347,7 @@ npm test
347
347
  - [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) - Experimental protocol, holdout scores
348
348
  - [Threat Model](docs/threat-model.md) - What MUAD'DIB detects and doesn't detect
349
349
  - [Adversarial Evaluation](ADVERSARIAL.md) - Red team samples and ADR results
350
- - [Security Policy](SECURITY.md) - Detection rules reference (129 rules)
350
+ - [Security Policy](SECURITY.md) - Detection rules reference (133 rules)
351
351
  - [Security Audit](docs/SECURITY_AUDIT.md) - Bypass validation report
352
352
  - [FP Analysis](docs/EVALUATION.md) - Historical false positive analysis
353
353
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.6.8",
3
+ "version": "2.7.0",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -48,10 +48,8 @@
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"
52
- },
53
- "overrides": {
54
- "loadash": "0.0.0-security"
51
+ "js-yaml": "4.1.1",
52
+ "loadash": "^1.0.0"
55
53
  },
56
54
  "devDependencies": {
57
55
  "@eslint/js": "10.0.1",
@@ -174,6 +174,15 @@ const PLAYBOOKS = {
174
174
  wget_base64_decode:
175
175
  'Telechargement + decodage base64 detecte. Verifier l\'URL de telechargement et decoder le contenu. Pattern de staging malveillant en deux etapes.',
176
176
 
177
+ curl_ifs_evasion:
178
+ 'CRITIQUE: Evasion IFS detectee — curl$IFS ou curl${IFS} pipe vers shell. Technique d\'evasion pour contourner la detection de "curl|sh". Ne pas installer.',
179
+
180
+ eval_curl_subshell:
181
+ 'CRITIQUE: eval $(curl ...) detecte. Telecharge et execute du code distant via command substitution. Ne pas installer.',
182
+
183
+ sh_c_curl_exec:
184
+ 'sh -c wrapping autour de curl detecte. Technique d\'evasion pour masquer l\'execution de commandes distantes. Analyser le contenu telecharge.',
185
+
177
186
  shai_hulud_backdoor:
178
187
  'CRITIQUE: Backdoor Shai-Hulud dans GitHub Actions. Supprimer le workflow et auditer les runs precedents.',
179
188
 
@@ -1369,6 +1369,35 @@ const RULES = {
1369
1369
  ],
1370
1370
  mitre: 'T1557'
1371
1371
  },
1372
+ // Shell IFS evasion rules (v2.6.9)
1373
+ curl_ifs_evasion: {
1374
+ id: 'MUADDIB-SHELL-016',
1375
+ name: 'Curl IFS Variable Evasion',
1376
+ severity: 'CRITICAL',
1377
+ confidence: 'high',
1378
+ description: 'Evasion IFS: curl$IFS ou curl${IFS} pipe vers shell. Technique d\'evasion pour contourner la detection de curl|sh en utilisant $IFS comme separateur.',
1379
+ references: ['https://attack.mitre.org/techniques/T1059/004/'],
1380
+ mitre: 'T1059.004'
1381
+ },
1382
+ eval_curl_subshell: {
1383
+ id: 'MUADDIB-SHELL-017',
1384
+ name: 'Eval Curl Command Substitution',
1385
+ severity: 'CRITICAL',
1386
+ confidence: 'high',
1387
+ description: 'eval $(curl ...) detecte. Telecharge et execute du code distant via command substitution.',
1388
+ references: ['https://attack.mitre.org/techniques/T1059/004/'],
1389
+ mitre: 'T1059.004'
1390
+ },
1391
+ sh_c_curl_exec: {
1392
+ id: 'MUADDIB-SHELL-018',
1393
+ name: 'Shell -c Curl Execution',
1394
+ severity: 'HIGH',
1395
+ confidence: 'high',
1396
+ description: 'sh -c wrapping autour de curl. Technique d\'evasion pour masquer l\'execution de commandes distantes.',
1397
+ references: ['https://attack.mitre.org/techniques/T1059/004/'],
1398
+ mitre: 'T1059.004'
1399
+ },
1400
+
1372
1401
  // Intent Graph rules (v2.6.0)
1373
1402
  intent_credential_exfil: {
1374
1403
  id: 'MUADDIB-INTENT-001',
@@ -71,6 +71,8 @@ function deobfuscate(sourceCode) {
71
71
  if (isStringFromCharCode(node)) {
72
72
  const nums = extractNumericArgs(node);
73
73
  if (nums === null) return;
74
+ // Validate charcode range [0, 0x10FFFF] to prevent invalid codepoints
75
+ if (nums.some(n => n < 0 || n > 0x10FFFF || !Number.isFinite(n))) return;
74
76
  try {
75
77
  const decoded = String.fromCharCode(...nums);
76
78
  const before = sourceCode.slice(node.start, node.end);
@@ -167,15 +167,16 @@ function analyzeExports(filePath) {
167
167
 
168
168
  // Track which local variables hold sensitive module references
169
169
  // e.g. const fs = require('fs') → moduleVars['fs'] = 'fs'
170
- const moduleVars = {};
170
+ // Object.create(null) prevents prototype pollution via __proto__/constructor keys
171
+ const moduleVars = Object.create(null);
171
172
  // Track which local variables hold tainted values
172
173
  // e.g. const data = fs.readFileSync(...) → taintedVars['data'] = { source, detail }
173
- const taintedVars = {};
174
+ const taintedVars = Object.create(null);
174
175
 
175
176
  // Track class declarations: class Foo { ... }
176
- const classDefs = {};
177
+ const classDefs = Object.create(null);
177
178
  // Track function declarations: function foo() { ... }
178
- const funcDefs = {};
179
+ const funcDefs = Object.create(null);
179
180
  walkAST(ast, (node) => {
180
181
  if (node.type === 'ClassDeclaration' && node.id && node.id.name) {
181
182
  classDefs[node.id.name] = node;
@@ -468,7 +469,7 @@ function describeSensitiveCall(mod, method, args) {
468
469
  */
469
470
  function scanBodyForTaint(body, moduleVars, taintedVars) {
470
471
  // Collect local tainted vars within this function scope too
471
- const localTainted = { ...taintedVars };
472
+ const localTainted = Object.assign(Object.create(null), taintedVars);
472
473
 
473
474
  let found = null;
474
475
  walkAST({ type: 'Program', body }, (node) => {
@@ -20,7 +20,11 @@ const MALICIOUS_PATTERNS = [
20
20
  { pattern: /mkfifo.*\/dev\/tcp/m, name: 'fifo_reverse_shell', severity: 'CRITICAL' },
21
21
  { pattern: /mkfifo\s+\S+.*(?:\|\s*nc\s|nc\s+\S+.*>\s*\/tmp\/)/m, name: 'fifo_nc_reverse_shell', severity: 'CRITICAL' },
22
22
  { pattern: /base64\s+-d\b.*\|\s*(ba)?sh/m, name: 'base64_decode_exec', severity: 'CRITICAL' },
23
- { pattern: /wget\s+\S+.*&&.*base64\s+-d/m, name: 'wget_base64_decode', severity: 'HIGH' }
23
+ { pattern: /wget\s+\S+.*&&.*base64\s+-d/m, name: 'wget_base64_decode', severity: 'HIGH' },
24
+ // IFS evasion patterns (v2.6.9)
25
+ { pattern: /curl\$\{?IFS\}?.*\|.*sh/m, name: 'curl_ifs_evasion', severity: 'CRITICAL' },
26
+ { pattern: /eval\s+.*\$\(curl/m, name: 'eval_curl_subshell', severity: 'CRITICAL' },
27
+ { pattern: /sh\s+-c\s+['"].*curl/m, name: 'sh_c_curl_exec', severity: 'HIGH' }
24
28
  ];
25
29
 
26
30
  const SHEBANG_RE = /^#!.*\b(?:ba)?sh\b/;
@@ -116,8 +116,15 @@ async function safeDnsResolve(hostname) {
116
116
  return hostname;
117
117
  }
118
118
  const dns = require('dns');
119
- const addresses = await dns.promises.resolve4(hostname);
120
- if (!addresses || addresses.length === 0) {
119
+ const [v4, v6] = await Promise.allSettled([
120
+ dns.promises.resolve4(hostname),
121
+ dns.promises.resolve6(hostname),
122
+ ]);
123
+ const addresses = [
124
+ ...(v4.status === 'fulfilled' ? v4.value : []),
125
+ ...(v6.status === 'fulfilled' ? v6.value : []),
126
+ ];
127
+ if (addresses.length === 0) {
121
128
  throw new Error(`DNS resolution failed for ${hostname}`);
122
129
  }
123
130
  for (const addr of addresses) {