muaddib-scanner 2.1.5 → 2.2.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.
Files changed (100) hide show
  1. package/README.fr.md +33 -8
  2. package/README.md +33 -8
  3. package/assets/logo2removebg.png +0 -0
  4. package/bin/muaddib.js +9 -0
  5. package/datasets/adversarial/README.md +23 -0
  6. package/datasets/adversarial/ai-agent-weaponization/index.js +5 -0
  7. package/datasets/adversarial/ai-agent-weaponization/package.json +9 -0
  8. package/datasets/adversarial/ai-agent-weaponization/setup.js +83 -0
  9. package/datasets/adversarial/ai-config-injection/.cursorrules +36 -0
  10. package/datasets/adversarial/ai-config-injection/index.js +16 -0
  11. package/datasets/adversarial/ai-config-injection/package.json +8 -0
  12. package/datasets/adversarial/browser-api-hook/index.js +66 -0
  13. package/datasets/adversarial/browser-api-hook/package.json +6 -0
  14. package/datasets/adversarial/bun-runtime-evasion/bun_environment.js +23 -0
  15. package/datasets/adversarial/bun-runtime-evasion/package.json +9 -0
  16. package/datasets/adversarial/bun-runtime-evasion/setup.js +10 -0
  17. package/datasets/adversarial/ci-trigger-exfil/index.js +17 -0
  18. package/datasets/adversarial/ci-trigger-exfil/package.json +9 -0
  19. package/datasets/adversarial/conditional-chain/index.js +14 -0
  20. package/datasets/adversarial/conditional-chain/package.json +9 -0
  21. package/datasets/adversarial/crypto-wallet-harvest/index.js +44 -0
  22. package/datasets/adversarial/crypto-wallet-harvest/package.json +6 -0
  23. package/datasets/adversarial/dead-mans-switch/index.js +35 -0
  24. package/datasets/adversarial/dead-mans-switch/package.json +9 -0
  25. package/datasets/adversarial/delayed-exfil/index.js +6 -0
  26. package/datasets/adversarial/delayed-exfil/package.json +6 -0
  27. package/datasets/adversarial/detached-background/launcher.js +11 -0
  28. package/datasets/adversarial/detached-background/package.json +9 -0
  29. package/datasets/adversarial/detached-background/worker.js +26 -0
  30. package/datasets/adversarial/discord-webhook-exfil/index.js +95 -0
  31. package/datasets/adversarial/discord-webhook-exfil/package.json +9 -0
  32. package/datasets/adversarial/dns-chunk-exfil/index.js +10 -0
  33. package/datasets/adversarial/dns-chunk-exfil/package.json +6 -0
  34. package/datasets/adversarial/docker-aware/index.js +10 -0
  35. package/datasets/adversarial/docker-aware/package.json +6 -0
  36. package/datasets/adversarial/double-base64-exfil/index.js +11 -0
  37. package/datasets/adversarial/double-base64-exfil/package.json +9 -0
  38. package/datasets/adversarial/dynamic-import/index.js +21 -0
  39. package/datasets/adversarial/dynamic-import/package.json +9 -0
  40. package/datasets/adversarial/dynamic-require/index.js +3 -0
  41. package/datasets/adversarial/dynamic-require/package.json +9 -0
  42. package/datasets/adversarial/fake-captcha-fingerprint/index.js +64 -0
  43. package/datasets/adversarial/fake-captcha-fingerprint/package.json +9 -0
  44. package/datasets/adversarial/gh-cli-token-steal/index.js +31 -0
  45. package/datasets/adversarial/gh-cli-token-steal/package.json +6 -0
  46. package/datasets/adversarial/github-exfil/index.js +33 -0
  47. package/datasets/adversarial/github-exfil/package.json +9 -0
  48. package/datasets/adversarial/iife-exfil/index.js +17 -0
  49. package/datasets/adversarial/iife-exfil/package.json +9 -0
  50. package/datasets/adversarial/nested-payload/index.js +3 -0
  51. package/datasets/adversarial/nested-payload/package.json +9 -0
  52. package/datasets/adversarial/nested-payload/utils/helper.js +6 -0
  53. package/datasets/adversarial/nested-payload/utils/lib/format.js +23 -0
  54. package/datasets/adversarial/postinstall-download/package.json +8 -0
  55. package/datasets/adversarial/preinstall-background-fork/bootstrap.js +16 -0
  56. package/datasets/adversarial/preinstall-background-fork/index.js +2 -0
  57. package/datasets/adversarial/preinstall-background-fork/package.json +9 -0
  58. package/datasets/adversarial/preinstall-background-fork/stealer.js +67 -0
  59. package/datasets/adversarial/preinstall-exec/package.json +9 -0
  60. package/datasets/adversarial/preinstall-exec/steal.js +24 -0
  61. package/datasets/adversarial/proxy-env-intercept/index.js +33 -0
  62. package/datasets/adversarial/proxy-env-intercept/package.json +9 -0
  63. package/datasets/adversarial/pyinstaller-dropper/index.js +25 -0
  64. package/datasets/adversarial/pyinstaller-dropper/package.json +9 -0
  65. package/datasets/adversarial/rdd-zero-deps/index.js +32 -0
  66. package/datasets/adversarial/rdd-zero-deps/init.js +15 -0
  67. package/datasets/adversarial/rdd-zero-deps/package.json +11 -0
  68. package/datasets/adversarial/remote-dynamic-dependency/index.js +15 -0
  69. package/datasets/adversarial/remote-dynamic-dependency/package.json +7 -0
  70. package/datasets/adversarial/self-hosted-runner-backdoor/index.js +28 -0
  71. package/datasets/adversarial/self-hosted-runner-backdoor/package.json +9 -0
  72. package/datasets/adversarial/silent-error-swallow/index.js +32 -0
  73. package/datasets/adversarial/silent-error-swallow/package.json +6 -0
  74. package/datasets/adversarial/staged-fetch/index.js +9 -0
  75. package/datasets/adversarial/staged-fetch/package.json +6 -0
  76. package/datasets/adversarial/string-concat-obfuscation/index.js +6 -0
  77. package/datasets/adversarial/string-concat-obfuscation/package.json +6 -0
  78. package/datasets/adversarial/template-literal-obfuscation/index.js +4 -0
  79. package/datasets/adversarial/template-literal-obfuscation/package.json +9 -0
  80. package/datasets/adversarial/triple-base64-github-push/index.js +38 -0
  81. package/datasets/adversarial/triple-base64-github-push/package.json +9 -0
  82. package/datasets/adversarial/websocket-exfil/index.js +34 -0
  83. package/datasets/adversarial/websocket-exfil/package.json +9 -0
  84. package/datasets/benign/README.md +20 -0
  85. package/datasets/benign/packages-npm.txt +98 -0
  86. package/datasets/benign/packages-pypi.txt +49 -0
  87. package/metrics/v2.1.5.json +753 -0
  88. package/metrics/v2.2.0.json +753 -0
  89. package/nul +0 -5
  90. package/package.json +1 -1
  91. package/src/commands/evaluate.js +270 -0
  92. package/src/index.js +12 -5
  93. package/src/ioc/bootstrap.js +1 -0
  94. package/src/response/playbooks.js +66 -0
  95. package/src/rules/index.js +181 -0
  96. package/src/scanner/ai-config.js +183 -0
  97. package/src/scanner/ast.js +496 -2
  98. package/src/scanner/dataflow.js +147 -16
  99. package/src/scanner/package.js +3 -1
  100. package/src/utils.js +10 -3
