muaddib-scanner 2.8.7 → 2.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.8.7",
3
+ "version": "2.9.0",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -44,7 +44,7 @@
44
44
  "node": ">=18.0.0"
45
45
  },
46
46
  "dependencies": {
47
- "@inquirer/prompts": "8.3.0",
47
+ "@inquirer/prompts": "8.3.2",
48
48
  "acorn": "8.16.0",
49
49
  "acorn-walk": "8.3.5",
50
50
  "adm-zip": "0.5.16",
package/src/index.js CHANGED
@@ -567,6 +567,33 @@ async function run(targetPath, options = {}) {
567
567
  }
568
568
  } catch { /* graceful fallback */ }
569
569
 
570
+ // Cross-scanner compound: detached_process + suspicious_dataflow in same file
571
+ // Catches cases where credential flow is detected by dataflow scanner, not AST scanner
572
+ {
573
+ const fileMap = Object.create(null);
574
+ for (const t of deduped) {
575
+ if (t.file) {
576
+ if (!fileMap[t.file]) fileMap[t.file] = [];
577
+ fileMap[t.file].push(t);
578
+ }
579
+ }
580
+ for (const file of Object.keys(fileMap)) {
581
+ const fileThreats = fileMap[file];
582
+ const hasDetached = fileThreats.some(t => t.type === 'detached_process');
583
+ const hasCredFlow = fileThreats.some(t => t.type === 'suspicious_dataflow');
584
+ const alreadyCompound = fileThreats.some(t => t.type === 'detached_credential_exfil');
585
+ if (hasDetached && hasCredFlow && !alreadyCompound) {
586
+ deduped.push({
587
+ type: 'detached_credential_exfil',
588
+ severity: 'CRITICAL',
589
+ message: 'Detached process + credential dataflow — background exfiltration (cross-scanner compound).',
590
+ file,
591
+ count: 1
592
+ });
593
+ }
594
+ }
595
+ }
596
+
570
597
  // FP reduction: legitimate frameworks produce high volumes of certain threat types.
571
598
  // A malware package typically has 1-3 occurrences, not dozens.
572
599
  applyFPReductions(deduped, reachableFiles, packageName, packageDeps);
