muaddib-scanner 2.2.27 → 2.2.28

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.2.27",
3
+ "version": "2.2.28",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -354,6 +354,31 @@ const PLAYBOOKS = {
354
354
  'execution via require() ou Module._compile(), puis suppression via unlinkSync/rmSync. ' +
355
355
  'Ce pattern de staging est typique des malwares cherchant a eviter la detection post-mortem. ' +
356
356
  'Isoler la machine et inspecter les artefacts temporaires avant nettoyage.',
357
+
358
+ mcp_config_injection:
359
+ 'CRITIQUE: Ecriture dans les fichiers de configuration MCP d\'assistants IA (.claude/, .cursor/, .continue/, .vscode/). ' +
360
+ 'Technique SANDWORM_MODE: le malware empoisonne la configuration MCP pour ajouter des serveurs malveillants. ' +
361
+ 'Verifier immediatement les fichiers de config IA. Supprimer les entrees MCP non reconnues. Supprimer le package.',
362
+
363
+ git_hooks_injection:
364
+ 'Injection de hooks Git detectee. Le package ecrit dans .git/hooks/ (pre-commit, pre-push, etc.) ' +
365
+ 'ou modifie git config init.templateDir pour la persistence globale. ' +
366
+ 'Verifier .git/hooks/ et git config --global --list. Supprimer les hooks non reconnus.',
367
+
368
+ env_harvesting_dynamic:
369
+ 'Collecte dynamique de variables d\'environnement via Object.entries/keys/values(process.env) ' +
370
+ 'avec filtrage par patterns sensibles. Technique de vol de credentials a grande echelle. ' +
371
+ 'Verifier les tokens/secrets exposes. Revoquer immediatement les credentials compromis.',
372
+
373
+ dns_chunk_exfiltration:
374
+ 'Exfiltration via requetes DNS detectee. Les donnees sont encodees en base64 et envoyees comme sous-domaines DNS. ' +
375
+ 'Cette technique contourne les firewalls et proxies car le DNS est rarement filtre. ' +
376
+ 'Bloquer les requetes DNS sortantes du package. Verifier les donnees exfiltrees.',
377
+
378
+ llm_api_key_harvesting:
379
+ 'Acces a 3+ cles API de providers LLM (OpenAI, Anthropic, Google, etc.). Usage unique = legitime, ' +
380
+ 'acces multiples = collecte pour revente ou abus. Verifier si le package a une raison ' +
381
+ 'legitime d\'acceder a plusieurs providers. Revoquer les cles exposees si necessaire.',
357
382
  };
358
383
 
