muaddib-scanner 1.0.21 → 1.1.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.
package/bin/muaddib.js CHANGED
@@ -1,15 +1,16 @@
1
- #!/usr/bin/env node
2
-
1
+ #!/usr/bin/env node
3
2
  const { run } = require('../src/index.js');
4
3
  const { updateIOCs } = require('../src/ioc/updater.js');
5
4
  const { watch } = require('../src/watch.js');
6
5
  const { startDaemon } = require('../src/daemon.js');
7
6
  const { runScraper } = require('../src/ioc/scraper.js');
7
+ const { safeInstall } = require('../src/safe-install.js');
8
8
 
9
9
  const args = process.argv.slice(2);
10
10
  const command = args[0];
11
11
  const options = args.slice(1);
12
12
 
13
+ // Parse options
13
14
  let target = '.';
14
15
  let jsonOutput = false;
15
16
  let htmlOutput = null;
@@ -43,35 +44,133 @@ for (let i = 0; i < options.length; i++) {
43
44
  }
44
45
  }
45
46
 
46
- if (!command) {
47
+ // Menu interactif si pas de commande
48
+ async function interactiveMenu() {
49
+ const { select, input, confirm } = await import('@inquirer/prompts');
50
+
47
51
  console.log(`
52
+ ╔══════════════════════════════════════════╗
53
+ ║ MUAD'DIB - npm Supply Chain Hunter ║
54
+ ║ "The worms must die." ║
55
+ ╚══════════════════════════════════════════╝
56
+ `);
57
+
58
+ const action = await select({
59
+ message: 'Que veux-tu faire ?',
60
+ choices: [
61
+ { name: 'Scanner un projet', value: 'scan' },
62
+ { name: 'Scanner avec mode paranoid', value: 'scan-paranoid' },
63
+ { name: 'Surveiller un projet (watch)', value: 'watch' },
64
+ { name: 'Lancer le daemon', value: 'daemon' },
65
+ { name: 'Mettre a jour les IOCs', value: 'update' },
66
+ { name: 'Scraper nouveaux IOCs', value: 'scrape' },
67
+ { name: 'Quitter', value: 'quit' }
68
+ ]
69
+ });
70
+
71
+ if (action === 'quit') {
72
+ console.log('Bye!');
73
+ process.exit(0);
74
+ }
75
+
76
+ if (action === 'scan' || action === 'scan-paranoid') {
77
+ const path = await input({
78
+ message: 'Chemin du projet :',
79
+ default: '.'
80
+ });
81
+
82
+ const outputFormat = await select({
83
+ message: 'Format de sortie :',
84
+ choices: [
85
+ { name: 'Console (defaut)', value: 'console' },
86
+ { name: 'JSON', value: 'json' },
87
+ { name: 'HTML', value: 'html' },
88
+ { name: 'SARIF (GitHub Security)', value: 'sarif' }
89
+ ]
90
+ });
91
+
92
+ const opts = {
93
+ json: outputFormat === 'json',
94
+ html: outputFormat === 'html' ? 'muaddib-report.html' : null,
95
+ sarif: outputFormat === 'sarif' ? 'muaddib-results.sarif' : null,
96
+ explain: true,
97
+ failLevel: 'high',
98
+ paranoid: action === 'scan-paranoid'
99
+ };
100
+
101
+ const exitCode = await run(path, opts);
102
+ process.exit(exitCode);
103
+ }
104
+
105
+ if (action === 'watch') {
106
+ const path = await input({
107
+ message: 'Chemin du projet :',
108
+ default: '.'
109
+ });
110
+ watch(path);
111
+ }
112
+
113
+ if (action === 'daemon') {
114
+ const useWebhook = await confirm({
115
+ message: 'Configurer un webhook Discord/Slack ?',
116
+ default: false
117
+ });
118
+
119
+ let webhook = null;
120
+ if (useWebhook) {
121
+ webhook = await input({
122
+ message: 'URL du webhook :'
123
+ });
124
+ }
125
+ startDaemon({ webhook });
126
+ }
127
+
128
+ if (action === 'update') {
129
+ await updateIOCs();
130
+ process.exit(0);
131
+ }
132
+
133
+ if (action === 'scrape') {
134
+ const result = await runScraper();
135
+ console.log(`[OK] ${result.added} nouveaux IOCs (total: ${result.total})`);
136
+ process.exit(0);
137
+ }
138
+ }
139
+
140
+ // Main
141
+ if (!command || command === '--help' || command === '-h') {
142
+ if (command === '--help' || command === '-h') {
143
+ console.log(`
48
144
  MUAD'DIB - npm Supply Chain Threat Hunter
49
145
 
50
146
  Usage:
51
- muaddib scan [path] [options] Scan a project
52
- muaddib watch [path] Watch a project in real-time
53
- muaddib daemon [options] Start background daemon
54
- muaddib update Update IOCs
55
- muaddib scrape Scrape new IOCs from advisories
56
- muaddib help Show help
57
-
147
+ muaddib Mode interactif
148
+ muaddib scan [path] [options] Scanner un projet
149
+ muaddib watch [path] Surveiller en temps reel
150
+ muaddib daemon [options] Lancer le daemon
151
+ muaddib update Mettre a jour les IOCs
152
+ muaddib scrape Scraper nouveaux IOCs
153
+ muaddib install <pkg> Installer apres scan (safe)
154
+
58
155
  Options:
59
- --json Output as JSON
60
- --html [file] Generate HTML report
61
- --sarif [file] Generate SARIF report (GitHub Security)
62
- --explain Show detailed explanations
63
- --fail-on [level] Severity level for exit code (critical|high|medium|low)
64
- Default: high (fail on HIGH and CRITICAL)
65
- --webhook [url] Send Discord/Slack alert
66
- --paranoid Enable ultra-strict rules (more false positives)
67
- `);
68
- process.exit(0);
69
- }
70
-
71
- if (command === 'scan') {
72
- run(target, {
73
- json: jsonOutput,
74
- html: htmlOutput,
156
+ --json Sortie JSON
157
+ --html [file] Rapport HTML
158
+ --sarif [file] Rapport SARIF (GitHub Security)
159
+ --explain Explications detaillees
160
+ --fail-on [level] Niveau d'echec (critical|high|medium|low)
161
+ --webhook [url] Webhook Discord/Slack
162
+ --paranoid Mode ultra-strict
163
+ `);
164
+ process.exit(0);
165
+ }
166
+ interactiveMenu().catch(err => {
167
+ console.error('[ERROR]', err.message);
168
+ process.exit(1);
169
+ });
170
+ } else if (command === 'scan') {
171
+ run(target, {
172
+ json: jsonOutput,
173
+ html: htmlOutput,
75
174
  sarif: sarifOutput,
76
175
  explain: explainMode,
77
176
  failLevel: failLevel,
@@ -91,7 +190,7 @@ if (command === 'scan') {
91
190
  });
92
191
  } else if (command === 'scrape') {
93
192
  runScraper().then(result => {
94
- console.log(`[OK] ${result.added} new IOCs added (total: ${result.total})`);
193
+ console.log(`[OK] ${result.added} nouveaux IOCs (total: ${result.total})`);
95
194
  process.exit(0);
96
195
  }).catch(err => {
97
196
  console.error('[ERROR]', err.message);
@@ -99,13 +198,50 @@ if (command === 'scan') {
99
198
  });
100
199
  } else if (command === 'daemon') {
101
200
  startDaemon({ webhook: webhookUrl });
201
+ } else if (command === 'install' || command === 'i') {
202
+ const packages = options.filter(o => !o.startsWith('-'));
203
+ const isDev = options.includes('--save-dev') || options.includes('-D');
204
+ const isGlobal = options.includes('-g') || options.includes('--global');
205
+ const force = options.includes('--force');
206
+
207
+ if (packages.length === 0) {
208
+ console.log('Usage: muaddib install <package> [<package>...] [--save-dev] [-g] [--force]');
209
+ process.exit(1);
210
+ }
211
+
212
+ safeInstall(packages, { isDev, isGlobal, force }).then(result => {
213
+ if (result.blocked && !force) {
214
+ process.exit(1);
215
+ }
216
+ process.exit(0);
217
+ }).catch(err => {
218
+ console.error('[ERROR]', err.message);
219
+ process.exit(1);
220
+ });
102
221
  } else if (command === 'help') {
103
- console.log('muaddib scan [path] [--json] [--html file] [--sarif file] [--explain] [--fail-on level] [--webhook url] [--paranoid]');
104
- console.log('muaddib watch [path] - Watch a project in real-time');
105
- console.log('muaddib daemon [--webhook url] - Start background daemon');
106
- console.log('muaddib update - Update IOCs');
107
- console.log('muaddib scrape - Scrape new IOCs');
222
+ console.log(`
223
+ MUAD'DIB - npm Supply Chain Threat Hunter
224
+
225
+ Usage:
226
+ muaddib Mode interactif
227
+ muaddib scan [path] [options] Scanner un projet
228
+ muaddib watch [path] Surveiller en temps reel
229
+ muaddib daemon [options] Lancer le daemon
230
+ muaddib update Mettre a jour les IOCs
231
+ muaddib scrape Scraper nouveaux IOCs
232
+
233
+ Options:
234
+ --json Sortie JSON
235
+ --html [file] Rapport HTML
236
+ --sarif [file] Rapport SARIF (GitHub Security)
237
+ --explain Explications detaillees
238
+ --fail-on [level] Niveau d'echec (critical|high|medium|low)
239
+ --webhook [url] Webhook Discord/Slack
240
+ --paranoid Mode ultra-strict
241
+ `);
242
+ process.exit(0);
108
243
  } else {
109
- console.log(`Unknown command: ${command}`);
244
+ console.log(`Commande inconnue: ${command}`);
245
+ console.log('Tape "muaddib help" pour voir les commandes.');
110
246
  process.exit(1);
111
247
  }
package/data/iocs.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "1.1.0",
3
- "updated": "2026-01-08T14:00:38.388Z",
3
+ "updated": "2026-01-08T21:13:53.854Z",
4
4
  "description": "IOCs communautaires MUAD'DIB - Contribuez via PR",
5
5
  "packages": [
6
6
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "1.0.21",
3
+ "version": "1.1.0",
4
4
  "description": "Supply-chain threat detection & response for npm",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -36,9 +36,15 @@
36
36
  "node": ">=18.0.0"
37
37
  },
38
38
  "dependencies": {
39
+ "@inquirer/prompts": "^8.1.0",
40
+ "@vietmoney/react-big-calendar": "^0.0.1-security",
39
41
  "acorn": "^8.14.0",
40
42
  "acorn-walk": "^8.3.4",
43
+ "chalk": "^5.6.2",
44
+ "express": "^5.2.1",
45
+ "is-odd": "^3.0.1",
41
46
  "js-yaml": "^4.1.0",
42
- "lodash": "^4.17.21"
47
+ "lodash": "^4.17.21",
48
+ "yargs": "^18.0.0"
43
49
  }
44
50
  }
@@ -0,0 +1,163 @@
1
+ const { execSync } = require('child_process');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const { loadCachedIOCs } = require('./ioc/updater.js');
5
+
6
+ // Packages connus sûrs qui utilisent des patterns "suspects" légitimement
7
+ const TRUSTED_PACKAGES = [
8
+ 'lodash', 'underscore', 'express', 'react', 'vue', 'angular',
9
+ 'webpack', 'babel', 'typescript', 'esbuild', 'vite', 'rollup',
10
+ 'jest', 'mocha', 'chai', 'sharp', 'bcrypt', 'argon2'
11
+ ];
12
+
13
+ // Cache pour eviter de scanner deux fois le meme package
14
+ const scannedPackages = new Set();
15
+
16
+ // Verifier si un package est dans les IOCs
17
+ function checkIOCs(pkg, pkgName) {
18
+ try {
19
+ const iocs = loadCachedIOCs();
20
+ return iocs.packages?.find(p => p.name === pkg || p.name === pkgName);
21
+ } catch (e) {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ // Scanner un package et ses dependances recursivement
27
+ async function scanPackageRecursive(pkg, depth = 0, maxDepth = 3) {
28
+ const indent = ' '.repeat(depth);
29
+ const pkgName = pkg.replace(/^@[^/]+\//, '').split('@')[0];
30
+
31
+ // Eviter les boucles infinies
32
+ if (scannedPackages.has(pkg) || scannedPackages.has(pkgName)) {
33
+ return { safe: true };
34
+ }
35
+ scannedPackages.add(pkg);
36
+ scannedPackages.add(pkgName);
37
+
38
+ // Skip trusted packages
39
+ if (TRUSTED_PACKAGES.includes(pkgName)) {
40
+ if (depth === 0) console.log(`[OK] ${pkg} - Package de confiance`);
41
+ return { safe: true };
42
+ }
43
+
44
+ // Limiter la profondeur
45
+ if (depth > maxDepth) {
46
+ return { safe: true };
47
+ }
48
+
49
+ if (depth === 0) {
50
+ console.log(`[*] Analyse de ${pkg}...`);
51
+ } else {
52
+ console.log(`${indent}[*] Dependance: ${pkg}`);
53
+ }
54
+
55
+ // Verifier IOCs
56
+ const malicious = checkIOCs(pkg, pkgName);
57
+ if (malicious) {
58
+ return {
59
+ safe: false,
60
+ package: pkg,
61
+ reason: 'known_malicious',
62
+ source: malicious.source || 'IOC Database',
63
+ description: malicious.description || 'Package malveillant connu',
64
+ depth
65
+ };
66
+ }
67
+
68
+ // Recuperer les infos du package
69
+ let pkgInfo;
70
+ try {
71
+ const infoRaw = execSync(`npm view ${pkg} --json 2>nul`, { encoding: 'utf8' });
72
+ pkgInfo = JSON.parse(infoRaw);
73
+ } catch (e) {
74
+ if (depth === 0) console.log(`[!] Package ${pkg} introuvable sur npm`);
75
+ return { safe: true };
76
+ }
77
+
78
+ // Scanner les dependances
79
+ const dependencies = pkgInfo.dependencies || {};
80
+ const depNames = Object.keys(dependencies);
81
+
82
+ if (depNames.length > 0 && depth < maxDepth) {
83
+ for (const depName of depNames) {
84
+ const depVersion = dependencies[depName];
85
+ const depPkg = depName; // On check juste le nom, pas la version specifique
86
+
87
+ const result = await scanPackageRecursive(depPkg, depth + 1, maxDepth);
88
+ if (!result.safe) {
89
+ return result;
90
+ }
91
+ }
92
+ }
93
+
94
+ if (depth === 0) {
95
+ console.log(`[OK] ${pkg} - Aucune menace (${depNames.length} dependances scannees)`);
96
+ }
97
+
98
+ return { safe: true };
99
+ }
100
+
101
+ async function safeInstall(packages, options = {}) {
102
+ const { isDev, isGlobal, force } = options;
103
+
104
+ console.log(`
105
+ ╔══════════════════════════════════════════╗
106
+ ║ MUAD'DIB Safe Install ║
107
+ ║ Scanning packages + dependencies... ║
108
+ ╚══════════════════════════════════════════╝
109
+ `);
110
+
111
+ // Reset le cache pour chaque install
112
+ scannedPackages.clear();
113
+
114
+ try {
115
+ for (const pkg of packages) {
116
+ const result = await scanPackageRecursive(pkg);
117
+
118
+ if (!result.safe) {
119
+ console.log(`
120
+ ╔══════════════════════════════════════════╗
121
+ ║ [!] PACKAGE MALVEILLANT DETECTE ║
122
+ ╚══════════════════════════════════════════╝
123
+ `);
124
+ if (result.depth > 0) {
125
+ console.log(`Package demande: ${pkg}`);
126
+ console.log(`Dependance malveillante: ${result.package} (profondeur: ${result.depth})`);
127
+ } else {
128
+ console.log(`Package: ${result.package}`);
129
+ }
130
+ console.log(`Source: ${result.source}`);
131
+ console.log(`Raison: ${result.description}`);
132
+ console.log('');
133
+
134
+ if (!force) {
135
+ console.log('[!] Installation BLOQUEE.');
136
+ return { blocked: true, package: result.package, threats: [{ type: 'known_malicious', severity: 'CRITICAL', message: result.description }] };
137
+ } else {
138
+ console.log('[!] --force active, installation malgre les menaces...');
139
+ }
140
+ }
141
+ }
142
+
143
+ // Tout est clean, installer pour de vrai
144
+ console.log('');
145
+ console.log('[*] Installation en cours...');
146
+
147
+ let cmd = `npm install ${packages.join(' ')}`;
148
+ if (isDev) cmd += ' --save-dev';
149
+ if (isGlobal) cmd += ' -g';
150
+
151
+ execSync(cmd, { stdio: 'inherit' });
152
+
153
+ console.log('');
154
+ console.log('[OK] Installation terminee.');
155
+
156
+ return { blocked: false };
157
+
158
+ } catch (e) {
159
+ throw e;
160
+ }
161
+ }
162
+
163
+ module.exports = { safeInstall };