muaddib-scanner 1.0.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 (41) hide show
  1. package/.github/workflows/scan.yml +33 -0
  2. package/LICENSE +21 -0
  3. package/MUADDIBLOGO.png +0 -0
  4. package/README.md +218 -0
  5. package/action/action.yml +28 -0
  6. package/bin/muaddib.js +84 -0
  7. package/data/iocs.json +38 -0
  8. package/docs/threat-model.md +116 -0
  9. package/iocs/hashes.yaml +220 -0
  10. package/iocs/packages.yaml +265 -0
  11. package/package.json +43 -0
  12. package/results.sarif +379 -0
  13. package/src/index.js +142 -0
  14. package/src/ioc/feeds.js +42 -0
  15. package/src/ioc/updater.js +244 -0
  16. package/src/ioc/yaml-loader.js +96 -0
  17. package/src/report.js +152 -0
  18. package/src/response/playbooks.js +115 -0
  19. package/src/rules/index.js +197 -0
  20. package/src/sarif.js +74 -0
  21. package/src/scanner/ast.js +175 -0
  22. package/src/scanner/dataflow.js +167 -0
  23. package/src/scanner/dependencies.js +110 -0
  24. package/src/scanner/hash.js +68 -0
  25. package/src/scanner/obfuscation.js +99 -0
  26. package/src/scanner/package.js +60 -0
  27. package/src/scanner/shell.js +63 -0
  28. package/src/watch.js +37 -0
  29. package/test/samples/malicious.js +20 -0
  30. package/tests/run-tests.js +363 -0
  31. package/tests/samples/ast/malicious.js +20 -0
  32. package/tests/samples/clean/safe.js +14 -0
  33. package/tests/samples/dataflow/exfiltration.js +20 -0
  34. package/tests/samples/edge/empty/empty.js +0 -0
  35. package/tests/samples/edge/invalid-syntax/broken.js +5 -0
  36. package/tests/samples/edge/large-file/large.js +6 -0
  37. package/tests/samples/edge/non-js/readme.txt +3 -0
  38. package/tests/samples/markers/shai-hulud.js +10 -0
  39. package/tests/samples/obfuscation/obfuscated.js +1 -0
  40. package/tests/samples/package/package.json +9 -0
  41. package/tests/samples/shell/malicious.sh +13 -0
