muaddib-scanner 2.11.13 → 2.11.14
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/monitor/classify.js +3 -1
- package/src/response/playbooks.js +17 -0
- package/src/rules/confidence-tiers.js +3 -1
- package/src/rules/index.js +36 -0
- package/src/scanner/ai-config.js +111 -1
- package/src/scanner/ast.js +17 -0
- package/src/scanner/github-actions.js +12 -0
package/package.json
CHANGED
package/src/monitor/classify.js
CHANGED
|
@@ -71,7 +71,9 @@ const HIGH_CONFIDENCE_MALICE_TYPES = new Set([
|
|
|
71
71
|
// String.fromCharCode() to evade static analysis. Structurally unique to malware —
|
|
72
72
|
// no legitimate code reconstructs env var names from character codes. Bypasses MT-1
|
|
73
73
|
// cap since the attack uses optionalDependencies + prepare hook (no direct lifecycle).
|
|
74
|
-
'env_charcode_reconstruction'
|
|
74
|
+
'env_charcode_reconstruction', // fromCharCode + process.env[computed] (TeamPCP credential stealer)
|
|
75
|
+
'ide_hook_autoexec', // .claude/settings.json SessionStart hook, .vscode/tasks.json folderOpen (Shai-Hulud)
|
|
76
|
+
'workflow_secrets_dump' // toJSON(secrets) in GitHub Actions workflow (Shai-Hulud)
|
|
75
77
|
]);
|
|
76
78
|
|
|
77
79
|
// Lifecycle compound types that indicate real malicious intent beyond a simple postinstall
|
|
@@ -224,6 +224,11 @@ const PLAYBOOKS = {
|
|
|
224
224
|
workflow_pwn_request:
|
|
225
225
|
'CRITIQUE: Pwn request detecte — pull_request_target avec checkout du head de la PR permet l\'execution de code arbitraire. Remplacer par pull_request ou utiliser une strategie de checkout securisee (base ref uniquement).',
|
|
226
226
|
|
|
227
|
+
workflow_secrets_dump:
|
|
228
|
+
'CRITIQUE: Workflow GitHub Actions utilise toJSON(secrets) pour dumper tous les secrets du repository. ' +
|
|
229
|
+
'Technique Shai-Hulud (TeamPCP). Supprimer le workflow immediatement. ' +
|
|
230
|
+
'Si le workflow a ete execute, considerer tous les secrets du repository compromis et les regenerer.',
|
|
231
|
+
|
|
227
232
|
sandbox_sensitive_file_read:
|
|
228
233
|
'CRITIQUE: Package lit des fichiers sensibles (credentials) lors de l\'installation. Ne pas installer. Supprimer immediatement.',
|
|
229
234
|
sandbox_sensitive_file_write:
|
|
@@ -374,6 +379,13 @@ const PLAYBOOKS = {
|
|
|
374
379
|
'NE PAS ouvrir ce projet avec un agent IA. Supprimer les fichiers de config compromis. ' +
|
|
375
380
|
'Si deja ouvert avec un agent IA, considerer la machine compromise. Regenerer tous les secrets.',
|
|
376
381
|
|
|
382
|
+
ide_hook_autoexec:
|
|
383
|
+
'CRITIQUE: Fichier de configuration IDE avec hooks d\'auto-execution detecte. ' +
|
|
384
|
+
'NE PAS ouvrir ce projet dans un IDE ou agent IA. Le fichier execute du code ' +
|
|
385
|
+
'automatiquement a l\'ouverture du projet (SessionStart, folderOpen). ' +
|
|
386
|
+
'Technique Shai-Hulud (TeamPCP). Supprimer les fichiers .claude/settings.json ' +
|
|
387
|
+
'et .vscode/tasks.json avant ouverture.',
|
|
388
|
+
|
|
377
389
|
ai_agent_abuse:
|
|
378
390
|
'CRITIQUE: Un agent IA (Claude, Gemini, Q) est invoque avec des flags de bypass de securite ' +
|
|
379
391
|
'(--dangerously-skip-permissions, --yolo, --trust-all-tools). Technique s1ngularity/Nx. ' +
|
|
@@ -728,6 +740,11 @@ const PLAYBOOKS = {
|
|
|
728
740
|
'Pattern kamikaze.sh du wiper CanisterWorm ciblant les systemes Iran (Asia/Tehran). ' +
|
|
729
741
|
'NE PAS executer. Isoler immediatement. Signaler comme destructware.',
|
|
730
742
|
|
|
743
|
+
geo_evasion_killswitch:
|
|
744
|
+
'Code verifie la locale systeme pour "ru" et fait process.exit — technique CIS kill switch ' +
|
|
745
|
+
'classique de malware (TeamPCP, Lazarus) pour eviter de cibler les systemes russophones. ' +
|
|
746
|
+
'Signal fort en combinaison avec d\'autres indicateurs. Analyser le code complet du package.',
|
|
747
|
+
|
|
731
748
|
proc_mem_scan:
|
|
732
749
|
'CRITIQUE: Acces a /proc/*/mem — extraction de secrets depuis la memoire des processus. ' +
|
|
733
750
|
'Technique TeamPCP credential stealer dans Trivy v0.69.4. ' +
|
|
@@ -100,7 +100,9 @@ const HIGH_TIER_EXTRA = new Set([
|
|
|
100
100
|
'anti_forensic_xor_autodelete',
|
|
101
101
|
'detached_credential_exfil',
|
|
102
102
|
// Workflow injection
|
|
103
|
-
'workflow_write'
|
|
103
|
+
'workflow_write',
|
|
104
|
+
// Geo-evasion (locale check + country code + exit = compound evidence)
|
|
105
|
+
'geo_evasion_killswitch'
|
|
104
106
|
]);
|
|
105
107
|
|
|
106
108
|
// Heuristic / count-based / weak-signal types. Always low tier regardless
|
package/src/rules/index.js
CHANGED
|
@@ -705,6 +705,18 @@ const RULES = {
|
|
|
705
705
|
],
|
|
706
706
|
mitre: 'T1059'
|
|
707
707
|
},
|
|
708
|
+
ide_hook_autoexec: {
|
|
709
|
+
id: 'MUADDIB-AICONF-003',
|
|
710
|
+
name: 'IDE Hook Auto-Execution',
|
|
711
|
+
severity: 'CRITICAL',
|
|
712
|
+
confidence: 'high',
|
|
713
|
+
description: 'Fichier de configuration IDE (.claude/settings.json, .vscode/tasks.json, .kiro/settings/mcp.json) contient des hooks qui executent du code automatiquement a l\'ouverture du projet. Technique Shai-Hulud (TeamPCP, mai 2026).',
|
|
714
|
+
references: [
|
|
715
|
+
'https://github.com/g00dfe11ow/Shai-Hulud-Open-Source',
|
|
716
|
+
'https://www.wiz.io/blog/mini-shai-hulud-supply-chain-sap-npm'
|
|
717
|
+
],
|
|
718
|
+
mitre: 'T1546'
|
|
719
|
+
},
|
|
708
720
|
|
|
709
721
|
require_cache_poison: {
|
|
710
722
|
id: 'MUADDIB-AST-019',
|
|
@@ -975,6 +987,18 @@ const RULES = {
|
|
|
975
987
|
],
|
|
976
988
|
mitre: 'T1195.002'
|
|
977
989
|
},
|
|
990
|
+
workflow_secrets_dump: {
|
|
991
|
+
id: 'MUADDIB-GHA-004',
|
|
992
|
+
name: 'GitHub Actions Secrets Dump',
|
|
993
|
+
severity: 'CRITICAL',
|
|
994
|
+
confidence: 'high',
|
|
995
|
+
description: 'Workflow utilise toJSON(secrets) pour exfiltrer tous les secrets du repository. Technique Shai-Hulud (TeamPCP, mai 2026).',
|
|
996
|
+
references: [
|
|
997
|
+
'https://github.com/g00dfe11ow/Shai-Hulud-Open-Source',
|
|
998
|
+
'https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions'
|
|
999
|
+
],
|
|
1000
|
+
mitre: 'T1552.001'
|
|
1001
|
+
},
|
|
978
1002
|
|
|
979
1003
|
// Sandbox detections
|
|
980
1004
|
sandbox_sensitive_file_read: {
|
|
@@ -2458,6 +2482,18 @@ const RULES = {
|
|
|
2458
2482
|
],
|
|
2459
2483
|
mitre: 'T1059.007'
|
|
2460
2484
|
},
|
|
2485
|
+
geo_evasion_killswitch: {
|
|
2486
|
+
id: 'MUADDIB-AST-091',
|
|
2487
|
+
name: 'Geo-Evasion CIS Kill Switch',
|
|
2488
|
+
severity: 'HIGH',
|
|
2489
|
+
confidence: 'medium',
|
|
2490
|
+
description: 'Code verifie la locale systeme (Intl.DateTimeFormat, LC_ALL/LANG) pour "ru" et fait process.exit — technique CIS kill switch pour eviter de cibler les pays de l\'operateur. Pattern TeamPCP/Shai-Hulud.',
|
|
2491
|
+
references: [
|
|
2492
|
+
'https://github.com/g00dfe11ow/Shai-Hulud-Open-Source',
|
|
2493
|
+
'https://attack.mitre.org/techniques/T1614/'
|
|
2494
|
+
],
|
|
2495
|
+
mitre: 'T1614'
|
|
2496
|
+
},
|
|
2461
2497
|
external_tarball_dep: {
|
|
2462
2498
|
id: 'MUADDIB-PKG-020',
|
|
2463
2499
|
name: 'External Tarball Dependency URL',
|
package/src/scanner/ai-config.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
|
|
22
|
-
// AI agent config files to scan (relative to project root)
|
|
22
|
+
// AI agent config files to scan for prompt injection (relative to project root)
|
|
23
23
|
const AI_CONFIG_FILES = [
|
|
24
24
|
'.cursorrules',
|
|
25
25
|
'.cursorignore',
|
|
@@ -31,6 +31,17 @@ const AI_CONFIG_FILES = [
|
|
|
31
31
|
'.github/copilot-setup-steps.yml'
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
+
// IDE/agent config files to scan for auto-exec hooks (JSON, relative to project root)
|
|
35
|
+
// These are distinct from AI_CONFIG_FILES: they contain machine-readable hooks
|
|
36
|
+
// that execute code on project open, not human-readable prompt injection.
|
|
37
|
+
// Technique: Shai-Hulud (TeamPCP, May 2026) — .claude/settings.json SessionStart hook.
|
|
38
|
+
const IDE_HOOK_FILES = [
|
|
39
|
+
'.claude/settings.json',
|
|
40
|
+
'.claude/settings.local.json',
|
|
41
|
+
'.vscode/tasks.json',
|
|
42
|
+
'.kiro/settings/mcp.json'
|
|
43
|
+
];
|
|
44
|
+
|
|
34
45
|
// Dangerous shell command patterns in AI config files
|
|
35
46
|
const SHELL_COMMAND_PATTERNS = [
|
|
36
47
|
// Download and execute
|
|
@@ -104,6 +115,105 @@ function scanAIConfig(targetPath) {
|
|
|
104
115
|
threats.push(...fileThreats);
|
|
105
116
|
}
|
|
106
117
|
|
|
118
|
+
// Scan IDE hook files for auto-exec patterns (separate from prompt injection)
|
|
119
|
+
for (const hookFile of IDE_HOOK_FILES) {
|
|
120
|
+
const filePath = path.join(targetPath, hookFile);
|
|
121
|
+
if (!fs.existsSync(filePath)) continue;
|
|
122
|
+
|
|
123
|
+
let content;
|
|
124
|
+
try {
|
|
125
|
+
const stat = fs.statSync(filePath);
|
|
126
|
+
if (stat.size > 1024 * 1024) continue;
|
|
127
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
128
|
+
} catch {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const hookThreats = analyzeIDEHookFile(content, hookFile);
|
|
133
|
+
threats.push(...hookThreats);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return threats;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Analyze an IDE/agent config JSON file for auto-exec hooks.
|
|
141
|
+
*
|
|
142
|
+
* Distinct from prompt injection: these files contain machine-readable
|
|
143
|
+
* hooks that execute arbitrary commands when the project is opened.
|
|
144
|
+
* No legitimate npm package should ship these files with hooks.
|
|
145
|
+
*/
|
|
146
|
+
function analyzeIDEHookFile(content, relPath) {
|
|
147
|
+
const threats = [];
|
|
148
|
+
|
|
149
|
+
let parsed;
|
|
150
|
+
try {
|
|
151
|
+
parsed = JSON.parse(content);
|
|
152
|
+
} catch {
|
|
153
|
+
return threats; // invalid JSON — skip silently
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!parsed || typeof parsed !== 'object') return threats;
|
|
157
|
+
|
|
158
|
+
// .claude/settings.json / .claude/settings.local.json
|
|
159
|
+
// Structure: { hooks: { EventName: [{ matcher, hooks: [{ type, command }] }] } }
|
|
160
|
+
if (relPath.includes('.claude/') && relPath.endsWith('settings.json')) {
|
|
161
|
+
const hooks = parsed.hooks;
|
|
162
|
+
if (hooks && typeof hooks === 'object') {
|
|
163
|
+
for (const [event, matchers] of Object.entries(hooks)) {
|
|
164
|
+
if (!Array.isArray(matchers)) continue;
|
|
165
|
+
for (const matcher of matchers) {
|
|
166
|
+
if (!matcher || !Array.isArray(matcher.hooks)) continue;
|
|
167
|
+
for (const hook of matcher.hooks) {
|
|
168
|
+
if (hook && hook.command) {
|
|
169
|
+
threats.push({
|
|
170
|
+
type: 'ide_hook_autoexec',
|
|
171
|
+
severity: 'CRITICAL',
|
|
172
|
+
message: `IDE auto-exec hook: .claude/settings.json ${event} event executes "${hook.command}" — Shai-Hulud (TeamPCP) pattern`,
|
|
173
|
+
file: relPath
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// .vscode/tasks.json
|
|
183
|
+
// Structure: { tasks: [{ label, command, runOptions: { runOn: "folderOpen" } }] }
|
|
184
|
+
if (relPath.includes('.vscode/') && relPath.endsWith('tasks.json')) {
|
|
185
|
+
const tasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
|
|
186
|
+
for (const task of tasks) {
|
|
187
|
+
if (task && task.runOptions && task.runOptions.runOn === 'folderOpen') {
|
|
188
|
+
const cmd = task.command || task.label || 'unknown';
|
|
189
|
+
threats.push({
|
|
190
|
+
type: 'ide_hook_autoexec',
|
|
191
|
+
severity: 'CRITICAL',
|
|
192
|
+
message: `IDE auto-exec hook: .vscode/tasks.json task "${cmd}" runs on folder open — Shai-Hulud (TeamPCP) pattern`,
|
|
193
|
+
file: relPath
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// .kiro/settings/mcp.json
|
|
200
|
+
// Structure: { mcpServers: { name: { command, args } } }
|
|
201
|
+
if (relPath.includes('.kiro/') && relPath.endsWith('mcp.json')) {
|
|
202
|
+
const mcpServers = parsed.mcpServers;
|
|
203
|
+
if (mcpServers && typeof mcpServers === 'object') {
|
|
204
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
205
|
+
if (config && typeof config === 'object' && config.command) {
|
|
206
|
+
threats.push({
|
|
207
|
+
type: 'ide_hook_autoexec',
|
|
208
|
+
severity: 'CRITICAL',
|
|
209
|
+
message: `IDE auto-exec hook: .kiro/settings/mcp.json server "${name}" executes "${config.command}" on project open`,
|
|
210
|
+
file: relPath
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
107
217
|
return threats;
|
|
108
218
|
}
|
|
109
219
|
|
package/src/scanner/ast.js
CHANGED
|
@@ -345,6 +345,23 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
345
345
|
});
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
// Geo-evasion CIS kill switch: locale check for "ru" + process.exit
|
|
349
|
+
// Pattern: TeamPCP/Shai-Hulud isSystemRussian() — checks Intl.DateTimeFormat
|
|
350
|
+
// locale or LC_ALL/LANG env vars for Russian locale, then process.exit(0).
|
|
351
|
+
// Triple-gate: (1) locale API or env var check, (2) "ru" string comparison,
|
|
352
|
+
// (3) process.exit in same file. No legitimate npm package does this.
|
|
353
|
+
const hasLocaleCheck = /resolvedOptions\s*\(\s*\)\s*\.locale/.test(content) ||
|
|
354
|
+
(/\bLC_ALL\b/.test(content) && /\bLANG\b/.test(content));
|
|
355
|
+
const hasRuCheck = /['"`]ru['"`]/.test(content) && /startsWith|===|==/.test(content);
|
|
356
|
+
if (hasLocaleCheck && hasRuCheck && /process\.exit/.test(content)) {
|
|
357
|
+
threats.push({
|
|
358
|
+
type: 'geo_evasion_killswitch',
|
|
359
|
+
severity: 'HIGH',
|
|
360
|
+
message: 'Geo-evasion CIS kill switch: locale check for "ru" + process.exit — malware avoids targeting operator\'s country (TeamPCP pattern)',
|
|
361
|
+
file: ctx.relPath
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
348
365
|
handlePostWalk(ctx);
|
|
349
366
|
|
|
350
367
|
return threats;
|
|
@@ -102,6 +102,18 @@ function scanDirRecursive(dirPath, targetPath, threats, depth = 0) {
|
|
|
102
102
|
file: relFile
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
// GHA-004: Secrets dump via toJSON(secrets) — exfiltrates ALL repository secrets
|
|
107
|
+
// Technique: Shai-Hulud (TeamPCP, May 2026) — workflow dumps toJSON(secrets) to a
|
|
108
|
+
// file and uploads it as an artifact. No legitimate workflow uses toJSON(secrets).
|
|
109
|
+
if (/toJSON\s*\(\s*secrets\s*\)/.test(activeContent)) {
|
|
110
|
+
threats.push({
|
|
111
|
+
type: 'workflow_secrets_dump',
|
|
112
|
+
severity: 'CRITICAL',
|
|
113
|
+
message: 'GitHub Actions secrets dump: toJSON(secrets) exfiltrates all repository secrets',
|
|
114
|
+
file: relFile
|
|
115
|
+
});
|
|
116
|
+
}
|
|
105
117
|
}
|
|
106
118
|
}
|
|
107
119
|
|