@@ -134,9 +134,15 @@ function getStats() {
134
134
  *
135
135
  * @param {string} packageName - package name to relabel
136
136
  * @param {string} newLabel - 'fp' or 'confirmed'
137
+ * @param {number} [sandboxFindingCount] - number of sandbox findings (defense-in-depth for 'confirmed')
137
138
  * @returns {number} number of records updated
138
139
  */
139
- function relabelRecords(packageName, newLabel) {
140
+ function relabelRecords(packageName, newLabel, sandboxFindingCount) {
141
+ // Defense-in-depth: never write 'confirmed' without real sandbox findings
142
+ if (newLabel === 'confirmed' && (!sandboxFindingCount || sandboxFindingCount === 0)) {
143
+ console.warn(`[ML] BLOCKED relabel to 'confirmed' for ${packageName}: sandbox_finding_count=${sandboxFindingCount || 0}`);
144
+ return 0;
145
+ }
140
146
  try {
141
147
  if (!fs.existsSync(TRAINING_FILE)) return 0;
142
148
  const content = fs.readFileSync(TRAINING_FILE, 'utf8');
@@ -501,6 +501,10 @@ const PLAYBOOKS = {
501
501
  'CRITIQUE: Un Proxy JavaScript avec trap set/get/apply est combine avec un appel reseau. ' +
502
502
  'Technique d\'interception: le Proxy capture toutes les ecritures de proprietes (credentials, tokens, config) ' +
503
503
  'et les exfiltre via HTTPS/fetch/dgram. Supprimer le package. Auditer tous les modules qui importent ce package.',
504
+ detached_credential_exfil:
505
+ 'CRITIQUE: Process detache avec acces aux credentials et exfiltration reseau. ' +
506
+ 'Technique DPRK/Lazarus: le process fils survit au parent (detached:true, unref()) et exfiltre des secrets en arriere-plan. ' +
507
+ 'Supprimer le package immediatement. Regenerer tous les tokens/credentials. Auditer les process en cours d\'execution.',
504
508
  intent_credential_exfil:
505
509
  'CRITIQUE: Coherence d\'intention detectee — lecture de credentials combinee avec exfiltration reseau. ' +
506
510
  'Pattern multi-fichier DPRK/Lazarus: chaque fichier semble legitime individuellement mais le package ' +
@@ -510,6 +514,52 @@ const PLAYBOOKS = {
510
514
  'Coherence d\'intention detectee — sortie de commande systeme combinee avec exfiltration reseau. ' +
511
515
  'Le package execute des commandes et transmet les resultats. Verifier les commandes executees. ' +
512
516
  'Supprimer le package si non attendu. Auditer les logs reseau pour identifier les donnees exfiltrees.',
517
+
518
+ bin_field_hijack:
519
+ 'CRITIQUE: Le champ "bin" de package.json shadow une commande systeme (node, npm, git, bash, etc.). ' +
520
+ 'A l\'installation, npm cree un symlink dans node_modules/.bin/ qui intercepte la commande reelle. ' +
521
+ 'Tous les npm scripts executeront le code malveillant au lieu de la vraie commande. ' +
522
+ 'NE PAS installer. Si deja installe: rm -rf node_modules && npm cache clean --force && npm install.',
523
+
524
+ git_dependency_rce:
525
+ 'Dependance utilisant une URL git+ ou git://. Vecteur d\'attaque PackageGate: si le package contient ' +
526
+ 'un .npmrc avec git=./malicious.sh, npm executera le script au lieu de git, meme avec --ignore-scripts. ' +
527
+ 'Verifier le contenu du .npmrc. Ne pas installer de packages avec des dependances git non vérifiées.',
528
+
529
+ npmrc_git_override:
530
+ 'CRITIQUE: Fichier .npmrc contient git= override — technique PackageGate. Le binaire git est remplace ' +
531
+ 'par un script controle par l\'attaquant. TOUTE operation git (clone, fetch, pull) executera le script malveillant. ' +
532
+ 'NE PAS installer. Si deja installe: supprimer le package, verifier .npmrc, reinstaller git.',
533
+
534
+ node_modules_write:
535
+ 'CRITIQUE: Le code ecrit dans node_modules/ — technique de propagation worm Shai-Hulud 2.0. ' +
536
+ 'Le malware modifie d\'autres packages installes (ethers, webpack, etc.) pour injecter un backdoor persistent. ' +
537
+ 'Verifier l\'integrite de tous les packages: rm -rf node_modules && npm install. ' +
538
+ 'Auditer les fichiers modifies. Regenerer tous les secrets si le code a ete execute.',
539
+
540
+ bun_runtime_evasion:
541
+ 'Invocation du runtime Bun detectee — technique Shai-Hulud 2.0. Le payload est execute via bun run ' +
542
+ 'au lieu de node, echappant a toutes les sandboxes Node.js et au monitoring (--experimental-permission). ' +
543
+ 'Verifier si bun est installe: which bun. Supprimer le package. ' +
544
+ 'Auditer les processus: ps aux | grep bun.',
545
+
546
+ static_timer_bomb:
547
+ 'Timer avec delai > 1h detecte dans l\'analyse statique (setTimeout/setInterval). ' +
548
+ 'Technique PhantomRaven: le payload s\'active 48h+ apres l\'installation pour echapper aux sandboxes. ' +
549
+ 'Analyser le callback du timer pour identifier le payload retarde. ' +
550
+ 'Si delai > 24h: fort indicateur de time-bomb malware. NE PAS installer.',
551
+
552
+ npm_publish_worm:
553
+ 'CRITIQUE: exec("npm publish") detecte — propagation worm. Le code utilise des tokens npm voles ' +
554
+ 'pour publier des versions infectees des packages de la victime. Technique Shai-Hulud 1.0 et 2.0. ' +
555
+ 'Isoler immediatement la machine. Revoquer les tokens npm: npm token revoke. ' +
556
+ 'Verifier les packages publies: npm profile ls. Signaler sur npm.',
557
+
558
+ ollama_local_llm:
559
+ 'Reference au port Ollama (11434) detectee. PhantomRaven Wave 4 utilise un LLM local (DeepSeek Coder) ' +
560
+ 'pour reecrire le malware a chaque execution, evitant la detection par signature. Moteur polymorphe. ' +
561
+ 'Verifier si Ollama est installe: curl http://localhost:11434/api/tags. ' +
562
+ 'Aucun package npm legitime n\'appelle un LLM local. Supprimer le package.',
513
563
  };
514
564
 
515
565
  function getPlaybook(threatType) {
@@ -1378,6 +1378,106 @@ const RULES = {
1378
1378
  ],
1379
1379
  mitre: 'T1557'
1380
1380
  },
1381
+ // Package manifest detections (v2.8.9)
1382
+ bin_field_hijack: {
1383
+ id: 'MUADDIB-PKG-013',
1384
+ name: 'Bin Field PATH Hijack',
1385
+ severity: 'CRITICAL',
1386
+ confidence: 'high',
1387
+ description: 'Le champ "bin" de package.json shadow une commande systeme (node, npm, git, bash, etc.). A l\'install, npm cree un symlink dans node_modules/.bin/ qui intercepte la commande reelle pour tous les npm scripts.',
1388
+ references: [
1389
+ 'https://socket.dev/blog/2025-supply-chain-report',
1390
+ 'https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack'
1391
+ ],
1392
+ mitre: 'T1574.007'
1393
+ },
1394
+ git_dependency_rce: {
1395
+ id: 'MUADDIB-PKG-014',
1396
+ name: 'Git Dependency RCE (PackageGate)',
1397
+ severity: 'HIGH',
1398
+ confidence: 'medium',
1399
+ description: 'Dependance utilisant une URL git+ ou git://. Vecteur PackageGate: un .npmrc malveillant peut overrider le binaire git, permettant l\'execution de code meme avec --ignore-scripts.',
1400
+ references: [
1401
+ 'https://socket.dev/blog/packagegate-npm-rce',
1402
+ 'https://attack.mitre.org/techniques/T1195/002/'
1403
+ ],
1404
+ mitre: 'T1195.002'
1405
+ },
1406
+ npmrc_git_override: {
1407
+ id: 'MUADDIB-PKG-015',
1408
+ name: '.npmrc Git Binary Override',
1409
+ severity: 'CRITICAL',
1410
+ confidence: 'high',
1411
+ description: 'Fichier .npmrc contient git= override — technique PackageGate: remplace le binaire git par un script controle par l\'attaquant.',
1412
+ references: [
1413
+ 'https://socket.dev/blog/packagegate-npm-rce'
1414
+ ],
1415
+ mitre: 'T1195.002'
1416
+ },
1417
+
1418
+ // AST detections (v2.8.9 — supply-chain gaps)
1419
+ node_modules_write: {
1420
+ id: 'MUADDIB-AST-048',
1421
+ name: 'Write to node_modules/ (Worm Propagation)',
1422
+ severity: 'CRITICAL',
1423
+ confidence: 'high',
1424
+ description: 'writeFileSync/writeFile/appendFileSync ciblant node_modules/ — technique de propagation worm Shai-Hulud 2.0: modifie d\'autres packages installes pour injecter un backdoor persistent.',
1425
+ references: [
1426
+ 'https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack',
1427
+ 'https://attack.mitre.org/techniques/T1195/002/'
1428
+ ],
1429
+ mitre: 'T1195.002'
1430
+ },
1431
+ bun_runtime_evasion: {
1432
+ id: 'MUADDIB-AST-049',
1433
+ name: 'Bun Runtime Evasion',
1434
+ severity: 'HIGH',
1435
+ confidence: 'medium',
1436
+ description: 'Invocation du runtime Bun (bun run/exec/install) via exec/spawn — technique Shai-Hulud 2.0: utilise un runtime alternatif pour echapper aux sandboxes et monitoring Node.js.',
1437
+ references: [
1438
+ 'https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack',
1439
+ 'https://attack.mitre.org/techniques/T1059/'
1440
+ ],
1441
+ mitre: 'T1059'
1442
+ },
1443
+ static_timer_bomb: {
1444
+ id: 'MUADDIB-AST-050',
1445
+ name: 'Static Timer Bomb',
1446
+ severity: 'MEDIUM',
1447
+ confidence: 'medium',
1448
+ description: 'setTimeout/setInterval avec delai > 1h detecte statiquement. PhantomRaven active le 2nd stage 48h+ apres install. Evasion temporelle: le payload s\'active bien apres l\'installation pour echapper aux sandboxes.',
1449
+ references: [
1450
+ 'https://www.sonatype.com/blog/phantomraven-supply-chain-attack',
1451
+ 'https://attack.mitre.org/techniques/T1497/003/'
1452
+ ],
1453
+ mitre: 'T1497.003'
1454
+ },
1455
+ npm_publish_worm: {
1456
+ id: 'MUADDIB-AST-051',
1457
+ name: 'npm publish Worm Propagation',
1458
+ severity: 'CRITICAL',
1459
+ confidence: 'high',
1460
+ description: 'exec("npm publish") detecte — technique de propagation worm Shai-Hulud: utilise les tokens npm voles pour publier des versions infectees des packages de la victime.',
1461
+ references: [
1462
+ 'https://blog.phylum.io/shai-hulud-npm-worm',
1463
+ 'https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack',
1464
+ 'https://attack.mitre.org/techniques/T1195/002/'
1465
+ ],
1466
+ mitre: 'T1195.002'
1467
+ },
1468
+ ollama_local_llm: {
1469
+ id: 'MUADDIB-AST-052',
1470
+ name: 'Ollama Local LLM (Polymorphic Engine)',
1471
+ severity: 'HIGH',
1472
+ confidence: 'medium',
1473
+ description: 'Reference au port 11434 (Ollama) detectee. PhantomRaven Wave 4 utilise un LLM local pour reecrire le malware et eviter la detection signature. Moteur polymorphe.',
1474
+ references: [
1475
+ 'https://www.sonatype.com/blog/phantomraven-supply-chain-attack',
1476
+ 'https://attack.mitre.org/techniques/T1027/005/'
1477
+ ],
1478
+ mitre: 'T1027.005'
1479
+ },
1480
+
1381
1481
  // Shell IFS evasion rules (v2.6.9)
1382
1482
  curl_ifs_evasion: {
1383
1483
  id: 'MUADDIB-SHELL-016',
@@ -1408,6 +1508,18 @@ const RULES = {
1408
1508
  },
1409
1509
 
1410
1510
  // Intent Graph rules (v2.6.0)
1511
+ detached_credential_exfil: {
1512
+ id: 'MUADDIB-AST-047',
1513
+ name: 'Detached Process Credential Exfiltration',
1514
+ severity: 'CRITICAL',
1515
+ confidence: 'high',
1516
+ description: 'Process detache (survit au parent) avec acces aux credentials et appel reseau — technique DPRK/Lazarus pour exfiltrer des secrets en arriere-plan',
1517
+ references: [
1518
+ 'https://attack.mitre.org/techniques/T1041/',
1519
+ 'https://www.cisa.gov/news-events/cybersecurity-advisories/aa22-108a'
1520
+ ],
1521
+ mitre: 'T1041'
1522
+ },
1411
1523
  intent_credential_exfil: {
1412
1524
  id: 'MUADDIB-INTENT-001',
1413
1525
  name: 'Intent Credential Exfiltration',
@@ -705,6 +705,26 @@ function handleCallExpression(node, ctx) {
705
705
  break;
706
706
  }
707
707
  }
708
+
709
+ // Bun runtime evasion: exec/spawn using bun instead of node (Shai-Hulud 2.0)
710
+ if (/\bbun\s+(run|exec|install|x)\b/.test(cmdStr)) {
711
+ ctx.threats.push({
712
+ type: 'bun_runtime_evasion',
713
+ severity: 'HIGH',
714
+ message: `Bun runtime invocation detected: "${cmdStr.slice(0, 80)}" — alternative runtime to evade Node.js monitoring/sandboxing.`,
715
+ file: ctx.relFile
716
+ });
717
+ }
718
+
719
+ // Worm propagation: npm publish / npm adduser / npm token create (Shai-Hulud)
720
+ if (/\bnpm\s+(publish|adduser|token\s+create|login)\b/.test(cmdStr)) {
721
+ ctx.threats.push({
722
+ type: 'npm_publish_worm',
723
+ severity: 'CRITICAL',
724
+ message: `exec("${cmdStr.slice(0, 80)}") — worm propagation: publishing packages with stolen credentials.`,
725
+ file: ctx.relFile
726
+ });
727
+ }
708
728
  }
709
729
  }
710
730
 
@@ -785,6 +805,30 @@ function handleCallExpression(node, ctx) {
785
805
  }
786
806
  }
787
807
 
808
+ // Detect spawn('bun', ['run', ...]) — Bun runtime evasion via spawn
809
+ if ((callName === 'spawn' || callName === 'execFile') && node.arguments.length >= 1) {
810
+ const bunArg = node.arguments[0];
811
+ if (bunArg.type === 'Literal' && typeof bunArg.value === 'string' && bunArg.value === 'bun') {
812
+ // Check the args array for run/exec/install/x
813
+ const argsArr = node.arguments[1];
814
+ let bunCmd = 'bun';
815
+ if (argsArr?.type === 'ArrayExpression' && argsArr.elements.length > 0) {
816
+ const firstEl = argsArr.elements[0];
817
+ if (firstEl?.type === 'Literal' && typeof firstEl.value === 'string') {
818
+ bunCmd = `bun ${firstEl.value}`;
819
+ }
820
+ }
821
+ if (/\bbun\s*(run|exec|install|x)?$/.test(bunCmd) || bunCmd === 'bun') {
822
+ ctx.threats.push({
823
+ type: 'bun_runtime_evasion',
824
+ severity: 'HIGH',
825
+ message: `spawn("bun", [...]) — Bun runtime invocation to evade Node.js monitoring/sandboxing.`,
826
+ file: ctx.relFile
827
+ });
828
+ }
829
+ }
830
+ }
831
+
788
832
  // Detect spawn/fork with {detached: true}
789
833
  if ((callName === 'spawn' || callName === 'fork') && node.arguments.length >= 2) {
790
834
  const lastArg = node.arguments[node.arguments.length - 1];
@@ -836,6 +880,27 @@ function handleCallExpression(node, ctx) {
836
880
  }
837
881
  }
838
882
 
883
+ // Detect writes to node_modules/ — worm propagation / package patching (Shai-Hulud 2.0)
884
+ if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
885
+ const nmWriteMethod = node.callee.property.name;
886
+ if (['writeFileSync', 'writeFile', 'appendFileSync'].includes(nmWriteMethod) && node.arguments.length >= 2) {
887
+ const nmPathArg = node.arguments[0];
888
+ let nmPathStr = extractStringValueDeep(nmPathArg);
889
+ // Also resolve variable indirection
890
+ if (!nmPathStr && nmPathArg?.type === 'Identifier' && ctx.stringVarValues?.has(nmPathArg.name)) {
891
+ nmPathStr = ctx.stringVarValues.get(nmPathArg.name);
892
+ }
893
+ if (nmPathStr && /node_modules[/\\]/.test(nmPathStr)) {
894
+ ctx.threats.push({
895
+ type: 'node_modules_write',
896
+ severity: 'CRITICAL',
897
+ message: `${nmWriteMethod}() targeting node_modules/ path: "${nmPathStr.substring(0, 80)}" — package patching / worm propagation technique.`,
898
+ file: ctx.relFile
899
+ });
900
+ }
901
+ }
902
+ }
903
+
839
904
  // Detect fs.mkdirSync creating .github/workflows
840
905
  if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
841
906
  const mkdirMethod = node.callee.property.name;
@@ -1200,6 +1265,24 @@ function handleCallExpression(node, ctx) {
1200
1265
  file: ctx.relFile
1201
1266
  });
1202
1267
  }