359
384
  function getPlaybook(threatType) {
@@ -713,6 +713,66 @@ const RULES = {
713
713
  mitre: 'T1070.004'
714
714
  },
715
715
 
716
+ mcp_config_injection: {
717
+ id: 'MUADDIB-AST-027',
718
+ name: 'MCP Config Injection',
719
+ severity: 'CRITICAL',
720
+ confidence: 'high',
721
+ description: 'Injection de configuration MCP: ecriture dans les fichiers de configuration d\'assistants IA (.claude/, .cursor/, .continue/, .vscode/, .windsurf/). Technique SANDWORM_MODE pour empoisonner la chaine d\'outils IA.',
722
+ references: [
723
+ 'https://attack.mitre.org/techniques/T1546/016/'
724
+ ],
725
+ mitre: 'T1546.016'
726
+ },
727
+
728
+ git_hooks_injection: {
729
+ id: 'MUADDIB-AST-028',
730
+ name: 'Git Hooks Injection',
731
+ severity: 'HIGH',
732
+ confidence: 'high',
733
+ description: 'Injection de hooks Git: ecriture dans .git/hooks/ ou modification de git config init.templateDir. Technique de persistence via hooks pre-commit, pre-push, post-checkout.',
734
+ references: [
735
+ 'https://attack.mitre.org/techniques/T1546/004/'
736
+ ],
737
+ mitre: 'T1546.004'
738
+ },
739
+
740
+ env_harvesting_dynamic: {
741
+ id: 'MUADDIB-AST-029',
742
+ name: 'Dynamic Environment Variable Harvesting',
743
+ severity: 'HIGH',
744
+ confidence: 'high',
745
+ description: 'Collecte dynamique de variables d\'environnement via Object.entries/keys/values(process.env) avec filtrage par patterns sensibles (TOKEN, SECRET, KEY, PASSWORD, AWS, SSH). Technique de vol de credentials.',
746
+ references: [
747
+ 'https://attack.mitre.org/techniques/T1552/001/'
748
+ ],
749
+ mitre: 'T1552.001'
750
+ },
751
+
752
+ dns_chunk_exfiltration: {
753
+ id: 'MUADDIB-AST-030',
754
+ name: 'DNS Chunk Exfiltration',
755
+ severity: 'HIGH',
756
+ confidence: 'high',
757
+ description: 'Exfiltration DNS: donnees encodees en base64 dans les requetes DNS. Canal covert pour contourner les firewalls. Pattern: dns.resolve + Buffer.from().toString("base64").',
758
+ references: [
759
+ 'https://attack.mitre.org/techniques/T1048/003/'
760
+ ],
761
+ mitre: 'T1048.003'
762
+ },
763
+
764
+ llm_api_key_harvesting: {
765
+ id: 'MUADDIB-AST-031',
766
+ name: 'LLM API Key Harvesting',
767
+ severity: 'MEDIUM',
768
+ confidence: 'medium',
769
+ description: 'Collecte de cles API LLM: acces a 3+ variables d\'environnement de providers IA (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY, etc.). Vecteur de monetisation.',
770
+ references: [
771
+ 'https://attack.mitre.org/techniques/T1552/001/'
772
+ ],
773
+ mitre: 'T1552.001'
774
+ },
775
+
716
776
  ai_agent_abuse: {
717
777
  id: 'MUADDIB-AST-013',
718
778
  name: 'AI Agent Weaponization',
@@ -83,6 +83,38 @@ const NODE_HOOKABLE_CLASSES = [
83
83
  'OutgoingMessage', 'Socket', 'Server', 'Agent'
84
84
  ];
85
85
 
86
+ // AI/MCP config paths targeted for config injection (SANDWORM_MODE)
87
+ const MCP_CONFIG_PATHS = [
88
+ '.claude/', 'claude_desktop_config',
89
+ '.cursor/', 'mcp.json',
90
+ '.continue/',
91
+ '.vscode/',
92
+ '.windsurf/', '.codeium/'
93
+ ];
94
+
95
+ // MCP content indicators in written data
96
+ const MCP_CONTENT_PATTERNS = ['mcpServers', '"mcp"', '"server"', '"command"', '"args"'];
97
+
98
+ // Git hooks names
99
+ const GIT_HOOKS = [
100
+ 'pre-commit', 'pre-push', 'post-checkout', 'post-merge',
101
+ 'pre-receive', 'post-receive', 'prepare-commit-msg', 'commit-msg',
102
+ 'pre-rebase', 'post-rewrite', 'pre-auto-gc'
103
+ ];
104
+
105
+ // LLM API key environment variable names (3+ = harvesting)
106
+ const LLM_API_KEY_VARS = [
107
+ 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'GOOGLE_API_KEY',
108
+ 'GROQ_API_KEY', 'TOGETHER_API_KEY', 'FIREWORKS_API_KEY',
109
+ 'REPLICATE_API_KEY', 'MISTRAL_API_KEY', 'COHERE_API_KEY'
110
+ ];
111
+
112
+ // Env harvesting sensitive patterns (for Object.entries/keys/values filtering)
113
+ const ENV_HARVEST_PATTERNS = [
114
+ 'KEY', 'SECRET', 'TOKEN', 'PASSWORD', 'CREDENTIAL',
115
+ 'NPM', 'AWS', 'SSH', 'WEBHOOK'
116
+ ];
117
+
86
118
  // Paths indicating sandbox/container environment detection (anti-analysis)
87
119
  const SANDBOX_INDICATORS = [
88
120
  '/.dockerenv',
@@ -487,6 +519,84 @@ function handleCallExpression(node, ctx) {
487
519
  }
488
520
  }
489
521
 
522
+ // SANDWORM_MODE R5: MCP config injection — writeFileSync to AI config paths
523
+ if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
524
+ const mcpWriteMethod = node.callee.property.name;
525
+ if (['writeFileSync', 'writeFile'].includes(mcpWriteMethod) && node.arguments.length >= 2) {
526
+ const mcpPathArg = node.arguments[0];
527
+ const mcpPathStr = extractStringValue(mcpPathArg);
528
+ // Also check path.join() calls
529
+ let mcpJoinedPath = '';
530
+ if (mcpPathArg?.type === 'CallExpression' && mcpPathArg.arguments) {
531
+ mcpJoinedPath = mcpPathArg.arguments.map(a => extractStringValue(a) || '').join('/');
532
+ }
533
+ const mcpCheckPath = (mcpPathStr || mcpJoinedPath).toLowerCase();
534
+ const isMcpPath = MCP_CONFIG_PATHS.some(p => mcpCheckPath.includes(p.toLowerCase()));
535
+ if (isMcpPath) {
536
+ // Check content argument for MCP-related patterns
537
+ const contentArg = node.arguments[1];
538
+ const contentStr = extractStringValue(contentArg);
539
+ const hasContentPattern = contentStr
540
+ ? MCP_CONTENT_PATTERNS.some(p => contentStr.includes(p.replace(/"/g, '')))
541
+ : true; // dynamic content = suspicious by default for AI config paths
542
+ if (hasContentPattern) {
543
+ ctx.threats.push({
544
+ type: 'mcp_config_injection',
545
+ severity: 'CRITICAL',
546
+ message: `MCP config injection: ${mcpWriteMethod}() writes to AI assistant configuration (${mcpCheckPath}). SANDWORM_MODE technique for AI toolchain poisoning.`,
547
+ file: ctx.relFile
548
+ });
549
+ }
550
+ }
551
+ }
552
+ }
553
+
554
+ // SANDWORM_MODE R6: Git hooks injection — writeFileSync to .git/hooks/
555
+ if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
556
+ const gitWriteMethod = node.callee.property.name;
557
+ if (['writeFileSync', 'writeFile'].includes(gitWriteMethod) && node.arguments.length >= 1) {
558
+ const gitPathArg = node.arguments[0];
559
+ const gitPathStr = extractStringValue(gitPathArg);
560
+ let gitJoinedPath = '';
561
+ if (gitPathArg?.type === 'CallExpression' && gitPathArg.arguments) {
562
+ gitJoinedPath = gitPathArg.arguments.map(a => extractStringValue(a) || '').join('/');
563
+ }
564
+ const gitCheckPath = gitPathStr || gitJoinedPath;
565
+ if (/\.git[\\/]hooks[\\/]/i.test(gitCheckPath) ||
566
+ GIT_HOOKS.some(h => gitCheckPath.includes(h) && gitCheckPath.includes('.git'))) {
567
+ ctx.threats.push({
568
+ type: 'git_hooks_injection',
569
+ severity: 'HIGH',
570
+ message: `Git hook injection: ${gitWriteMethod}() writes to .git/hooks/. Persistence technique.`,
571
+ file: ctx.relFile
572
+ });
573
+ }
574
+ }
575
+ }
576
+
577
+ // SANDWORM_MODE R6: Git hooks injection via exec — git config --global init.templateDir or git config hooks
578
+ if ((execName || memberExec) && node.arguments.length > 0) {
579
+ const gitExecArg = node.arguments[0];
580
+ const gitExecStr = extractStringValue(gitExecArg);
581
+ if (gitExecStr) {
582
+ if (/git\s+config\s+.*init\.templateDir/i.test(gitExecStr)) {
583
+ ctx.threats.push({
584
+ type: 'git_hooks_injection',
585
+ severity: 'HIGH',
586
+ message: 'Git hook injection: modifying global git template directory via "git config init.templateDir". Persistence technique.',
587
+ file: ctx.relFile
588
+ });
589
+ } else if (/git\s+config\b/.test(gitExecStr) && /hooks/i.test(gitExecStr)) {
590
+ ctx.threats.push({
591
+ type: 'git_hooks_injection',
592
+ severity: 'HIGH',
593
+ message: 'Git hook injection: modifying git hooks configuration. Persistence technique.',
594
+ file: ctx.relFile
595
+ });
596
+ }
597
+ }
598
+ }
599
+
490
600
  // Detect fs.chmodSync with executable permissions (deferred to postWalk for compound check)
491
601
  if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
492
602
  const chmodMethod = node.callee.property.name;
@@ -734,6 +844,32 @@ function handleCallExpression(node, ctx) {
734
844
  ctx.hasTempFileExec = true;
735
845
  }
736
846
  }
847
+
848
+ // SANDWORM_MODE R7: Detect Object.entries/keys/values(process.env)
849
+ if (node.callee.type === 'MemberExpression' &&
850
+ node.callee.object?.type === 'Identifier' && node.callee.object.name === 'Object' &&
851
+ node.callee.property?.type === 'Identifier' &&
852
+ ['entries', 'keys', 'values'].includes(node.callee.property.name) &&
853
+ node.arguments.length >= 1) {
854
+ const enumArg = node.arguments[0];
855
+ // Check if argument is process.env
856
+ if (enumArg.type === 'MemberExpression' &&
857
+ enumArg.object?.type === 'Identifier' && enumArg.object.name === 'process' &&
858
+ enumArg.property?.type === 'Identifier' && enumArg.property.name === 'env') {
859
+ ctx.hasEnvEnumeration = true;
860
+ }
861
+ }
862
+
863
+ // SANDWORM_MODE R8: Detect dns.resolve/resolve4/resolveTxt calls (flag for co-occurrence)
864
+ if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
865
+ const dnsPropName = node.callee.property.name;
866
+ if (['resolve', 'resolve4', 'resolveTxt', 'resolveCname'].includes(dnsPropName)) {
867
+ // Set hasDnsLoop if file has dns require + base64 encoding (co-occurrence checked in postWalk)
868
+ if (ctx.hasDnsRequire && ctx.hasBase64Encode) {
869
+ ctx.hasDnsLoop = true;
870
+ }
871
+ }
872
+ }
737
873
  }