@@ -0,0 +1,244 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const https = require('https');
4
+
5
+ const CACHE_PATH = path.join(__dirname, '../../.muaddib-cache');
6
+ const IOC_FILE = path.join(CACHE_PATH, 'iocs.json');
7
+ const { loadYAMLIOCs, getIOCStats } = require('./yaml-loader.js');
8
+
9
+ const BUILTIN_IOCS = {
10
+ packages: [
11
+ // Shai-Hulud v1 (septembre 2025)
12
+ { name: '@ctrl/tinycolor', version: '4.1.1', source: 'shai-hulud-v1' },
13
+ { name: 'ng2-file-upload', version: '*', source: 'shai-hulud-v1' },
14
+ { name: 'ngx-bootstrap', version: '*', source: 'shai-hulud-v1' },
15
+
16
+ // Shai-Hulud v2 (novembre 2025)
17
+ { name: '@asyncapi/specs', version: '*', source: 'shai-hulud-v2' },
18
+ { name: '@asyncapi/openapi-schema-parser', version: '*', source: 'shai-hulud-v2' },
19
+ { name: 'get-them-args', version: '*', source: 'shai-hulud-v2' },
20
+ { name: 'kill-port', version: '*', source: 'shai-hulud-v2' },
21
+ { name: 'shell-exec', version: '*', source: 'shai-hulud-v2' },
22
+ { name: 'posthog-node', version: '*', source: 'shai-hulud-v2' },
23
+ { name: 'posthog-js', version: '*', source: 'shai-hulud-v2' },
24
+ { name: '@postman/tunnel-agent', version: '*', source: 'shai-hulud-v2' },
25
+ { name: '@zapier/secret-scrubber', version: '*', source: 'shai-hulud-v2' },
26
+
27
+ // Shai-Hulud v3 Golden Path (decembre 2025)
28
+ { name: '@vietmoney/react-big-calendar', version: '0.26.2', source: 'shai-hulud-v3' },
29
+
30
+ // Attaques historiques
31
+ { name: 'flatmap-stream', version: '0.1.1', source: 'event-stream-2018' },
32
+ { name: 'event-stream', version: '3.3.6', source: 'event-stream-2018' },
33
+ { name: 'eslint-scope', version: '3.7.2', source: 'eslint-scope-2018' },
34
+ { name: 'eslint-config-prettier', version: '8.10.1', source: 'eslint-prettier-2025' },
35
+ { name: 'eslint-config-prettier', version: '9.1.1', source: 'eslint-prettier-2025' },
36
+ { name: 'eslint-plugin-prettier', version: '4.2.2', source: 'eslint-prettier-2025' },
37
+ { name: 'synckit', version: '0.11.9', source: 'eslint-prettier-2025' },
38
+ { name: '@pkgr/core', version: '0.2.8', source: 'eslint-prettier-2025' },
39
+ { name: 'napi-postinstall', version: '0.3.1', source: 'eslint-prettier-2025' },
40
+ { name: 'got-fetch', version: '5.1.11', source: 'eslint-prettier-2025' },
41
+ { name: 'is', version: '3.3.1', source: 'is-package-2025' },
42
+ { name: 'is', version: '5.0.0', source: 'is-package-2025' },
43
+
44
+ // Typosquats connus
45
+ { name: 'crossenv', version: '*', source: 'typosquat' },
46
+ { name: 'cross-env.js', version: '*', source: 'typosquat' },
47
+ { name: 'mongose', version: '*', source: 'typosquat' },
48
+ { name: 'mssql.js', version: '*', source: 'typosquat' },
49
+ { name: 'mssql-node', version: '*', source: 'typosquat' },
50
+ { name: 'babelcli', version: '*', source: 'typosquat' },
51
+ { name: 'http-proxy.js', version: '*', source: 'typosquat' },
52
+ { name: 'proxy.js', version: '*', source: 'typosquat' },
53
+ { name: 'shadowsock', version: '*', source: 'typosquat' },
54
+ { name: 'smb', version: '*', source: 'typosquat' },
55
+ { name: 'nodesass', version: '*', source: 'typosquat' },
56
+ { name: 'node-sass.js', version: '*', source: 'typosquat' },
57
+
58
+ // Protestware
59
+ { name: 'node-ipc', version: '10.1.1', source: 'protestware' },
60
+ { name: 'node-ipc', version: '10.1.2', source: 'protestware' },
61
+ { name: 'node-ipc', version: '10.1.3', source: 'protestware' },
62
+ { name: 'colors', version: '1.4.1', source: 'protestware' },
63
+ { name: 'colors', version: '1.4.2', source: 'protestware' },
64
+ { name: 'faker', version: '6.6.6', source: 'protestware' }
65
+ ],
66
+ files: [
67
+ 'setup_bun.js',
68
+ 'bun_environment.js',
69
+ 'bundle.js',
70
+ 'node-gyp.dll',
71
+ 'preinstall.js',
72
+ 'postinstall.js',
73
+ 'install.js'
74
+ ],
75
+ hashes: [
76
+ '62ee164b9b306250c1172583f138c9614139264f889fa99614903c12755468d0',
77
+ 'cbb9bc5a8496243e02f3cc080efbe3e4a1430ba0671f2e43a202bf45b05479cd',
78
+ 'f099c5d9ec417d4445a0328ac0ada9cde79fc37410914103ae9c609cbc0ee068',
79
+ 'a3894003ad1d293ba96d77881ccd2071446dc3f65f434669b49b3da92421901a',
80
+ 'f1df4896244500671eb4aa63ebb48ea11cee196fafaa0e9874e17b24ac053c02',
81
+ '9d59fd0bcc14b671079824c704575f201b74276238dc07a9c12a93a84195648a',
82
+ 'e0250076c1d2ac38777ea8f542431daf61fcbaab0ca9c196614b28065ef5b918',
83
+ '6c9628f72d2bb789fe8f097a611d61c8c53f2f21e47c6a5d8d3e0e0b8e5e8c8f',
84
+ 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2',
85
+ '4b2399646573bb737c4969563303d8ee2e9ddbd1b271f1ca9e35ea78062538db',
86
+ '46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09',
87
+ 'b74caeaa75e077c99f7d44f46daaf9796a3be43ecf24f2a1fd381844669da777',
88
+ 'dc67467a39b70d1cd4c1f7f7a459b35058163592f4a9e8fb4dffcbba98ef210c'
89
+ ],
90
+ markers: [
91
+ 'Sha1-Hulud',
92
+ 'Shai-Hulud',
93
+ 'The Second Coming',
94
+ 'Goldox-T3chs',
95
+ 'Only Happy Girl',
96
+ 'peacenotwar',
97
+ 'protestware',
98
+ '/dev/tcp',
99
+ 'reverse shell'
100
+ ]
101
+ };
102
+
103
+ const EXTERNAL_FEEDS = [
104
+ {
105
+ name: 'muaddib-community',
106
+ url: 'https://raw.githubusercontent.com/DNSZLSK/muad-dib/master/data/iocs.json',
107
+ parser: parseMuaddibFeed
108
+ }
109
+ ];
110
+
111
+ async function updateIOCs() {
112
+ console.log('[MUADDIB] Mise a jour des IOCs...\n');
113
+
114
+ if (!fs.existsSync(CACHE_PATH)) {
115
+ fs.mkdirSync(CACHE_PATH, { recursive: true });
116
+ }
117
+
118
+ const iocs = JSON.parse(JSON.stringify(BUILTIN_IOCS));
119
+
120
+ for (const feed of EXTERNAL_FEEDS) {
121
+ try {
122
+ console.log(`[INFO] Telechargement depuis ${feed.name}...`);
123
+ const data = await fetchUrl(feed.url);
124
+ const externalIOCs = feed.parser(data);
125
+
126
+ // Merge packages
127
+ for (const pkg of externalIOCs.packages || []) {
128
+ if (!iocs.packages.find(p => p.name === pkg.name && p.version === pkg.version)) {
129
+ iocs.packages.push(pkg);
130
+ }
131
+ }
132
+
133
+ // Merge hashes
134
+ for (const hash of externalIOCs.hashes || []) {
135
+ if (!iocs.hashes.includes(hash)) {
136
+ iocs.hashes.push(hash);
137
+ }
138
+ }
139
+
140
+ // Merge markers
141
+ for (const marker of externalIOCs.markers || []) {
142
+ if (!iocs.markers.includes(marker)) {
143
+ iocs.markers.push(marker);
144
+ }
145
+ }
146
+
147
+ // Merge files
148
+ for (const file of externalIOCs.files || []) {
149
+ if (!iocs.files.includes(file)) {
150
+ iocs.files.push(file);
151
+ }
152
+ }
153
+
154
+ console.log(`[OK] IOCs externes merges depuis ${feed.name}`);
155
+ } catch (err) {
156
+ console.log(`[WARN] Echec ${feed.name}: ${err.message}`);
157
+ }
158
+ }
159
+
160
+ iocs.updated = new Date().toISOString();
161
+
162
+ fs.writeFileSync(IOC_FILE, JSON.stringify(iocs, null, 2));
163
+ console.log(`\n[OK] IOCs sauvegardes:`);
164
+ console.log(` - ${iocs.packages.length} packages malveillants`);
165
+ console.log(` - ${iocs.files.length} fichiers suspects`);
166
+ console.log(` - ${iocs.hashes.length} hashes connus`);
167
+ console.log(` - ${iocs.markers.length} marqueurs\n`);
168
+
169
+ return iocs;
170
+ }
171
+
172
+ function fetchUrl(url) {
173
+ return new Promise((resolve, reject) => {
174
+ https.get(url, (res) => {
175
+ if (res.statusCode === 301 || res.statusCode === 302) {
176
+ fetchUrl(res.headers.location).then(resolve).catch(reject);
177
+ return;
178
+ }
179
+ if (res.statusCode !== 200) {
180
+ reject(new Error(`HTTP ${res.statusCode}`));
181
+ return;
182
+ }
183
+ let data = '';
184
+ res.on('data', chunk => data += chunk);
185
+ res.on('end', () => resolve(data));
186
+ }).on('error', reject);
187
+ });
188
+ }
189
+
190
+ function parseMuaddibFeed(data) {
191
+ try {
192
+ return JSON.parse(data);
193
+ } catch (e) {
194
+ return { packages: [], hashes: [], markers: [], files: [] };
195
+ }
196
+ }
197
+
198
+ function loadCachedIOCs() {
199
+ // Priorite 1 : IOCs YAML locaux
200
+ const yamlIOCs = loadYAMLIOCs();
201
+
202
+ // Priorite 2 : Cache telecharge
203
+ let cachedIOCs = { packages: [], hashes: [], markers: [], files: [] };
204
+ if (fs.existsSync(IOC_FILE)) {
205
+ cachedIOCs = JSON.parse(fs.readFileSync(IOC_FILE, 'utf8'));
206
+ }
207
+
208
+ // Merge : YAML + Cache + Builtin
209
+ const merged = {
210
+ packages: [...yamlIOCs.packages],
211
+ hashes: yamlIOCs.hashes.map(h => h.sha256),
212
+ markers: yamlIOCs.markers.map(m => m.pattern),
213
+ files: yamlIOCs.files.map(f => f.name)
214
+ };
215
+
216
+ // Ajouter les IOCs du cache sans doublons
217
+ for (const pkg of cachedIOCs.packages || []) {
218
+ if (!merged.packages.find(p => p.name === pkg.name)) {
219
+ merged.packages.push(pkg);
220
+ }
221
+ }
222
+
223
+ for (const hash of cachedIOCs.hashes || []) {
224
+ if (!merged.hashes.includes(hash)) {
225
+ merged.hashes.push(hash);
226
+ }
227
+ }
228
+
229
+ for (const marker of cachedIOCs.markers || []) {
230
+ if (!merged.markers.includes(marker)) {
231
+ merged.markers.push(marker);
232
+ }
233
+ }
234
+
235
+ for (const file of cachedIOCs.files || []) {
236
+ if (!merged.files.includes(file)) {
237
+ merged.files.push(file);
238
+ }
239
+ }
240
+
241
+ return merged;
242
+ }
243
+
244
+ module.exports = { updateIOCs, loadCachedIOCs };
@@ -0,0 +1,96 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const yaml = require('js-yaml');
4
+
5
+ const IOCS_DIR = path.join(__dirname, '../../iocs');
6
+
7
+ function loadYAMLIOCs() {
8
+ const iocs = {
9
+ packages: [],
10
+ hashes: [],
11
+ markers: [],
12
+ files: []
13
+ };
14
+
15
+ // Charger packages.yaml
16
+ const packagesPath = path.join(IOCS_DIR, 'packages.yaml');
17
+ if (fs.existsSync(packagesPath)) {
18
+ try {
19
+ const data = yaml.load(fs.readFileSync(packagesPath, 'utf8'));
20
+ if (data && data.packages) {
21
+ iocs.packages = data.packages.map(p => ({
22
+ id: p.id,
23
+ name: p.name,
24
+ version: p.version,
25
+ severity: p.severity,
26
+ confidence: p.confidence,
27
+ source: p.source,
28
+ description: p.description,
29
+ references: p.references || [],
30
+ mitre: p.mitre
31
+ }));
32
+ }
33
+ } catch (e) {
34
+ console.error('[WARN] Erreur parsing packages.yaml:', e.message);
35
+ }
36
+ }
37
+
38
+ // Charger hashes.yaml
39
+ const hashesPath = path.join(IOCS_DIR, 'hashes.yaml');
40
+ if (fs.existsSync(hashesPath)) {
41
+ try {
42
+ const data = yaml.load(fs.readFileSync(hashesPath, 'utf8'));
43
+
44
+ if (data && data.hashes) {
45
+ iocs.hashes = data.hashes.map(h => ({
46
+ id: h.id,
47
+ sha256: h.sha256,
48
+ file: h.file,
49
+ severity: h.severity,
50
+ confidence: h.confidence,
51
+ source: h.source,
52
+ description: h.description,
53
+ references: h.references || []
54
+ }));
55
+ }
56
+
57
+ if (data && data.markers) {
58
+ iocs.markers = data.markers.map(m => ({
59
+ id: m.id,
60
+ pattern: m.pattern,
61
+ severity: m.severity,
62
+ confidence: m.confidence,
63
+ source: m.source,
64
+ description: m.description
65
+ }));
66
+ }
67
+
68
+ if (data && data.files) {
69
+ iocs.files = data.files.map(f => ({
70
+ id: f.id,
71
+ name: f.name,
72
+ severity: f.severity,
73
+ confidence: f.confidence,
74
+ source: f.source,
75
+ description: f.description
76
+ }));
77
+ }
78
+ } catch (e) {
79
+ console.error('[WARN] Erreur parsing hashes.yaml:', e.message);
80
+ }
81
+ }
82
+
83
+ return iocs;
84
+ }
85
+
86
+ function getIOCStats() {
87
+ const iocs = loadYAMLIOCs();
88
+ return {
89
+ packages: iocs.packages.length,
90
+ hashes: iocs.hashes.length,
91
+ markers: iocs.markers.length,
92
+ files: iocs.files.length
93
+ };
94
+ }
95
+
96
+ module.exports = { loadYAMLIOCs, getIOCStats };
package/src/report.js ADDED
@@ -0,0 +1,152 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function generateHTML(results) {
5
+ const { target, timestamp, threats, summary } = results;
6
+
7
+ const threatRows = threats.map(t => `
8
+ <tr class="${t.severity.toLowerCase()}">
9
+ <td>${t.severity}</td>
10
+ <td>${t.type}</td>
11
+ <td>${t.message}</td>
12
+ <td>${t.file}</td>
13
+ <td>${t.playbook}</td>
14
+ </tr>
15
+ `).join('');
16
+
17
+ return `<!DOCTYPE html>
18
+ <html lang="fr">
19
+ <head>
20
+ <meta charset="UTF-8">
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
+ <title>MUAD'DIB - Rapport de scan</title>
23
+ <style>
24
+ body {
25
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
26
+ background: #1a1a2e;
27
+ color: #eee;
28
+ margin: 0;
29
+ padding: 20px;
30
+ }
31
+ .container {
32
+ max-width: 1200px;
33
+ margin: 0 auto;
34
+ }
35
+ h1 {
36
+ color: #e94560;
37
+ border-bottom: 2px solid #e94560;
38
+ padding-bottom: 10px;
39
+ }
40
+ .summary {
41
+ display: flex;
42
+ gap: 20px;
43
+ margin: 20px 0;
44
+ }
45
+ .summary-card {
46
+ background: #16213e;
47
+ padding: 20px;
48
+ border-radius: 8px;
49
+ flex: 1;
50
+ }
51
+ .summary-card h3 {
52
+ margin: 0 0 10px 0;
53
+ color: #888;
54
+ font-size: 14px;
55
+ }
56
+ .summary-card .value {
57
+ font-size: 32px;
58
+ font-weight: bold;
59
+ }
60
+ .critical .value { color: #e94560; }
61
+ .high .value { color: #ff6b35; }
62
+ .medium .value { color: #f9c74f; }
63
+ .total .value { color: #4ecdc4; }
64
+ table {
65
+ width: 100%;
66
+ border-collapse: collapse;
67
+ margin-top: 20px;
68
+ }
69
+ th, td {
70
+ padding: 12px;
71
+ text-align: left;
72
+ border-bottom: 1px solid #333;
73
+ }
74
+ th {
75
+ background: #16213e;
76
+ color: #e94560;
77
+ }
78
+ tr.critical { background: rgba(233, 69, 96, 0.2); }
79
+ tr.high { background: rgba(255, 107, 53, 0.2); }
80
+ tr.medium { background: rgba(249, 199, 79, 0.1); }
81
+ .meta {
82
+ color: #666;
83
+ font-size: 12px;
84
+ margin-top: 40px;
85
+ }
86
+ .ok {
87
+ background: #16213e;
88
+ padding: 40px;
89
+ border-radius: 8px;
90
+ text-align: center;
91
+ color: #4ecdc4;
92
+ font-size: 24px;
93
+ }
94
+ </style>
95
+ </head>
96
+ <body>
97
+ <div class="container">
98
+ <h1>MUAD'DIB - Rapport de scan</h1>
99
+
100
+ <div class="summary">
101
+ <div class="summary-card total">
102
+ <h3>TOTAL</h3>
103
+ <div class="value">${summary.total}</div>
104
+ </div>
105
+ <div class="summary-card critical">
106
+ <h3>CRITICAL</h3>
107
+ <div class="value">${summary.critical}</div>
108
+ </div>
109
+ <div class="summary-card high">
110
+ <h3>HIGH</h3>
111
+ <div class="value">${summary.high}</div>
112
+ </div>
113
+ <div class="summary-card medium">
114
+ <h3>MEDIUM</h3>
115
+ <div class="value">${summary.medium}</div>
116
+ </div>
117
+ </div>
118
+
119
+ ${threats.length > 0 ? `
120
+ <table>
121
+ <thead>
122
+ <tr>
123
+ <th>Severite</th>
124
+ <th>Type</th>
125
+ <th>Message</th>
126
+ <th>Fichier</th>
127
+ <th>Recommandation</th>
128
+ </tr>
129
+ </thead>
130
+ <tbody>
131
+ ${threatRows}
132
+ </tbody>
133
+ </table>
134
+ ` : '<div class="ok">Aucune menace detectee</div>'}
135
+
136
+ <div class="meta">
137
+ <p>Cible: ${target}</p>
138
+ <p>Date: ${timestamp}</p>
139
+ <p>Genere par MUAD'DIB</p>
140
+ </div>
141
+ </div>
142
+ </body>
143
+ </html>`;
144
+ }
145
+
146
+ function saveReport(results, outputPath) {
147
+ const html = generateHTML(results);
148
+ fs.writeFileSync(outputPath, html);
149
+ return outputPath;
150
+ }
151
+
152
+ module.exports = { generateHTML, saveReport };
@@ -0,0 +1,115 @@
1
+ const PLAYBOOKS = {
2
+ lifecycle_script:
3
+ 'Verifier le contenu du script. Desactiver avec "npm config set ignore-scripts true" si suspect.',
4
+
5
+ curl_pipe_sh:
6
+ 'CRITIQUE: Ne jamais executer. Inspecter l\'URL telechargee. Verifier si deja execute.',
7
+
8
+ wget_pipe_sh:
9
+ 'CRITIQUE: Ne jamais executer. Inspecter l\'URL telechargee. Verifier si deja execute.',
10
+
11
+ eval_usage:
12
+ 'Code dynamique detecte. Verifier la source des donnees evaluees. Risque d\'injection.',
13
+
14
+ child_process:
15
+ 'Execution de commandes systeme. Verifier quelles commandes sont lancees.',
16
+
17
+ child_process_import:
18
+ 'Module child_process importe. Verifier son utilisation dans le code.',
19
+
20
+ npmrc_access:
21
+ 'Acces au fichier .npmrc detecte. Risque de vol de token npm. Regenerer le token.',
22
+
23
+ npmrc_read:
24
+ 'Lecture du .npmrc. Regenerer immediatement: npm token revoke && npm login',
25
+
26
+ github_token_access:
27
+ 'Acces au GITHUB_TOKEN. Verifier les permissions. Regenerer si compromis.',
28
+
29
+ aws_credential_access:
30
+ 'Acces aux credentials AWS. Rotation immediate recommandee via AWS IAM.',
31
+
32
+ sensitive_env_access:
33
+ 'Acces a des variables sensibles. Verifier l\'usage. Rotation des secrets recommandee.',
34
+
35
+ base64_encoding:
36
+ 'Encodage base64 detecte. Souvent utilise pour obfusquer du code malveillant.',
37
+
38
+ base64_decode:
39
+ 'Decodage base64 detecte. Verifier ce qui est decode et execute.',
40
+
41
+ reverse_shell:
42
+ 'CRITIQUE: Reverse shell detecte. Machine potentiellement compromise. Isoler immediatement.',
43
+
44
+ netcat_shell:
45
+ 'CRITIQUE: Shell netcat detecte. Machine potentiellement compromise. Isoler immediatement.',
46
+
47
+ home_deletion:
48
+ 'CRITIQUE: Tentative de suppression du repertoire home. Dead man\'s switch probable.',
49
+
50
+ shred_home:
51
+ 'CRITIQUE: Destruction de donnees detectee. Dead man\'s switch de Shai-Hulud.',
52
+
53
+ curl_exfiltration:
54
+ 'Exfiltration de donnees via curl. Verifier les donnees envoyees et la destination.',
55
+
56
+ ssh_access:
57
+ 'Acces aux cles SSH. Regenerer les cles si compromis: ssh-keygen -t ed25519',
58
+
59
+ ssh_key_read:
60
+ 'Lecture des cles SSH. Regenerer immediatement toutes les cles.',
61
+
62
+ github_api_call:
63
+ 'Appel a l\'API GitHub. Verifier le contexte. Peut etre legitime ou exfiltration.',
64
+
65
+ exec_curl:
66
+ 'Execution de curl via child_process. Verifier l\'URL et les donnees.',
67
+
68
+ exec_wget:
69
+ 'Execution de wget via child_process. Verifier l\'URL et les donnees.',
70
+
71
+ wget_chmod_exec:
72
+ 'Telechargement et execution de binaire. Ne pas executer. Analyser le fichier.',
73
+
74
+ known_malicious_package:
75
+ 'CRITIQUE: Supprimer immediatement. rm -rf node_modules && npm cache clean --force && npm install',
76
+
77
+ lifecycle_script_dependency:
78
+ 'Verifier le contenu du script dans le package. Reinstaller avec --ignore-scripts si suspect.',
79
+
80
+ suspicious_file:
81
+ 'Fichier typique de Shai-Hulud. Ne pas executer. Verifier le hash contre les IOCs connus.',
82
+
83
+ obfuscation_detected:
84
+ 'Code volontairement obscurci. Analyser dans un environnement isole. Probable malware.',
85
+
86
+ dangerous_call_eval:
87
+ 'Appel eval() detecte. Verifier la source des donnees. Risque d\'execution de code arbitraire.',
88
+
89
+ dangerous_call_exec:
90
+ 'Execution de commande systeme. Verifier les arguments passes.',
91
+
92
+ dangerous_call_spawn:
93
+ 'Spawn de processus detecte. Verifier la commande executee.',
94
+
95
+ sensitive_string:
96
+ 'Reference a un chemin ou identifiant sensible. Verifier le contexte d\'utilisation.',
97
+
98
+ env_access:
99
+ 'Acces a une variable d\'environnement sensible. Verifier si les donnees sont exfiltrees.',
100
+
101
+ shai_hulud_marker:
102
+ 'CRITIQUE: Marqueur Shai-Hulud detecte. Package compromis. Supprimer immediatement et regenerer tous les tokens.',
103
+
104
+ known_malicious_hash:
105
+ 'CRITIQUE: Fichier malveillant confirme par hash. Supprimer immediatement. Considerer la machine compromise.',
106
+
107
+ suspicious_dataflow:
108
+ 'CRITIQUE: Code lit des credentials et les envoie sur le reseau. Exfiltration probable. Isoler la machine, regenerer tous les secrets.',
109
+ };
110
+
111
+ function getPlaybook(threatType) {
112
+ return PLAYBOOKS[threatType] || 'Analyser manuellement cette menace.';
113
+ }
114
+
115
+ module.exports = { getPlaybook, PLAYBOOKS };