1268
+
1269
+ // Static timer bomb: setTimeout/setInterval with delay > 1 hour (PhantomRaven 48h delay)
1270
+ if (node.arguments.length >= 2) {
1271
+ const delayArg = node.arguments[1];
1272
+ let delayMs = null;
1273
+ if (delayArg.type === 'Literal' && typeof delayArg.value === 'number') {
1274
+ delayMs = delayArg.value;
1275
+ }
1276
+ if (delayMs !== null && delayMs > 3600000) { // > 1 hour
1277
+ const hours = (delayMs / 3600000).toFixed(1);
1278
+ ctx.threats.push({
1279
+ type: 'static_timer_bomb',
1280
+ severity: delayMs > 86400000 ? 'HIGH' : 'MEDIUM', // > 24h = HIGH
1281
+ message: `${callName}() with ${hours}h delay (${delayMs}ms) — time-bomb evasion: payload activates long after install.`,
1282
+ file: ctx.relFile
1283
+ });
1284
+ }
1285
+ }
1203
1286
  }
1204
1287
 
1205
1288
  // Detect eval.call(null, code) / eval.apply(null, [code]) / Function.call/apply
@@ -1727,6 +1810,17 @@ function handleLiteral(node, ctx) {
1727
1810
  break;
1728
1811
  }