738
874
 
739
875
  function handleImportExpression(node, ctx) {
@@ -939,6 +1075,10 @@ function handleMemberExpression(node, ctx) {
939
1075
  file: ctx.relFile
940
1076
  });
941
1077
  }
1078
+ // SANDWORM_MODE R9: Count LLM API key accesses
1079
+ if (LLM_API_KEY_VARS.includes(envVar)) {
1080
+ ctx.llmApiKeyCount++;
1081
+ }
942
1082
  }
943
1083
  }
944
1084
  }
@@ -964,6 +1104,36 @@ function handlePostWalk(ctx) {
964
1104
  });
965
1105
  }
966
1106
 
1107
+ // SANDWORM_MODE R7: env harvesting = Object.entries/keys/values(process.env) + sensitive pattern in file
1108
+ if (ctx.hasEnvEnumeration && ctx.hasEnvHarvestPattern) {
1109
+ ctx.threats.push({
1110
+ type: 'env_harvesting_dynamic',
1111
+ severity: 'HIGH',
1112
+ message: 'Dynamic environment variable harvesting with sensitive pattern matching. Credential theft technique.',
1113
+ file: ctx.relFile
1114
+ });
1115
+ }
1116
+
1117
+ // SANDWORM_MODE R8: DNS exfiltration = dns require + base64 encode + dns call (loop implied by co-occurrence)
1118
+ if (ctx.hasDnsLoop) {
1119
+ ctx.threats.push({
1120
+ type: 'dns_chunk_exfiltration',
1121
+ severity: 'HIGH',
1122
+ message: 'DNS exfiltration: data encoded in DNS queries. Covert channel for firewall bypass.',
1123
+ file: ctx.relFile
1124
+ });
1125
+ }
1126
+
1127
+ // SANDWORM_MODE R9: LLM API key harvesting (3+ different providers = harvesting)
1128
+ if (ctx.llmApiKeyCount >= 3) {
1129
+ ctx.threats.push({
1130
+ type: 'llm_api_key_harvesting',
1131
+ severity: 'MEDIUM',
1132
+ message: `LLM API key harvesting: accessing ${ctx.llmApiKeyCount} AI provider keys. Monetization vector.`,
1133
+ file: ctx.relFile
1134
+ });
1135
+ }
1136
+
967
1137
  // JS reverse shell pattern
