muaddib-scanner 1.0.2 → 1.0.4

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/bin/muaddib.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const { run } = require('../src/index.js');
4
4
  const { updateIOCs } = require('../src/ioc/updater.js');
5
5
  const { watch } = require('../src/watch.js');
6
+ const { startDaemon } = require('../src/daemon.js');
6
7
 
7
8
  const args = process.argv.slice(2);
8
9
  const command = args[0];
@@ -13,7 +14,8 @@ let jsonOutput = false;
13
14
  let htmlOutput = null;
14
15
  let sarifOutput = null;
15
16
  let explainMode = false;
16
- let failLevel = 'high'; // Par defaut, fail sur HIGH et CRITICAL
17
+ let failLevel = 'high';
18
+ let webhookUrl = null;
17
19
 
18
20
  for (let i = 0; i < options.length; i++) {
19
21
  if (options[i] === '--json') {
@@ -29,6 +31,9 @@ for (let i = 0; i < options.length; i++) {
29
31
  } else if (options[i] === '--fail-on') {
30
32
  failLevel = options[i + 1] || 'high';
31
33
  i++;
34
+ } else if (options[i] === '--webhook') {
35
+ webhookUrl = options[i + 1];
36
+ i++;
32
37
  } else if (!options[i].startsWith('-')) {
33
38
  target = options[i];
34
39
  }
@@ -51,6 +56,8 @@ if (!command) {
51
56
  --explain Affiche les details de chaque detection
52
57
  --fail-on [level] Niveau de severite pour exit code (critical|high|medium|low)
53
58
  Defaut: high (fail sur HIGH et CRITICAL)
59
+ --webhook [url] Envoie une alerte Discord/Slack
60
+ muaddib daemon [options] Lance le daemon de surveillance
54
61
  `);
55
62
  process.exit(0);
56
63
  }
@@ -61,7 +68,8 @@ if (command === 'scan') {
61
68
  html: htmlOutput,
62
69
  sarif: sarifOutput,
63
70
  explain: explainMode,
64
- failLevel: failLevel
71
+ failLevel: failLevel,
72
+ webhook: webhookUrl
65
73
  }).then(exitCode => {
66
74
  process.exit(exitCode);
67
75
  });
@@ -75,10 +83,12 @@ if (command === 'scan') {
75
83
  process.exit(1);
76
84
  });
77
85
  } else if (command === 'help') {
78
- console.log('muaddib scan [path] [--json] [--html file] [--sarif file] [--explain] [--fail-on level]');
86
+ console.log('muaddib scan [path] [--json] [--html file] [--sarif file] [--explain] [--fail-on level] [--webhook url]');
79
87
  console.log('muaddib watch [path] - Surveille un projet en temps reel');
80
88
  console.log('muaddib update - Met a jour les IOCs');
89
+ } else if (command === 'daemon') {
90
+ startDaemon({ webhook: webhookUrl });
81
91
  } else {
82
92
  console.log(`Commande inconnue: ${command}`);
83
93
  process.exit(1);
84
- }
94
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Supply-chain threat detection & response for npm",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -38,6 +38,7 @@
38
38
  "dependencies": {
39
39
  "acorn": "^8.14.0",
40
40
  "acorn-walk": "^8.3.4",
41
- "js-yaml": "^4.1.0"
41
+ "js-yaml": "^4.1.0",
42
+ "lodash": "^4.17.21"
42
43
  }
43
44
  }
package/src/daemon.js ADDED
@@ -0,0 +1,142 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { spawn } = require('child_process');
4
+ const { run } = require('./index.js');
5
+
6
+ let webhookUrl = null;
7
+ let watchedDirs = [];
8
+ let isRunning = false;
9
+
10
+ async function startDaemon(options = {}) {
11
+ webhookUrl = options.webhook || null;
12
+ isRunning = true;
13
+
14
+ console.log(`
15
+ ╔════════════════════════════════════════════╗
16
+ ║ MUAD'DIB Security Daemon ║
17
+ ║ Surveillance npm install active ║
18
+ ╚════════════════════════════════════════════╝
19
+ `);
20
+
21
+ console.log('[DAEMON] Demarrage...');
22
+ console.log(`[DAEMON] Webhook: ${webhookUrl ? 'Configure' : 'Non configure'}`);
23
+ console.log('[DAEMON] Ctrl+C pour arreter\n');
24
+
25
+ // Surveille le dossier courant
26
+ const cwd = process.cwd();
27
+ watchDirectory(cwd);
28
+
29
+ // Garde le processus actif
30
+ process.on('SIGINT', () => {
31
+ console.log('\n[DAEMON] Arret...');
32
+ isRunning = false;
33
+ process.exit(0);
34
+ });
35
+
36
+ // Boucle infinie
37
+ while (isRunning) {
38
+ await sleep(1000);
39
+ }
40
+ }
41
+
42
+ function watchDirectory(dir) {
43
+ const nodeModulesPath = path.join(dir, 'node_modules');
44
+ const packageLockPath = path.join(dir, 'package-lock.json');
45
+ const yarnLockPath = path.join(dir, 'yarn.lock');
46
+
47
+ console.log(`[DAEMON] Surveillance de ${dir}`);
48
+
49
+ // Surveille package-lock.json
50
+ if (fs.existsSync(packageLockPath)) {
51
+ watchFile(packageLockPath, dir);
52
+ }
53
+
54
+ // Surveille yarn.lock
55
+ if (fs.existsSync(yarnLockPath)) {
56
+ watchFile(yarnLockPath, dir);
57
+ }
58
+
59
+ // Surveille node_modules
60
+ if (fs.existsSync(nodeModulesPath)) {
61
+ watchNodeModules(nodeModulesPath, dir);
62
+ }
63
+
64
+ // Surveille la creation de node_modules
65
+ fs.watch(dir, (eventType, filename) => {
66
+ if (filename === 'node_modules' && eventType === 'rename') {
67
+ const nmPath = path.join(dir, 'node_modules');
68
+ if (fs.existsSync(nmPath)) {
69
+ console.log('[DAEMON] node_modules detecte, scan en cours...');
70
+ triggerScan(dir);
71
+ }
72
+ }
73
+ if (filename === 'package-lock.json' || filename === 'yarn.lock') {
74
+ console.log(`[DAEMON] ${filename} modifie, scan en cours...`);
75
+ triggerScan(dir);
76
+ }
77
+ });
78
+ }
79
+
80
+ function watchFile(filePath, projectDir) {
81
+ let lastMtime = fs.statSync(filePath).mtime.getTime();
82
+
83
+ fs.watch(filePath, (eventType) => {
84
+ if (eventType === 'change') {
85
+ const currentMtime = fs.statSync(filePath).mtime.getTime();
86
+ if (currentMtime !== lastMtime) {
87
+ lastMtime = currentMtime;
88
+ console.log(`[DAEMON] ${path.basename(filePath)} modifie`);
89
+ triggerScan(projectDir);
90
+ }
91
+ }
92
+ });
93
+ }
94
+
95
+ function watchNodeModules(nodeModulesPath, projectDir) {
96
+ fs.watch(nodeModulesPath, { recursive: true }, (eventType, filename) => {
97
+ if (filename && filename.includes('package.json')) {
98
+ console.log(`[DAEMON] Nouveau package detecte: ${filename}`);
99
+ triggerScan(projectDir);
100
+ }
101
+ });
102
+ }
103
+
104
+ let scanTimeout = null;
105
+ let lastScanTime = 0;
106
+
107
+ function triggerScan(dir) {
108
+ const now = Date.now();
109
+
110
+ // Debounce: attend 3 secondes avant de scanner
111
+ if (scanTimeout) {
112
+ clearTimeout(scanTimeout);
113
+ }
114
+
115
+ // Evite les scans trop frequents (minimum 10 secondes entre chaque)
116
+ if (now - lastScanTime < 10000) {
117
+ scanTimeout = setTimeout(() => triggerScan(dir), 10000 - (now - lastScanTime));
118
+ return;
119
+ }
120
+
121
+ scanTimeout = setTimeout(async () => {
122
+ lastScanTime = Date.now();
123
+ console.log(`\n[DAEMON] ========== SCAN AUTOMATIQUE ==========`);
124
+ console.log(`[DAEMON] Cible: ${dir}`);
125
+ console.log(`[DAEMON] Heure: ${new Date().toLocaleTimeString()}\n`);
126
+
127
+ try {
128
+ await run(dir, { webhook: webhookUrl });
129
+ } catch (err) {
130
+ console.log(`[DAEMON] Erreur scan: ${err.message}`);
131
+ }
132
+
133
+ console.log(`\n[DAEMON] ======================================\n`);
134
+ console.log('[DAEMON] En attente de modifications...');
135
+ }, 3000);
136
+ }
137
+
138
+ function sleep(ms) {
139
+ return new Promise(resolve => setTimeout(resolve, ms));
140
+ }
141
+
142
+ module.exports = { startDaemon };
package/src/index.js CHANGED
@@ -10,6 +10,7 @@ const { getRule } = require('./rules/index.js');
10
10
  const { saveReport } = require('./report.js');
11
11
  const { saveSARIF } = require('./sarif.js');
12
12
  const { scanTyposquatting } = require('./scanner/typosquat.js');
13
+ const { sendWebhook } = require('./webhook.js');
13
14
 
14
15
  async function run(targetPath, options = {}) {
15
16
  const threats = [];
@@ -53,15 +54,34 @@ async function run(targetPath, options = {}) {
53
54
  };
54
55
  });
55
56
 
57
+ // Calculer le score de risque (0-100)
58
+ const criticalCount = threats.filter(t => t.severity === 'CRITICAL').length;
59
+ const highCount = threats.filter(t => t.severity === 'HIGH').length;
60
+ const mediumCount = threats.filter(t => t.severity === 'MEDIUM').length;
61
+
62
+ let riskScore = 0;
63
+ riskScore += criticalCount * 25; // CRITICAL = 25 points
64
+ riskScore += highCount * 10; // HIGH = 10 points
65
+ riskScore += mediumCount * 3; // MEDIUM = 3 points
66
+ riskScore = Math.min(100, riskScore); // Cap a 100
67
+
68
+ const riskLevel = riskScore >= 75 ? 'CRITICAL'
69
+ : riskScore >= 50 ? 'HIGH'
70
+ : riskScore >= 25 ? 'MEDIUM'
71
+ : riskScore > 0 ? 'LOW'
72
+ : 'SAFE';
73
+
56
74
  const result = {
57
75
  target: targetPath,
58
76
  timestamp: new Date().toISOString(),
59
77
  threats: enrichedThreats,
60
78
  summary: {
61
79
  total: threats.length,
62
- critical: threats.filter(t => t.severity === 'CRITICAL').length,
63
- high: threats.filter(t => t.severity === 'HIGH').length,
64
- medium: threats.filter(t => t.severity === 'MEDIUM').length
80
+ critical: criticalCount,
81
+ high: highCount,
82
+ medium: mediumCount,
83
+ riskScore: riskScore,
84
+ riskLevel: riskLevel
65
85
  }
66
86
  };
67
87
 
@@ -105,10 +125,14 @@ async function run(targetPath, options = {}) {
105
125
  });
106
126
  }
107
127
  }
108
- // Sortie normale
128
+ // Sortie normale
109
129
  else {
110
130
  console.log(`\n[MUADDIB] Scan de ${targetPath}\n`);
111
131
 
132
+ // Afficher le score de risque
133
+ const scoreBar = '█'.repeat(Math.floor(result.summary.riskScore / 5)) + '░'.repeat(20 - Math.floor(result.summary.riskScore / 5));
134
+ console.log(`[SCORE] ${result.summary.riskScore}/100 [${scoreBar}] ${result.summary.riskLevel}\n`);
135
+
112
136
  if (threats.length === 0) {
113
137
  console.log('[OK] Aucune menace detectee.\n');
114
138
  } else {
@@ -129,7 +153,17 @@ async function run(targetPath, options = {}) {
129
153
  }
130
154
  }
131
155
 
132
- // Calculer exit code selon le niveau de fail
156
+ // Envoyer webhook si configure
157
+ if (options.webhook) {
158
+ try {
159
+ await sendWebhook(options.webhook, result);
160
+ console.log(`[OK] Alerte envoyee au webhook`);
161
+ } catch (err) {
162
+ console.log(`[WARN] Echec envoi webhook: ${err.message}`);
163
+ }
164
+ }
165
+
166
+ // Calculer exit code selon le niveau de fail
133
167
  const failLevel = options.failLevel || 'high';
134
168
  const severityLevels = {
135
169
  critical: ['CRITICAL'],
@@ -21,6 +21,34 @@ const POPULAR_PACKAGES = [
21
21
  'prop-types', 'cross-env', 'npm', 'yarn', 'pnpm', 'node-fetch', 'got'
22
22
  ];
23
23
 
24
+ // Packages legitimes qui ressemblent a des populaires mais sont OK
25
+ const WHITELIST = [
26
+ 'acorn',
27
+ 'acorn-walk',
28
+ 'js-yaml',
29
+ 'cross-env',
30
+ 'node-fetch',
31
+ 'node-gyp',
32
+ 'core-js',
33
+ 'lodash-es',
34
+ 'date-fns',
35
+ 'ts-node',
36
+ 'ts-jest',
37
+ 'css-loader',
38
+ 'style-loader',
39
+ 'file-loader',
40
+ 'url-loader',
41
+ 'babel-loader',
42
+ 'vue-loader',
43
+ 'react-dom',
44
+ 'react-router',
45
+ 'react-redux',
46
+ 'vue-router',
47
+ 'express-session',
48
+ 'body-parser',
49
+ 'cookie-parser'
50
+ ];
51
+
24
52
  // Techniques de typosquatting connues
25
53
  const TYPOSQUAT_PATTERNS = [
26
54
  { type: 'missing_char', fn: (name) => generateMissingChar(name) },
@@ -69,6 +97,9 @@ async function scanTyposquatting(targetPath) {
69
97
  }
70
98
 
71
99
  function findTyposquatMatch(name) {
100
+ // Ignore les packages whitelistes
101
+ if (WHITELIST.includes(name)) return null;
102
+
72
103
  // Ignore les packages scoped (@org/package)
73
104
  if (name.startsWith('@')) return null;
74
105
 
package/src/webhook.js ADDED
@@ -0,0 +1,176 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+
4
+ async function sendWebhook(url, results) {
5
+ const isDiscord = url.includes('discord.com');
6
+ const isSlack = url.includes('hooks.slack.com');
7
+
8
+ let payload;
9
+
10
+ if (isDiscord) {
11
+ payload = formatDiscord(results);
12
+ } else if (isSlack) {
13
+ payload = formatSlack(results);
14
+ } else {
15
+ payload = formatGeneric(results);
16
+ }
17
+
18
+ return send(url, payload);
19
+ }
20
+
21
+ function formatDiscord(results) {
22
+ const { summary, threats, target } = results;
23
+
24
+ const color = summary.riskLevel === 'CRITICAL' ? 0xe74c3c
25
+ : summary.riskLevel === 'HIGH' ? 0xe67e22
26
+ : summary.riskLevel === 'MEDIUM' ? 0xf1c40f
27
+ : summary.riskLevel === 'LOW' ? 0x3498db
28
+ : 0x2ecc71;
29
+
30
+ const criticalThreats = threats
31
+ .filter(t => t.severity === 'CRITICAL')
32
+ .slice(0, 5)
33
+ .map(t => `- ${t.message}`)
34
+ .join('\n');
35
+
36
+ return {
37
+ embeds: [{
38
+ title: 'MUAD\'DIB Security Scan',
39
+ description: `Scan de **${target}**`,
40
+ color: color,
41
+ fields: [
42
+ {
43
+ name: 'Score de risque',
44
+ value: `**${summary.riskScore}/100** (${summary.riskLevel})`,
45
+ inline: true
46
+ },
47
+ {
48
+ name: 'Menaces',
49
+ value: `${summary.critical} CRITICAL\n${summary.high} HIGH\n${summary.medium} MEDIUM`,
50
+ inline: true
51
+ },
52
+ {
53
+ name: 'Total',
54
+ value: `**${summary.total}** menace(s)`,
55
+ inline: true
56
+ }
57
+ ],
58
+ footer: {
59
+ text: 'MUAD\'DIB - Supply-chain threat detection'
60
+ },
61
+ timestamp: results.timestamp
62
+ }]
63
+ };
64
+ }
65
+
66
+ function formatSlack(results) {
67
+ const { summary, threats, target } = results;
68
+
69
+ const emoji = summary.riskLevel === 'CRITICAL' ? ':rotating_light:'
70
+ : summary.riskLevel === 'HIGH' ? ':warning:'
71
+ : summary.riskLevel === 'MEDIUM' ? ':large_yellow_circle:'
72
+ : summary.riskLevel === 'LOW' ? ':information_source:'
73
+ : ':white_check_mark:';
74
+
75
+ const criticalList = threats
76
+ .filter(t => t.severity === 'CRITICAL')
77
+ .slice(0, 5)
78
+ .map(t => `• ${t.message}`)
79
+ .join('\n');
80
+
81
+ return {
82
+ blocks: [
83
+ {
84
+ type: 'header',
85
+ text: {
86
+ type: 'plain_text',
87
+ text: `${emoji} MUAD'DIB Security Scan`
88
+ }
89
+ },
90
+ {
91
+ type: 'section',
92
+ fields: [
93
+ {
94
+ type: 'mrkdwn',
95
+ text: `*Cible:*\n${target}`
96
+ },
97
+ {
98
+ type: 'mrkdwn',
99
+ text: `*Score:*\n${summary.riskScore}/100 (${summary.riskLevel})`
100
+ }
101
+ ]
102
+ },
103
+ {
104
+ type: 'section',
105
+ fields: [
106
+ {
107
+ type: 'mrkdwn',
108
+ text: `*CRITICAL:* ${summary.critical}`
109
+ },
110
+ {
111
+ type: 'mrkdwn',
112
+ text: `*HIGH:* ${summary.high}`
113
+ },
114
+ {
115
+ type: 'mrkdwn',
116
+ text: `*MEDIUM:* ${summary.medium}`
117
+ },
118
+ {
119
+ type: 'mrkdwn',
120
+ text: `*Total:* ${summary.total}`
121
+ }
122
+ ]
123
+ }
124
+ ]
125
+ };
126
+ }
127
+
128
+ function formatGeneric(results) {
129
+ return {
130
+ tool: 'MUADDIB',
131
+ target: results.target,
132
+ timestamp: results.timestamp,
133
+ summary: results.summary,
134
+ threats: results.threats.map(t => ({
135
+ type: t.type,
136
+ severity: t.severity,
137
+ message: t.message,
138
+ file: t.file
139
+ }))
140
+ };
141
+ }
142
+
143
+ function send(url, payload) {
144
+ return new Promise((resolve, reject) => {
145
+ const urlObj = new URL(url);
146
+ const protocol = urlObj.protocol === 'https:' ? https : http;
147
+
148
+ const options = {
149
+ hostname: urlObj.hostname,
150
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
151
+ path: urlObj.pathname + urlObj.search,
152
+ method: 'POST',
153
+ headers: {
154
+ 'Content-Type': 'application/json'
155
+ }
156
+ };
157
+
158
+ const req = protocol.request(options, (res) => {
159
+ let data = '';
160
+ res.on('data', chunk => data += chunk);
161
+ res.on('end', () => {
162
+ if (res.statusCode >= 200 && res.statusCode < 300) {
163
+ resolve({ success: true, status: res.statusCode });
164
+ } else {
165
+ reject(new Error(`Webhook failed: HTTP ${res.statusCode}`));
166
+ }
167
+ });
168
+ });
169
+
170
+ req.on('error', reject);
171
+ req.write(JSON.stringify(payload));
172
+ req.end();
173
+ });
174
+ }
175
+
176
+ module.exports = { sendWebhook };
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Run Extension",
6
+ "type": "extensionHost",
7
+ "request": "launch",
8
+ "args": [
9
+ "--extensionDevelopmentPath=${workspaceFolder}"
10
+ ]
11
+ }
12
+ ]
13
+ }
File without changes
File without changes
@@ -0,0 +1,264 @@
1
+ const vscode = require('vscode');
2
+ const { exec } = require('child_process');
3
+ const path = require('path');
4
+
5
+ let diagnosticCollection;
6
+
7
+ function activate(context) {
8
+ console.log('MUAD\'DIB Security Scanner active');
9
+
10
+ // Collection de diagnostics pour afficher les erreurs
11
+ diagnosticCollection = vscode.languages.createDiagnosticCollection('muaddib');
12
+ context.subscriptions.push(diagnosticCollection);
13
+
14
+ // Commande: Scanner le projet
15
+ const scanCommand = vscode.commands.registerCommand('muaddib.scan', async () => {
16
+ await scanProject();
17
+ });
18
+ context.subscriptions.push(scanCommand);
19
+
20
+ // Commande: Scanner le fichier actuel
21
+ const scanFileCommand = vscode.commands.registerCommand('muaddib.scanFile', async () => {
22
+ await scanCurrentFile();
23
+ });
24
+ context.subscriptions.push(scanFileCommand);
25
+
26
+ // Auto-scan a l'ouverture si active
27
+ const config = vscode.workspace.getConfiguration('muaddib');
28
+ if (config.get('autoScan')) {
29
+ scanProject();
30
+ }
31
+
32
+ // Re-scan quand package.json change
33
+ const watcher = vscode.workspace.createFileSystemWatcher('**/package.json');
34
+ watcher.onDidChange(() => {
35
+ const config = vscode.workspace.getConfiguration('muaddib');
36
+ if (config.get('autoScan')) {
37
+ scanProject();
38
+ }
39
+ });
40
+ context.subscriptions.push(watcher);
41
+ }
42
+
43
+ async function scanProject() {
44
+ const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
45
+ if (!workspaceFolder) {
46
+ vscode.window.showWarningMessage('MUAD\'DIB: Aucun projet ouvert');
47
+ return;
48
+ }
49
+
50
+ const projectPath = workspaceFolder.uri.fsPath;
51
+
52
+ vscode.window.withProgress({
53
+ location: vscode.ProgressLocation.Notification,
54
+ title: 'MUAD\'DIB: Scan en cours...',
55
+ cancellable: false
56
+ }, async () => {
57
+ return new Promise((resolve) => {
58
+ const config = vscode.workspace.getConfiguration('muaddib');
59
+ const webhookUrl = config.get('webhookUrl');
60
+ const failLevel = config.get('failLevel');
61
+
62
+ let cmd = `npx muaddib-scanner scan "${projectPath}" --json`;
63
+ if (webhookUrl) {
64
+ cmd += ` --webhook "${webhookUrl}"`;
65
+ }
66
+
67
+ exec(cmd, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
68
+ try {
69
+ const result = JSON.parse(stdout);
70
+ displayResults(result, projectPath);
71
+ } catch (e) {
72
+ if (stdout.includes('Aucune menace')) {
73
+ vscode.window.showInformationMessage('MUAD\'DIB: Aucune menace detectee');
74
+ diagnosticCollection.clear();
75
+ } else {
76
+ vscode.window.showErrorMessage(`MUAD\'DIB: Erreur de scan - ${e.message}`);
77
+ }
78
+ }
79
+ resolve();
80
+ });
81
+ });
82
+ });
83
+ }
84
+
85
+ async function scanCurrentFile() {
86
+ const editor = vscode.window.activeTextEditor;
87
+ if (!editor) {
88
+ vscode.window.showWarningMessage('MUAD\'DIB: Aucun fichier ouvert');
89
+ return;
90
+ }
91
+
92
+ const filePath = editor.document.uri.fsPath;
93
+ if (!filePath.endsWith('.js') && !filePath.endsWith('.json')) {
94
+ vscode.window.showWarningMessage('MUAD\'DIB: Seuls les fichiers JS et JSON sont scannes');
95
+ return;
96
+ }
97
+
98
+ const dirPath = path.dirname(filePath);
99
+
100
+ vscode.window.withProgress({
101
+ location: vscode.ProgressLocation.Notification,
102
+ title: 'MUAD\'DIB: Scan du fichier...',
103
+ cancellable: false
104
+ }, async () => {
105
+ return new Promise((resolve) => {
106
+ exec(`npx muaddib-scanner scan "${dirPath}" --json`, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
107
+ try {
108
+ const result = JSON.parse(stdout);
109
+ // Filtrer les menaces pour ce fichier seulement
110
+ const fileThreats = result.threats.filter(t =>
111
+ t.file === path.basename(filePath) || t.file.includes(path.basename(filePath))
112
+ );
113
+ if (fileThreats.length > 0) {
114
+ result.threats = fileThreats;
115
+ result.summary.total = fileThreats.length;
116
+ displayResults(result, dirPath);
117
+ } else {
118
+ vscode.window.showInformationMessage('MUAD\'DIB: Aucune menace dans ce fichier');
119
+ }
120
+ } catch (e) {
121
+ vscode.window.showInformationMessage('MUAD\'DIB: Aucune menace detectee');
122
+ }
123
+ resolve();
124
+ });
125
+ });
126
+ });
127
+ }
128
+
129
+ function displayResults(result, projectPath) {
130
+ const { threats, summary } = result;
131
+
132
+ // Clear previous diagnostics
133
+ diagnosticCollection.clear();
134
+
135
+ if (threats.length === 0) {
136
+ vscode.window.showInformationMessage('MUAD\'DIB: Aucune menace detectee');
137
+ return;
138
+ }
139
+
140
+ // Afficher le score
141
+ const scoreMessage = `MUAD'DIB: Score ${summary.riskScore}/100 (${summary.riskLevel}) - ${summary.total} menace(s)`;
142
+
143
+ if (summary.riskLevel === 'CRITICAL' || summary.riskLevel === 'HIGH') {
144
+ vscode.window.showErrorMessage(scoreMessage, 'Voir details').then(selection => {
145
+ if (selection === 'Voir details') {
146
+ showDetailPanel(result);
147
+ }
148
+ });
149
+ } else {
150
+ vscode.window.showWarningMessage(scoreMessage, 'Voir details').then(selection => {
151
+ if (selection === 'Voir details') {
152
+ showDetailPanel(result);
153
+ }
154
+ });
155
+ }
156
+
157
+ // Creer les diagnostics pour chaque menace
158
+ const diagnosticsMap = new Map();
159
+
160
+ for (const threat of threats) {
161
+ const filePath = path.join(projectPath, threat.file);
162
+ const uri = vscode.Uri.file(filePath);
163
+
164
+ const severity = threat.severity === 'CRITICAL' ? vscode.DiagnosticSeverity.Error
165
+ : threat.severity === 'HIGH' ? vscode.DiagnosticSeverity.Error
166
+ : threat.severity === 'MEDIUM' ? vscode.DiagnosticSeverity.Warning
167
+ : vscode.DiagnosticSeverity.Information;
168
+
169
+ const line = (threat.line || 1) - 1;
170
+ const range = new vscode.Range(line, 0, line, 100);
171
+
172
+ const diagnostic = new vscode.Diagnostic(
173
+ range,
174
+ `[${threat.severity}] ${threat.message}`,
175
+ severity
176
+ );
177
+ diagnostic.source = 'MUAD\'DIB';
178
+ diagnostic.code = threat.rule_id || threat.type;
179
+
180
+ if (!diagnosticsMap.has(uri.toString())) {
181
+ diagnosticsMap.set(uri.toString(), []);
182
+ }
183
+ diagnosticsMap.get(uri.toString()).push(diagnostic);
184
+ }
185
+
186
+ // Appliquer les diagnostics
187
+ for (const [uriString, diagnostics] of diagnosticsMap) {
188
+ diagnosticCollection.set(vscode.Uri.parse(uriString), diagnostics);
189
+ }
190
+ }
191
+
192
+ function showDetailPanel(result) {
193
+ const panel = vscode.window.createWebviewPanel(
194
+ 'muaddibResults',
195
+ 'MUAD\'DIB - Resultats',
196
+ vscode.ViewColumn.Two,
197
+ { enableScripts: true }
198
+ );
199
+
200
+ const { threats, summary } = result;
201
+
202
+ const threatRows = threats.map(t => `
203
+ <tr class="${t.severity.toLowerCase()}">
204
+ <td>${t.severity}</td>
205
+ <td>${t.type}</td>
206
+ <td>${t.message}</td>
207
+ <td>${t.file}</td>
208
+ <td>${t.playbook || '-'}</td>
209
+ </tr>
210
+ `).join('');
211
+
212
+ panel.webview.html = `
213
+ <!DOCTYPE html>
214
+ <html>
215
+ <head>
216
+ <style>
217
+ body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 20px; background: #1e1e1e; color: #fff; }
218
+ h1 { color: #e94560; }
219
+ .score { font-size: 24px; margin: 20px 0; }
220
+ .critical { color: #e74c3c; }
221
+ .high { color: #e67e22; }
222
+ .medium { color: #f1c40f; }
223
+ .low { color: #3498db; }
224
+ table { width: 100%; border-collapse: collapse; margin-top: 20px; }
225
+ th, td { padding: 10px; text-align: left; border-bottom: 1px solid #333; }
226
+ th { background: #2d2d2d; color: #e94560; }
227
+ tr.critical { background: rgba(231, 76, 60, 0.2); }
228
+ tr.high { background: rgba(230, 126, 34, 0.2); }
229
+ tr.medium { background: rgba(241, 196, 15, 0.1); }
230
+ </style>
231
+ </head>
232
+ <body>
233
+ <h1>MUAD'DIB Security Scan</h1>
234
+ <div class="score">
235
+ Score: <strong>${summary.riskScore}/100</strong>
236
+ (<span class="${summary.riskLevel.toLowerCase()}">${summary.riskLevel}</span>)
237
+ </div>
238
+ <p>CRITICAL: ${summary.critical} | HIGH: ${summary.high} | MEDIUM: ${summary.medium}</p>
239
+ <table>
240
+ <thead>
241
+ <tr>
242
+ <th>Severite</th>
243
+ <th>Type</th>
244
+ <th>Message</th>
245
+ <th>Fichier</th>
246
+ <th>Recommandation</th>
247
+ </tr>
248
+ </thead>
249
+ <tbody>
250
+ ${threatRows}
251
+ </tbody>
252
+ </table>
253
+ </body>
254
+ </html>
255
+ `;
256
+ }
257
+
258
+ function deactivate() {
259
+ if (diagnosticCollection) {
260
+ diagnosticCollection.dispose();
261
+ }
262
+ }
263
+
264
+ module.exports = { activate, deactivate };
Binary file
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "muaddib-vscode",
3
+ "displayName": "MUAD'DIB Security Scanner",
4
+ "description": "Detecte les attaques supply chain npm directement dans VS Code",
5
+ "version": "1.0.0",
6
+ "publisher": "dnszlsk",
7
+ "engines": {
8
+ "vscode": "^1.85.0"
9
+ },
10
+ "categories": [
11
+ "Linters",
12
+ "Other"
13
+ ],
14
+ "keywords": [
15
+ "security",
16
+ "npm",
17
+ "supply-chain",
18
+ "malware",
19
+ "scanner"
20
+ ],
21
+ "icon": "icon.png",
22
+ "activationEvents": [
23
+ "workspaceContains:package.json"
24
+ ],
25
+ "main": "./extension.js",
26
+ "contributes": {
27
+ "commands": [
28
+ {
29
+ "command": "muaddib.scan",
30
+ "title": "MUAD'DIB: Scan Project"
31
+ },
32
+ {
33
+ "command": "muaddib.scanFile",
34
+ "title": "MUAD'DIB: Scan Current File"
35
+ }
36
+ ],
37
+ "configuration": {
38
+ "title": "MUAD'DIB",
39
+ "properties": {
40
+ "muaddib.autoScan": {
41
+ "type": "boolean",
42
+ "default": true,
43
+ "description": "Scanner automatiquement a l'ouverture du projet"
44
+ },
45
+ "muaddib.webhookUrl": {
46
+ "type": "string",
47
+ "default": "",
48
+ "description": "URL du webhook Discord/Slack pour les alertes"
49
+ },
50
+ "muaddib.failLevel": {
51
+ "type": "string",
52
+ "enum": ["critical", "high", "medium", "low"],
53
+ "default": "high",
54
+ "description": "Niveau de severite pour afficher les alertes"
55
+ }
56
+ }
57
+ }
58
+ },
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "https://github.com/DNSZLSK/muad-dib"
62
+ },
63
+ "license": "MIT"
64
+ }
@@ -0,0 +1,44 @@
1
+ # MUAD'DIB Security Scanner for VS Code
2
+
3
+ Detecte les attaques supply chain npm directement dans VS Code.
4
+
5
+ ## Features
6
+
7
+ - Scan automatique a l'ouverture d'un projet npm
8
+ - Detection des packages malveillants (Shai-Hulud, event-stream, etc.)
9
+ - Detection du typosquatting
10
+ - Analyse AST et dataflow
11
+ - Score de risque 0-100
12
+ - Alertes Discord/Slack
13
+ - Diagnostics inline dans l'editeur
14
+
15
+ ## Installation
16
+
17
+ 1. Installer l'extension depuis VS Code Marketplace
18
+ 2. Ouvrir un projet npm
19
+ 3. Le scan se lance automatiquement
20
+
21
+ ## Commandes
22
+
23
+ - `MUAD'DIB: Scan Project` — Scanner tout le projet
24
+ - `MUAD'DIB: Scan Current File` — Scanner le fichier actuel
25
+
26
+ ## Configuration
27
+
28
+ - `muaddib.autoScan` — Scanner automatiquement (defaut: true)
29
+ - `muaddib.webhookUrl` — URL webhook Discord/Slack
30
+ - `muaddib.failLevel` — Niveau d'alerte (critical/high/medium/low)
31
+
32
+ ## Prerequis
33
+
34
+ - Node.js >= 18
35
+ - muaddib-scanner installe globalement : `npm install -g muaddib-scanner`
36
+ ```
37
+
38
+ Sauvegarde.
39
+
40
+ Ouvre `vscode-extension/.vscodeignore` et mets :
41
+ ```
42
+ .git
43
+ node_modules
44
+ *.md
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "muaddib-vscode",
3
+ "displayName": "MUAD'DIB Security Scanner",
4
+ "description": "Detecte les attaques supply chain npm directement dans VS Code",
5
+ "version": "1.0.0",
6
+ "publisher": "dnszlsk",
7
+ "engines": {
8
+ "vscode": "^1.85.0"
9
+ },
10
+ "categories": [
11
+ "Linters",
12
+ "Other"
13
+ ],
14
+ "keywords": [
15
+ "security",
16
+ "npm",
17
+ "supply-chain",
18
+ "malware",
19
+ "scanner"
20
+ ],
21
+ "activationEvents": [
22
+ "workspaceContains:package.json"
23
+ ],
24
+ "main": "./extension.js",
25
+ "icon": "icon.png",
26
+ "contributes": {
27
+ "commands": [
28
+ {
29
+ "command": "muaddib.scan",
30
+ "title": "MUAD'DIB: Scan Project"
31
+ },
32
+ {
33
+ "command": "muaddib.scanFile",
34
+ "title": "MUAD'DIB: Scan Current File"
35
+ }
36
+ ],
37
+ "configuration": {
38
+ "title": "MUAD'DIB",
39
+ "properties": {
40
+ "muaddib.autoScan": {
41
+ "type": "boolean",
42
+ "default": true,
43
+ "description": "Scanner automatiquement a l'ouverture du projet"
44
+ },
45
+ "muaddib.webhookUrl": {
46
+ "type": "string",
47
+ "default": "",
48
+ "description": "URL du webhook Discord/Slack pour les alertes"
49
+ },
50
+ "muaddib.failLevel": {
51
+ "type": "string",
52
+ "enum": ["critical", "high", "medium", "low"],
53
+ "default": "high",
54
+ "description": "Niveau de severite pour afficher les alertes"
55
+ }
56
+ }
57
+ }
58
+ },
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "https://github.com/DNSZLSK/muad-dib"
62
+ },
63
+ "license": "MIT"
64
+ }