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 +1 -1
- package/src/response/playbooks.js +25 -0
- package/src/rules/index.js +60 -0
- package/src/scanner/ast-detectors.js +170 -0
- package/src/scanner/ast.js +10 -1
package/package.json
CHANGED
|
@@ -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) {
|
package/src/rules/index.js
CHANGED
|
@@ -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({
|
package/src/scanner/ast.js
CHANGED
|
@@ -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, {
|