1729
1812
  }
1813
+
1814
+ // Ollama LLM local: polymorphic engine indicator (PhantomRaven Wave 4)
1815
+ // Port 11434 is Ollama's default port. Legitimate packages don't call local LLMs.
1816
+ if (/(?:localhost|127\.0\.0\.1):11434/.test(node.value)) {
1817
+ ctx.threats.push({
1818
+ type: 'ollama_local_llm',
1819
+ severity: 'HIGH',
1820
+ message: `Reference to Ollama LLM API (${node.value.slice(0, 60)}) — polymorphic malware engine: uses local LLM to rewrite code and evade detection.`,
1821
+ file: ctx.relFile
1822
+ });
1823
+ }
1730
1824
  }
1731
1825
  }
1732
1826
 
@@ -2187,6 +2281,24 @@ function handlePostWalk(ctx) {
2187
2281
  file: ctx.relFile
2188
2282
  });
2189
2283
  }
2284
+
2285
+ // DPRK/Lazarus compound: detached background process + credential env access + network
2286
+ // Pattern: spawn({detached:true}) reads secrets then exfils via network.
2287
+ // This combination is never legitimate — daemons don't read API keys and send them out.
2288
+ const hasDetachedInFile = ctx.threats.some(t =>
2289
+ t.file === ctx.relFile && t.type === 'detached_process'
2290
+ );
2291
+ const hasSensitiveEnvInFile = ctx.threats.some(t =>
2292
+ t.file === ctx.relFile && t.type === 'env_access'
2293
+ );
2294
+ if (hasDetachedInFile && hasSensitiveEnvInFile && ctx.hasNetworkCallInFile) {
2295
+ ctx.threats.push({
2296
+ type: 'detached_credential_exfil',
2297
+ severity: 'CRITICAL',
2298
+ message: 'Detached process + sensitive env access + network call — credential exfiltration via background process (DPRK/Lazarus evasion pattern).',
2299
+ file: ctx.relFile
2300
+ });
2301
+ }
2190
2302
  }