package/nul CHANGED
@@ -1,5 +0,0 @@
1
- {
2
- "generated_at": "2026-02-14T15:37:31.286Z",
3
- "version": "2.0.0",
4
- "feed": []
5
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.1.5",
3
+ "version": "2.2.0",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,270 @@
1
+ /**
2
+ * MUAD'DIB Evaluate — Scanner effectiveness measurement
3
+ *
4
+ * Measures TPR (Ground Truth), FPR (Benign), and ADR (Adversarial).
5
+ * Saves versioned metrics to metrics/v{version}.json.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const { run } = require('../index.js');
12
+
13
+ const ROOT = path.join(__dirname, '..', '..');
14
+ const GT_DIR = path.join(ROOT, 'tests', 'ground-truth');
15
+ const BENIGN_DIR = path.join(ROOT, 'datasets', 'benign');
16
+ const ADVERSARIAL_DIR = path.join(ROOT, 'datasets', 'adversarial');
17
+ const METRICS_DIR = path.join(ROOT, 'metrics');
18
+
19
+ const GT_THRESHOLD = 3;
20
+ const BENIGN_THRESHOLD = 20;
21
+
22
+ const ADVERSARIAL_THRESHOLDS = {
23
+ // Vague 1 (20 samples)
24
+ 'ci-trigger-exfil': 35,
25
+ 'delayed-exfil': 30,
26
+ 'docker-aware': 35,
27
+ 'staged-fetch': 35,
28
+ 'dns-chunk-exfil': 35,
29
+ 'string-concat-obfuscation': 30,
30
+ 'postinstall-download': 30,
31
+ 'dynamic-require': 40,
32
+ 'iife-exfil': 40,
33
+ 'conditional-chain': 30,
34
+ 'template-literal-obfuscation': 30,
35
+ 'proxy-env-intercept': 40,
36
+ 'nested-payload': 30,
37
+ 'dynamic-import': 30,
38
+ 'websocket-exfil': 30,
39
+ 'bun-runtime-evasion': 30,
40
+ 'preinstall-exec': 35,
41
+ 'remote-dynamic-dependency': 35,
42
+ 'github-exfil': 30,
43
+ 'detached-background': 35,
44
+ // Vague 3 (5 samples)
45
+ 'ai-agent-weaponization': 35,
46
+ 'ai-config-injection': 30,
47
+ 'rdd-zero-deps': 35,
48
+ 'discord-webhook-exfil': 30,
49
+ 'preinstall-background-fork': 35,
50
+ // Holdout → promoted (10 samples)
51
+ 'silent-error-swallow': 25,
52
+ 'double-base64-exfil': 30,
53
+ 'crypto-wallet-harvest': 25,
54
+ 'self-hosted-runner-backdoor': 20,
55
+ 'dead-mans-switch': 30,
56
+ 'fake-captcha-fingerprint': 20,
57
+ 'pyinstaller-dropper': 35,
58
+ 'gh-cli-token-steal': 30,
59
+ 'triple-base64-github-push': 30,
60
+ 'browser-api-hook': 20
61
+ };
62
+
63
+ /**
64
+ * Scan a directory silently and return the result
65
+ */
66
+ async function silentScan(dir) {
67
+ try {
68
+ return await run(dir, { _capture: true });
69
+ } catch (err) {
70
+ return { summary: { riskScore: 0, total: 0 }, threats: [], error: err.message };
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 1. Ground Truth — scan real-world attack samples
76
+ */
77
+ async function evaluateGroundTruth() {
78
+ const attacksFile = path.join(GT_DIR, 'attacks.json');
79
+ const data = JSON.parse(fs.readFileSync(attacksFile, 'utf8'));
80
+ const attacks = data.attacks.filter(a => a.expected.min_threats > 0);
81
+
82
+ const details = [];
83
+ let detected = 0;
84
+
85
+ for (const attack of attacks) {
86
+ const sampleDir = path.join(GT_DIR, attack.sample_dir);
87
+ const result = await silentScan(sampleDir);
88
+ const score = result.summary.riskScore;
89
+ const isDetected = score >= GT_THRESHOLD;
90
+ if (isDetected) detected++;
91
+ details.push({
92
+ name: attack.name,
93
+ id: attack.id,
94
+ score,
95
+ detected: isDetected,
96
+ threshold: GT_THRESHOLD
97
+ });
98
+ }
99
+
100
+ const total = attacks.length;
101
+ const tpr = total > 0 ? detected / total : 0;
102
+ return { detected, total, tpr, details };
103
+ }
104
+
105
+ /**
106
+ * 2. Benign — scan popular npm packages for false positives
107
+ */
108
+ async function evaluateBenign() {
109
+ const listFile = path.join(BENIGN_DIR, 'packages-npm.txt');
110
+ const packages = fs.readFileSync(listFile, 'utf8')
111
+ .split('\n')
112
+ .map(l => l.trim())
113
+ .filter(l => l && !l.startsWith('#'));
114
+
115
+ const details = [];
116
+ let flagged = 0;
117
+
118
+ for (const pkg of packages) {
119
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'muaddib-eval-'));
120
+ try {
121
+ // Create minimal project with this package as dependency
122
+ const pkgJson = { name: 'eval-project', version: '1.0.0', dependencies: { [pkg]: '*' } };
123
+ fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify(pkgJson));
124
+
125
+ // Create fake node_modules entry so dependency scanner picks it up
126
+ const parts = pkg.split('/');
127
+ const nmDir = path.join(tmpDir, 'node_modules', ...parts);
128
+ fs.mkdirSync(nmDir, { recursive: true });
129
+ fs.writeFileSync(path.join(nmDir, 'package.json'), JSON.stringify({ name: pkg, version: '999.0.0' }));
130
+
131
+ const result = await silentScan(tmpDir);
132
+ const score = result.summary.riskScore;
133
+ const isFlagged = score > BENIGN_THRESHOLD;
134
+ if (isFlagged) flagged++;
135
+ details.push({ name: pkg, score, flagged: isFlagged });
136
+ } finally {
137
+ fs.rmSync(tmpDir, { recursive: true, force: true });
138
+ }
139
+ }
140
+
141
+ const total = packages.length;
142
+ const fpr = total > 0 ? flagged / total : 0;
143
+ return { flagged, total, fpr, details };
144
+ }
145
+
146
+ /**
147
+ * 3. Adversarial — scan evasive malicious samples
148
+ */
149
+ async function evaluateAdversarial() {
150
+ const details = [];
151
+ let detected = 0;
152
+
153
+ const sampleNames = Object.keys(ADVERSARIAL_THRESHOLDS);
154
+ for (const name of sampleNames) {
155
+ const sampleDir = path.join(ADVERSARIAL_DIR, name);
156
+ if (!fs.existsSync(sampleDir)) {
157
+ details.push({ name, score: 0, threshold: ADVERSARIAL_THRESHOLDS[name], detected: false, error: 'directory not found' });
158
+ continue;
159
+ }
160
+
161
+ const result = await silentScan(sampleDir);
162
+ const score = result.summary.riskScore;
163
+ const threshold = ADVERSARIAL_THRESHOLDS[name];
164
+ const isDetected = score >= threshold;
165
+ if (isDetected) detected++;
166
+ details.push({ name, score, threshold, detected: isDetected });
167
+ }
168
+
169
+ const total = sampleNames.length;
170
+ const adr = total > 0 ? detected / total : 0;
171
+ return { detected, total, adr, details };
172
+ }
173
+
174
+ /**
175
+ * Save metrics to metrics/v{version}.json
176
+ */
177
+ function saveMetrics(report) {
178
+ if (!fs.existsSync(METRICS_DIR)) {
179
+ fs.mkdirSync(METRICS_DIR, { recursive: true });
180
+ }
181
+ const filename = `v${report.version}.json`;
182
+ const filepath = path.join(METRICS_DIR, filename);
183
+ fs.writeFileSync(filepath, JSON.stringify(report, null, 2));
184
+ return filepath;
185
+ }
186
+
187
+ /**
188
+ * Main evaluate function
189
+ */
190
+ async function evaluate(options = {}) {
191
+ const version = require('../../package.json').version;
192
+ const jsonMode = options.json || false;
193
+
194
+ if (!jsonMode) {
195
+ console.log(`\n MUAD'DIB Evaluation (v${version})\n`);
196
+ console.log(` [1/3] Ground Truth...`);
197
+ }
198
+ const groundTruth = await evaluateGroundTruth();
199
+
200
+ if (!jsonMode) {
201
+ console.log(` [2/3] Benign packages...`);
202
+ }
203
+ const benign = await evaluateBenign();
204
+
205
+ if (!jsonMode) {
206
+ console.log(` [3/3] Adversarial samples...`);
207
+ }
208
+ const adversarial = await evaluateAdversarial();
209
+
210
+ const report = {
211
+ version,
212
+ date: new Date().toISOString(),
213
+ groundTruth,
214
+ benign,
215
+ adversarial
216
+ };
217
+
218
+ const metricsPath = saveMetrics(report);
219
+
220
+ if (jsonMode) {
221
+ console.log(JSON.stringify(report, null, 2));
222
+ } else {
223
+ const tprPct = (groundTruth.tpr * 100).toFixed(1);
224
+ const fprPct = (benign.fpr * 100).toFixed(1);
225
+ const adrPct = (adversarial.adr * 100).toFixed(1);
226
+
227
+ console.log('');
228
+ console.log(` Ground Truth (TPR): ${groundTruth.detected}/${groundTruth.total} ${tprPct}%`);
229
+ console.log(` Benign (FPR): ${benign.flagged}/${benign.total} ${fprPct}%`);
230
+ console.log(` Adversarial (ADR): ${adversarial.detected}/${adversarial.total} ${adrPct}%`);
231
+ console.log('');
232
+
233
+ // Show failed adversarial samples
234
+ const missed = adversarial.details.filter(d => !d.detected);
235
+ if (missed.length > 0) {
236
+ console.log(' Adversarial misses:');
237
+ for (const m of missed) {
238
+ console.log(` ${m.name}: score ${m.score} < threshold ${m.threshold}`);
239
+ }
240
+ console.log('');
241
+ }
242
+
243
+ // Show false positives
244
+ const fps = benign.details.filter(d => d.flagged);
245
+ if (fps.length > 0) {
246
+ console.log(' False positives:');
247
+ for (const fp of fps) {
248
+ console.log(` ${fp.name}: score ${fp.score}`);
249
+ }
250
+ console.log('');
251
+ }
252
+
253
+ console.log(` Saved: ${path.relative(ROOT, metricsPath)}`);
254
+ console.log('');
255
+ }
256
+
257
+ return report;
258
+ }
259
+
260
+ module.exports = {
261
+ evaluate,
262
+ evaluateGroundTruth,
263
+ evaluateBenign,
264
+ evaluateAdversarial,
265
+ saveMetrics,
266
+ silentScan,
267
+ ADVERSARIAL_THRESHOLDS,
268
+ GT_THRESHOLD,
269
+ BENIGN_THRESHOLD
270
+ };
package/src/index.js CHANGED
@@ -18,6 +18,7 @@ const { detectPythonProject, normalizePythonName } = require('./scanner/python.j
18
18
  const { loadCachedIOCs } = require('./ioc/updater.js');
19
19
  const { ensureIOCs } = require('./ioc/bootstrap.js');
20
20
  const { scanEntropy } = require('./scanner/entropy.js');
21
+ const { scanAIConfig } = require('./scanner/ai-config.js');
21
22
  const { detectSuddenLifecycleChange } = require('./temporal-analysis.js');
22
23
  const { detectSuddenAstChanges } = require('./temporal-ast-diff.js');
23
24
  const { detectPublishAnomaly } = require('./publish-anomaly.js');
@@ -105,7 +106,10 @@ function scanParanoid(targetPath) {
105
106
  if (stat.isSymbolicLink()) continue;
106
107
 
107
108
  if (stat.isDirectory()) {
108
- if (!excluded.includes(file)) {
109
+ const rel = path.relative(targetPath, fullPath).replace(/\\/g, '/');
110
+ const isExcluded = excluded.includes(file) ||
111
+ excluded.some(ex => rel === ex || rel.startsWith(ex + '/'));
112
+ if (!isExcluded) {
109
113
  walkDir(fullPath, depth + 1);
110
114
  }
111
115
  } else if (file.endsWith('.js') || file.endsWith('.json') || file.endsWith('.sh')) {
@@ -204,7 +208,7 @@ async function run(targetPath, options = {}) {
204
208
 
205
209
  // Apply --exclude dirs for this scan
206
210
  if (options.exclude && options.exclude.length > 0) {
207
- setExtraExcludes(options.exclude);
211
+ setExtraExcludes(options.exclude, targetPath);
208
212
  }
209
213
 
210
214
  // Detect Python project (synchronous, fast file reads)
@@ -231,7 +235,8 @@ async function run(targetPath, options = {}) {
231
235
  ghActionsThreats,
232
236
  pythonThreats,
233
237
  pypiTyposquatThreats,
234
- entropyThreats
238
+ entropyThreats,
239
+ aiConfigThreats
235
240
  ] = await Promise.all([
236
241
  scanPackageJson(targetPath),
237
242
  scanShellScripts(targetPath),
@@ -244,7 +249,8 @@ async function run(targetPath, options = {}) {
244
249
  Promise.resolve(scanGitHubActions(targetPath)),
245
250
  Promise.resolve(matchPythonIOCs(pythonDeps, targetPath)),
246
251
  Promise.resolve(checkPyPITyposquatting(pythonDeps, targetPath)),
247
- Promise.resolve(scanEntropy(targetPath, { entropyThreshold: options.entropyThreshold || undefined }))
252
+ Promise.resolve(scanEntropy(targetPath, { entropyThreshold: options.entropyThreshold || undefined })),
253
+ Promise.resolve(scanAIConfig(targetPath))
248
254
  ]);
249
255
 
250
256
  // Stop spinner now that scanning is complete
@@ -264,7 +270,8 @@ async function run(targetPath, options = {}) {
264
270
  ...ghActionsThreats,
265
271
  ...pythonThreats,
266
272
  ...pypiTyposquatThreats,
267
- ...entropyThreats
273
+ ...entropyThreats,
274
+ ...aiConfigThreats
268
275
  ];
269
276
 
270
277
  // Paranoid mode
@@ -1,3 +1,4 @@
1
+ // muaddib-ignore — os.homedir() is used for IOC cache path, not credential access
1
2
  const https = require('https');
2
3
  const fs = require('fs');
3
4
  const path = require('path');
@@ -56,6 +56,36 @@ const PLAYBOOKS = {
56
56
  exec_wget:
57
57
  'Execution de wget via child_process. Verifier l\'URL et les donnees.',
58
58
 
59
+ dynamic_require:
60
+ 'require() avec concatenation detecte. Technique d\'obfuscation pour masquer le module charge. Analyser les variables concatenees.',
61
+
62
+ dangerous_exec:
63
+ 'CRITIQUE: Execution de commande shell dangereuse detectee. Isoler la machine. Verifier si la commande a ete executee.',
64
+
65
+ staged_payload:
66
+ 'CRITIQUE: Code telecharge depuis le reseau et execute via eval(). Payload distant probable. Isoler et analyser le trafic reseau.',
67
+
68
+ network_require:
69
+ 'require(https/http) dans un script lifecycle. Le package telecharge du code lors de l\'installation. Verifier l\'URL de destination.',
70
+
71
+ node_inline_exec:
72
+ 'node -e dans un script lifecycle. Code inline execute a l\'installation. Analyser le code inline.',
73
+
74
+ dynamic_import:
75
+ 'import() dynamique detecte. Technique d\'evasion pour eviter la detection de require(). Verifier quel module est charge et son usage.',
76
+
77
+ env_proxy_intercept:
78
+ 'CRITIQUE: new Proxy(process.env) intercepte tous les acces aux variables d\'environnement. Technique d\'exfiltration silencieuse. Isoler la machine, regenerer tous les secrets.',
79
+
80
+ dynamic_require_exec:
81
+ 'CRITIQUE: exec/execSync appele sur un module charge via require() obfusque. Le module child_process est dissimule par concatenation/encodage. Isoler la machine, auditer les commandes executees.',
82
+
83
+ sandbox_evasion:
84
+ 'Code detecte la presence d\'un sandbox/container (/.dockerenv, /proc/cgroup). Technique anti-analyse: le malware adapte son comportement selon l\'environnement. Analyser les deux branches (sandbox vs production).',
85
+
86
+ detached_process:
87
+ 'spawn/fork avec {detached: true} detecte. Le processus enfant survit a la fin de npm install et execute le payload en arriere-plan. Verifier les processus en cours: ps aux | grep node. Tuer le processus suspect.',
88
+
59
89
  known_malicious_package:
60
90
  'CRITIQUE: Supprimer immediatement. rm -rf node_modules && npm cache clean --force && npm install',
61
91
 
@@ -223,6 +253,42 @@ const PLAYBOOKS = {
223
253
  new_publisher:
224
254
  'New publisher detected. Package published by a different user than before. Verify legitimacy by checking the package\'s npm page and changelog.',
225
255
 
256
+ credential_command_exec:
257
+ 'CRITIQUE: Le code utilise un outil CLI legitime (gh, gcloud, aws, az) pour voler des tokens. ' +
258
+ 'Verifier: gh auth status, gcloud auth list, aws sts get-caller-identity. ' +
259
+ 'Regenerer tous les tokens des outils concernes. Revoquer les sessions actives.',
260
+
261
+ workflow_write:
262
+ 'Le code cree un fichier dans .github/workflows/ — injection de workflow GitHub Actions. ' +
263
+ 'Supprimer le fichier cree. Auditer les workflows existants. ' +
264
+ 'Verifier les GitHub Actions runs recents pour des executions non autorisees.',
265
+
266
+ binary_dropper:
267
+ 'CRITIQUE: Pattern dropper detecte — fichier telecharge, rendu executable (chmod), et execute. ' +
268
+ 'Verifier /tmp pour des binaires suspects. Tuer les processus inconnus. ' +
269
+ 'Considerer la machine compromise si le binaire a ete execute.',
270
+
271
+ prototype_hook:
272
+ 'Prototype de fonction native modifie (fetch, XMLHttpRequest, http.request). ' +
273
+ 'Technique d\'interception de trafic pour voler des donnees en transit. ' +
274
+ 'Supprimer le package. Auditer le trafic reseau recent.',
275
+
276
+ ai_config_injection:
277
+ 'Fichier de config d\'agent IA contient des instructions malveillantes. ' +
278
+ 'NE PAS ouvrir le projet avec un agent IA sans verifier les fichiers .cursorrules, CLAUDE.md, copilot-instructions.md. ' +
279
+ 'Supprimer ou nettoyer ces fichiers avant toute utilisation. Technique ToxicSkills/Clinejection.',
280
+
281
+ ai_config_injection_critical:
282
+ 'CRITIQUE: Fichier de config d\'agent IA contient des commandes d\'exfiltration ou un combo shell + credential access. ' +
283
+ 'NE PAS ouvrir ce projet avec un agent IA. Supprimer les fichiers de config compromis. ' +
284
+ 'Si deja ouvert avec un agent IA, considerer la machine compromise. Regenerer tous les secrets.',
285
+
286
+ ai_agent_abuse:
287
+ 'CRITIQUE: Un agent IA (Claude, Gemini, Q) est invoque avec des flags de bypass de securite ' +
288
+ '(--dangerously-skip-permissions, --yolo, --trust-all-tools). Technique s1ngularity/Nx. ' +
289
+ 'NE PAS installer. Verifier si l\'agent a ete execute. Si oui, considerer la machine compromise. ' +
290
+ 'Auditer les fichiers sensibles (.ssh, .aws, .env) pour des acces non autorises.',
291
+
226
292
  canary_exfiltration:
227
293
  'CRITIQUE: Le package a tente de voler des credentials (honey tokens). Comportement malveillant confirme. ' +
228
294
  'NE PAS installer. Signaler immediatement sur npm/PyPI. ' +
@@ -385,6 +385,96 @@ const RULES = {
385
385
  references: ['https://attack.mitre.org/techniques/T1027/'],
386
386
  mitre: 'T1027'
387
387
  },
388
+ dynamic_require: {
389
+ id: 'MUADDIB-AST-006',
390
+ name: 'Dynamic Require with Concatenation',
391
+ severity: 'HIGH',
392
+ confidence: 'high',
393
+ description: 'require() avec concatenation de chaines — technique d\'obfuscation pour masquer le nom du module',
394
+ references: ['https://attack.mitre.org/techniques/T1027/'],
395
+ mitre: 'T1027'
396
+ },
397
+ dangerous_exec: {
398
+ id: 'MUADDIB-AST-007',
399
+ name: 'Dangerous Shell Command Execution',
400
+ severity: 'CRITICAL',
401
+ confidence: 'high',
402
+ description: 'exec() avec commande shell dangereuse (pipe to shell, reverse shell, netcat)',
403
+ references: ['https://owasp.org/www-community/attacks/Command_Injection'],
404
+ mitre: 'T1059.004'
405
+ },
406
+ staged_payload: {
407
+ id: 'MUADDIB-FLOW-002',
408
+ name: 'Staged Payload Execution',
409
+ severity: 'CRITICAL',
410
+ confidence: 'high',
411
+ description: 'Telechargement reseau + eval() dans le meme fichier — execution de payload distant',
412
+ references: ['https://attack.mitre.org/techniques/T1105/'],
413
+ mitre: 'T1105'
414
+ },
415
+ network_require: {
416
+ id: 'MUADDIB-PKG-006',
417
+ name: 'Network Module in Lifecycle Script',
418
+ severity: 'HIGH',
419
+ confidence: 'high',
420
+ description: 'require(https/http) dans un script lifecycle — telechargement au moment de l\'installation',
421
+ references: ['https://blog.phylum.io/shai-hulud-npm-worm'],
422
+ mitre: 'T1105'
423
+ },
424
+ node_inline_exec: {
425
+ id: 'MUADDIB-PKG-007',
426
+ name: 'Node Inline Execution in Lifecycle Script',
427
+ severity: 'HIGH',
428
+ confidence: 'high',
429
+ description: 'node -e dans un script lifecycle — execution de code inline au moment de l\'installation',
430
+ references: ['https://owasp.org/www-community/attacks/Command_Injection'],
431
+ mitre: 'T1059.007'
432
+ },
433
+ dynamic_import: {
434
+ id: 'MUADDIB-AST-008',
435
+ name: 'Dynamic import() of Dangerous Module',
436
+ severity: 'HIGH',
437
+ confidence: 'high',
438
+ description: 'import() dynamique pour charger un module dangereux ou avec argument calcule — technique d\'evasion pour eviter la detection de require()',
439
+ references: ['https://attack.mitre.org/techniques/T1027/'],
440
+ mitre: 'T1027'
441
+ },
442
+ env_proxy_intercept: {
443
+ id: 'MUADDIB-AST-009',
444
+ name: 'Environment Variable Proxy Interception',
445
+ severity: 'CRITICAL',
446
+ confidence: 'high',
447
+ description: 'new Proxy(process.env) detecte — intercepte silencieusement tous les acces aux variables d\'environnement pour exfiltration',
448
+ references: ['https://attack.mitre.org/techniques/T1552/001/'],
449
+ mitre: 'T1552.001'
450
+ },
451
+ dynamic_require_exec: {
452
+ id: 'MUADDIB-AST-010',
453
+ name: 'Command Execution via Dynamic Require',
454
+ severity: 'CRITICAL',
455
+ confidence: 'high',
456
+ description: 'exec/execSync appele sur un module charge dynamiquement (require obfusque) — execution de commandes dissimulees',
457
+ references: ['https://attack.mitre.org/techniques/T1059/007/'],
458
+ mitre: 'T1059.007'
459
+ },
460
+ sandbox_evasion: {
461
+ id: 'MUADDIB-AST-011',
462
+ name: 'Sandbox/Container Evasion',
463
+ severity: 'HIGH',
464
+ confidence: 'high',
465
+ description: 'Detection de sandbox/container (/.dockerenv, /proc/cgroup) — technique anti-analyse pour eviter la detection en environnement controle',
466
+ references: ['https://attack.mitre.org/techniques/T1497/001/'],
467
+ mitre: 'T1497.001'
468
+ },
469
+ detached_process: {
470
+ id: 'MUADDIB-AST-012',
471
+ name: 'Detached Background Process',
472
+ severity: 'HIGH',
473
+ confidence: 'high',
474
+ description: 'spawn/fork avec {detached: true} — le processus survit a la fin de npm install et execute le payload en arriere-plan',
475
+ references: ['https://attack.mitre.org/techniques/T1036/009/'],
476
+ mitre: 'T1036.009'
477
+ },
388
478
  dangerous_call_function: {
389
479
  id: 'MUADDIB-AST-005',
390
480
  name: 'new Function() Constructor',
@@ -395,6 +485,97 @@ const RULES = {
395
485
  mitre: 'T1059.007'
396
486
  },
397
487
 
488
+ credential_command_exec: {
489
+ id: 'MUADDIB-AST-014',
490
+ name: 'Credential Theft via CLI Tool',
491
+ severity: 'CRITICAL',
492
+ confidence: 'high',
493
+ description: 'exec/execSync appelle un outil CLI legitime pour voler des tokens d\'authentification (gh auth token, gcloud auth, aws sts). Technique s1ngularity/Nx.',
494
+ references: [
495
+ 'https://snyk.io/blog/malicious-npm-packages-abuse-ai-agents/',
496
+ 'https://attack.mitre.org/techniques/T1059/'
497
+ ],
498
+ mitre: 'T1059'
499
+ },
500
+ workflow_write: {
501
+ id: 'MUADDIB-AST-015',
502
+ name: 'GitHub Actions Workflow Write',
503
+ severity: 'HIGH',
504
+ confidence: 'high',
505
+ description: 'fs.writeFileSync cree un fichier dans .github/workflows — injection de workflow GitHub Actions pour persistence. Technique Shai-Hulud 2.0.',
506
+ references: [
507
+ 'https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack',
508
+ 'https://attack.mitre.org/techniques/T1195/002/'
509
+ ],
510
+ mitre: 'T1195.002'
511
+ },
512
+ binary_dropper: {
513
+ id: 'MUADDIB-AST-016',
514
+ name: 'Binary Dropper Pattern',
515
+ severity: 'CRITICAL',
516
+ confidence: 'high',
517
+ description: 'fs.chmodSync avec permissions executables (0o755/0o777) — pattern de dropper binaire: telecharge, ecrit, chmod, execute.',
518
+ references: [
519
+ 'https://www.sonatype.com/blog/phantomraven-supply-chain-attack',
520
+ 'https://attack.mitre.org/techniques/T1105/'
521
+ ],
522
+ mitre: 'T1105'
523
+ },
524
+ prototype_hook: {
525
+ id: 'MUADDIB-AST-017',
526
+ name: 'Native API Prototype Hooking',
527
+ severity: 'HIGH',
528
+ confidence: 'high',
529
+ description: 'Modification du prototype ou remplacement de fonctions natives du navigateur/Node.js (fetch, XMLHttpRequest, http.request). Technique chalk/debug (Sygnia, sept 2025) pour intercepter du trafic.',
530
+ references: [
531
+ 'https://www.sygnia.co/blog/malicious-chalk-debug-npm-packages/',
532
+ 'https://attack.mitre.org/techniques/T1557/'
533
+ ],
534
+ mitre: 'T1557'
535
+ },
536
+
537
+ ai_config_injection: {
538
+ id: 'MUADDIB-AICONF-001',
539
+ name: 'AI Config Prompt Injection',
540
+ severity: 'HIGH',
541
+ confidence: 'high',
542
+ description: 'Fichier de configuration d\'agent IA (.cursorrules, CLAUDE.md, copilot-instructions.md) contient des instructions d\'execution de commandes shell ou d\'acces a des credentials. Technique ToxicSkills/Clinejection.',
543
+ references: [
544
+ 'https://snyk.io/blog/toxicskills-prompt-injection-ai-agents/',
545
+ 'https://snyk.io/blog/clinejection-ai-config-prompt-injection/',
546
+ 'https://arxiv.org/abs/2601.17548'
547
+ ],
548
+ mitre: 'T1059'
549
+ },
550
+ ai_config_injection_critical: {
551
+ id: 'MUADDIB-AICONF-002',
552
+ name: 'AI Config Prompt Injection (Critical)',
553
+ severity: 'CRITICAL',
554
+ confidence: 'high',
555
+ description: 'Fichier de configuration d\'agent IA contient des commandes d\'exfiltration (curl POST vers un domaine externe, pipe vers shell) ou une combinaison commande shell + acces credentials. Attaque confirmee.',
556
+ references: [
557
+ 'https://snyk.io/blog/toxicskills-prompt-injection-ai-agents/',
558
+ 'https://snyk.io/blog/clinejection-ai-config-prompt-injection/',
559
+ 'https://arxiv.org/abs/2601.17548',
560
+ 'https://developer.nvidia.com/blog/ai-agent-security-guidance/'
561
+ ],
562
+ mitre: 'T1059'
563
+ },
564
+
565
+ ai_agent_abuse: {
566
+ id: 'MUADDIB-AST-013',
567
+ name: 'AI Agent Weaponization',
568
+ severity: 'CRITICAL',
569
+ confidence: 'high',
570
+ description: 'Invocation d\'un agent IA (Claude, Gemini, Q, Aider) avec des flags qui desactivent les controles de securite (--dangerously-skip-permissions, --yolo, --trust-all-tools). Technique s1ngularity/Nx (aout 2025).',
571
+ references: [
572
+ 'https://snyk.io/blog/malicious-npm-packages-abuse-ai-agents/',
573
+ 'https://stepsecurity.io/blog/ai-agent-weaponization-supply-chain',
574
+ 'https://attack.mitre.org/techniques/T1059/'
575
+ ],
576
+ mitre: 'T1059'
577
+ },
578
+
398
579
  // GitHub Actions patterns
399
580
  shai_hulud_backdoor: {
400
581
  id: 'MUADDIB-GHA-001',