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 +14 -4
- package/package.json +3 -2
- package/src/daemon.js +142 -0
- package/src/index.js +39 -5
- package/src/scanner/typosquat.js +31 -0
- package/src/webhook.js +176 -0
- package/vscode-extension/.vscode/launch.json +13 -0
- package/vscode-extension/.vscodeignore +0 -0
- package/vscode-extension/README.md +0 -0
- package/vscode-extension/extension.js +264 -0
- package/vscode-extension/icon.png +0 -0
- package/vscode-extension/package.json +64 -0
- package/vscode-extension/vscode-extension/README.md +44 -0
- package/vscode-extension/vscode-extension/package.json +64 -0
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';
|
|
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.
|
|
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:
|
|
63
|
-
high:
|
|
64
|
-
medium:
|
|
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
|
-
|
|
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
|
-
//
|
|
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'],
|
package/src/scanner/typosquat.js
CHANGED
|
@@ -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 };
|
|
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
|
+
}
|