2191
2303
 
2192
2304
  function handleWithStatement(node, ctx) {
@@ -28,6 +28,13 @@ const DANGEROUS_PATTERNS = [
28
28
  const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype', 'toString', 'valueOf']);
29
29
  const DEP_FP_WHITELIST = new Set(['es5-ext', 'bootstrap-sass']);
30
30
 
31
+ // System commands that should never be shadowed via the "bin" field (PATH hijack)
32
+ const SHADOWED_COMMANDS = new Set([
33
+ 'node', 'npm', 'npx', 'git', 'sh', 'bash', 'zsh', 'python', 'python3',
34
+ 'curl', 'wget', 'ssh', 'scp', 'tar', 'make', 'gcc', 'go', 'ruby',
35
+ 'perl', 'php', 'java', 'javac', 'pip', 'pip3', 'yarn', 'pnpm', 'bun'
36
+ ]);
37
+
31
38
  /**
32
39
  * Clean a version specifier to extract the primary version number.
33
40
  * Handles: ^1.0.0, ~1.0.0, >=1.0.0, >=1.0.0,<2.0.0, git URLs, etc.
@@ -95,6 +102,16 @@ async function scanPackageJson(targetPath) {
95
102
  });
96
103
  }
97
104
  }
105
+
106
+ // Detect Bun runtime evasion in lifecycle scripts (Shai-Hulud 2.0)
107
+ if (/\bbun\s+(run|exec|install|x)\b/.test(scriptContent) || /\bbunx\s+/.test(scriptContent)) {
108
+ threats.push({
109
+ type: 'bun_runtime_evasion',
110
+ severity: 'HIGH',
111
+ message: `Bun runtime invocation in lifecycle script "${scriptName}" — alternative runtime to evade Node.js monitoring/sandboxing.`,
112
+ file: 'package.json'
113
+ });
114
+ }
98
115
  }
99
116
  }
100
117
 
@@ -113,6 +130,39 @@ async function scanPackageJson(targetPath) {
113
130
  }
114
131
  }
115
132
 
133
+ // Detect bin field hijacking: shadowing system commands (node, npm, git, bash, etc.)
134
+ if (pkg.bin) {
135
+ const binEntries = typeof pkg.bin === 'string'
136
+ ? { [pkg.name]: pkg.bin }
137
+ : pkg.bin;
138
+ for (const [cmdName, cmdPath] of Object.entries(binEntries || {})) {
139
+ if (SHADOWED_COMMANDS.has(cmdName)) {
140
+ threats.push({
141
+ type: 'bin_field_hijack',
142
+ severity: 'CRITICAL',
143
+ message: `package.json "bin" field shadows system command "${cmdName}" → ${cmdPath}. PATH hijack: all npm scripts will execute this instead of the real ${cmdName}.`,
144
+ file: 'package.json'
145
+ });
146
+ }
147
+ }
148
+ }
149
+
150
+ // Detect .npmrc with git= override (PackageGate technique)
151
+ const npmrcPath = path.join(targetPath, '.npmrc');
152
+ if (fs.existsSync(npmrcPath)) {
153
+ try {
154
+ const npmrcContent = fs.readFileSync(npmrcPath, 'utf8');
155
+ if (/^git\s*=/m.test(npmrcContent)) {
156
+ threats.push({
157
+ type: 'npmrc_git_override',
158
+ severity: 'CRITICAL',
159
+ message: '.npmrc contains git= override — PackageGate technique: replaces git binary with attacker-controlled script.',
160
+ file: '.npmrc'
161
+ });
162
+ }
163
+ } catch { /* permission error */ }
164
+ }
165
+
116
166
  // Scan declared dependencies against IOCs
