muaddib-scanner 2.8.8 → 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 +1 -1
- package/src/response/playbooks.js +46 -0
- package/src/rules/index.js +100 -0
- package/src/scanner/ast-detectors.js +94 -0
- package/src/scanner/package.js +60 -1
- package/src/scanner/shell.js +3 -1
- package/src/scoring.js +3 -1
package/package.json
CHANGED
|
@@ -514,6 +514,52 @@ const PLAYBOOKS = {
|
|
|
514
514
|
'Coherence d\'intention detectee — sortie de commande systeme combinee avec exfiltration reseau. ' +
|
|
515
515
|
'Le package execute des commandes et transmet les resultats. Verifier les commandes executees. ' +
|
|
516
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.',
|
|
517
563
|
};
|
|
518
564
|
|
|
519
565
|
function getPlaybook(threatType) {
|
package/src/rules/index.js
CHANGED
|
@@ -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',
|
|
@@ -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
|
|
package/src/scanner/package.js
CHANGED
|
@@ -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 ? '
|
|
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;
|
package/src/scanner/shell.js
CHANGED
|
@@ -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
|
@@ -154,7 +154,9 @@ const DIST_EXEMPT_TYPES = new Set([
|
|
|
154
154
|
'cross_file_dataflow', // credential read → network exfil across files
|
|
155
155
|
'staged_eval_decode', // eval(atob(...)) (explicit payload staging)
|
|
156
156
|
'reverse_shell', // net.Socket + connect + pipe (always malicious)
|
|
157
|
-
'detached_credential_exfil' // detached process + credential exfil (DPRK/Lazarus)
|
|
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)
|
|
158
160
|
// P6: remote_code_load and proxy_data_intercept removed — in bundled dist/ files,
|
|
159
161
|
// fetch + eval co-occurrence is coincidental (bundler combines HTTP client + template compilation).
|
|
160
162
|
// fetch_decrypt_exec (fetch+decrypt+eval triple) remains exempt — never coincidental.
|