968
1138
  if (ctx.hasJsReverseShell) {
969
1139
  ctx.threats.push({
@@ -84,7 +84,16 @@ function analyzeFile(content, filePath, basePath) {
84
84
  hasTempFileWrite: false,
85
85
  hasTempFileExec: false,
86
86
  hasFileDelete: false,
87
- hasTmpdirInContent: /\btmpdir\b|\/dev\/shm\b|\/tmp\b/i.test(content)
87
+ hasTmpdirInContent: /\btmpdir\b|\/dev\/shm\b|\/tmp\b/i.test(content),
88
+ // SANDWORM_MODE P2: env harvesting co-occurrence
89
+ hasEnvEnumeration: false, // Object.entries/keys/values(process.env)
90
+ hasEnvHarvestPattern: /\b(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|NPM|AWS|SSH|WEBHOOK)\b/.test(content),
91
+ // SANDWORM_MODE P2: DNS exfiltration co-occurrence
92
+ hasDnsRequire: /\brequire\s*\(\s*['"]dns['"]\s*\)/.test(content) || /\bdns\s*\.\s*resolve/.test(content),
93
+ hasBase64Encode: /\.toString\s*\(\s*['"]base64(url)?['"]\s*\)/.test(content),
94
+ hasDnsLoop: false, // set when dns call inside loop context detected
95
+ // SANDWORM_MODE P2: LLM API key harvesting
96
+ llmApiKeyCount: 0
88
97
  };
89
98
 
90
99
  walk.simple(ast, {