117
167
  let iocs;
118
168
  try {
@@ -156,12 +206,21 @@ async function scanPackageJson(targetPath) {
156
206
  ].some(p => p.test(urlLower));
157
207
  threats.push({
158
208
  type: 'dependency_url_suspicious',
159
- severity: isSuspicious ? 'HIGH' : 'MEDIUM',
209
+ severity: isSuspicious ? 'CRITICAL' : 'HIGH',
160
210
  message: `Dependency "${depName}" uses HTTP URL: ${depVersion}` +
161
211
  (isSuspicious ? ' (tunnel/private/localhost)' : ' (unusual, verify source)'),
162
212
  file: 'package.json'
163
213
  });
164
214
  }
215
+ // Detect git-based dependencies — potential PackageGate RCE vector
216
+ if (typeof depVersion === 'string' && /^git[+:]/.test(depVersion)) {
217
+ threats.push({
218
+ type: 'git_dependency_rce',
219
+ severity: 'HIGH',
220
+ message: `Dependency "${depName}" uses git URL: ${depVersion} — potential PackageGate RCE vector (malicious .npmrc can override git binary).`,
221
+ file: 'package.json'
222
+ });
223
+ }
165
224
  // Skip known FP packages that share names with malicious IOC entries
166
225
  if (DEP_FP_WHITELIST.has(depName)) continue;
167
226
  let malicious = null;
@@ -24,7 +24,9 @@ const MALICIOUS_PATTERNS = [
24
24
  // IFS evasion patterns (v2.6.9)
25
25
  { pattern: /curl\$\{?IFS\}?.*\|.*sh/m, name: 'curl_ifs_evasion', severity: 'CRITICAL' },
26
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' }
27
+ { pattern: /sh\s+-c\s+['"].*curl/m, name: 'sh_c_curl_exec', severity: 'HIGH' },
28
+ // Bun runtime evasion (v2.8.9 — Shai-Hulud 2.0)
29
+ { pattern: /\bbun\s+run\b/m, name: 'bun_runtime_evasion', severity: 'HIGH' }
28
30
  ];
29
31
 
30
32
  const SHEBANG_RE = /^#!.*\b(?:ba)?sh\b/;
package/src/scoring.js CHANGED
@@ -153,7 +153,10 @@ const DIST_EXEMPT_TYPES = new Set([
153
153
  'download_exec_binary', // download + chmod + exec (binary dropper)
154
154
  'cross_file_dataflow', // credential read → network exfil across files
155
155
  'staged_eval_decode', // eval(atob(...)) (explicit payload staging)
156
- 'reverse_shell' // net.Socket + connect + pipe (always malicious)
156
+ 'reverse_shell', // net.Socket + connect + pipe (always malicious)
157
+ 'detached_credential_exfil', // detached process + credential exfil (DPRK/Lazarus)
158
+ 'node_modules_write', // writeFile to node_modules/ (worm propagation)
159
+ 'npm_publish_worm' // exec("npm publish") (worm propagation)
157
160
  // P6: remote_code_load and proxy_data_intercept removed — in bundled dist/ files,
158
161
  // fetch + eval co-occurrence is coincidental (bundler combines HTTP client + template compilation).
159
162
  // fetch_decrypt_exec (fetch+decrypt+eval triple) remains exempt — never coincidental.
@@ -196,7 +199,8 @@ const REACHABILITY_EXEMPT_TYPES = new Set([
196
199
  'cross_file_dataflow',
197
200
  'typosquat_detected', 'pypi_typosquat_detected',
198
201
  'pypi_malicious_package',
199
- 'ai_config_injection', 'ai_config_injection_compound'
202
+ 'ai_config_injection', 'ai_config_injection_compound',
203
+ 'detached_credential_exfil' // DPRK/Lazarus: invoked via lifecycle, not require/import
200
204
  ]);
201
205
 
202
206
  // Custom class prototypes that HTTP frameworks legitimately extend.