muaddib-scanner 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.muaddib-cache/iocs.json +355 -0
  2. package/package.json +3 -3
  3. package/rapport.html +159 -0
  4. package/src/scanner/ast.js +45 -14
  5. package/src/scanner/dataflow.js +39 -11
  6. package/src/scanner/typosquat.js +97 -140
  7. package/.github/workflows/scan.yml +0 -33
  8. package/CONTRIBUTING.md +0 -98
  9. package/docs/threat-model.md +0 -116
  10. package/test/samples/malicious.js +0 -20
  11. package/tests/run-tests.js +0 -389
  12. package/tests/samples/ast/malicious.js +0 -20
  13. package/tests/samples/clean/safe.js +0 -14
  14. package/tests/samples/dataflow/exfiltration.js +0 -20
  15. package/tests/samples/edge/empty/empty.js +0 -0
  16. package/tests/samples/edge/invalid-syntax/broken.js +0 -5
  17. package/tests/samples/edge/large-file/large.js +0 -6
  18. package/tests/samples/edge/non-js/readme.txt +0 -3
  19. package/tests/samples/markers/shai-hulud.js +0 -10
  20. package/tests/samples/obfuscation/obfuscated.js +0 -1
  21. package/tests/samples/package/package.json +0 -9
  22. package/tests/samples/shell/malicious.sh +0 -13
  23. package/tests/samples/typosquat/package.json +0 -11
  24. package/vscode-extension/.vscode/launch.json +0 -13
  25. package/vscode-extension/.vscodeignore +0 -0
  26. package/vscode-extension/LICENSE +0 -21
  27. package/vscode-extension/README.md +0 -0
  28. package/vscode-extension/extension.js +0 -271
  29. package/vscode-extension/icon.png +0 -0
  30. package/vscode-extension/muaddib-vscode-1.0.0.vsix +0 -0
  31. package/vscode-extension/package.json +0 -69
  32. package/vscode-extension/vscode-extension/README.md +0 -44
  33. package/vscode-extension/vscode-extension/package.json +0 -64
@@ -3,13 +3,24 @@ const path = require('path');
3
3
  const acorn = require('acorn');
4
4
  const walk = require('acorn-walk');
5
5
 
6
- const EXCLUDED_DIRS = ['test', 'tests', 'node_modules', '.git', 'src', 'vscode-extension'];
6
+ const EXCLUDED_DIRS = [
7
+ 'test', 'tests', 'node_modules', '.git', 'src', 'vscode-extension',
8
+ 'scripts', 'bin', 'tools', 'build', 'dist', 'fixtures', 'examples',
9
+ '__tests__', '__mocks__', 'benchmark', 'benchmarks', 'docs', 'doc'
10
+ ];
7
11
 
8
12
  async function analyzeDataFlow(targetPath) {
9
13
  const threats = [];
10
14
  const files = findJsFiles(targetPath);
11
15
 
12
16
  for (const file of files) {
17
+ const relativePath = path.relative(targetPath, file).replace(/\\/g, '/');
18
+
19
+ // Ignorer les fichiers de dev/build/scripts
20
+ if (isDevFile(relativePath)) {
21
+ continue;
22
+ }
23
+
13
24
  const content = fs.readFileSync(file, 'utf8');
14
25
  const fileThreats = analyzeFile(content, file, targetPath);
15
26
  threats.push(...fileThreats);
@@ -18,6 +29,29 @@ async function analyzeDataFlow(targetPath) {
18
29
  return threats;
19
30
  }
20
31
 
32
+ function isDevFile(relativePath) {
33
+ const devPatterns = [
34
+ /^scripts\//,
35
+ /^bin\//,
36
+ /^tools\//,
37
+ /^build\//,
38
+ /^fixtures\//,
39
+ /^examples\//,
40
+ /^__tests__\//,
41
+ /^__mocks__\//,
42
+ /^benchmark/,
43
+ /^docs?\//,
44
+ /^compiler\//,
45
+ /^packages\/.*\/scripts\//,
46
+ /\.test\.js$/,
47
+ /\.spec\.js$/,
48
+ /test\.js$/,
49
+ /spec\.js$/
50
+ ];
51
+
52
+ return devPatterns.some(pattern => pattern.test(relativePath));
53
+ }
54
+
21
55
  function analyzeFile(content, filePath, basePath) {
22
56
  const threats = [];
23
57
  let ast;
@@ -32,15 +66,13 @@ function analyzeFile(content, filePath, basePath) {
32
66
  return threats;
33
67
  }
34
68
 
35
- const sources = []; // Ou les donnees sensibles sont lues
36
- const sinks = []; // Ou les donnees sont envoyees
69
+ const sources = [];
70
+ const sinks = [];
37
71
 
38
72
  walk.simple(ast, {
39
- // Detecte les lectures de fichiers sensibles
40
73
  CallExpression(node) {
41
74
  const callName = getCallName(node);
42
75
 
43
- // fs.readFileSync, fs.readFile
44
76
  if (callName === 'readFileSync' || callName === 'readFile') {
45
77
  const arg = node.arguments[0];
46
78
  if (arg && isCredentialPath(arg, content)) {
@@ -52,7 +84,6 @@ function analyzeFile(content, filePath, basePath) {
52
84
  }
53
85
  }
54
86
 
55
- // Detecte les envois reseau
56
87
  if (callName === 'request' || callName === 'fetch' || callName === 'post' || callName === 'get') {
57
88
  sinks.push({
58
89
  type: 'network_send',
@@ -61,7 +92,6 @@ function analyzeFile(content, filePath, basePath) {
61
92
  });
62
93
  }
63
94
 
64
- // exec avec curl/wget
65
95
  if (callName === 'exec' || callName === 'execSync') {
66
96
  const arg = node.arguments[0];
67
97
  if (arg && arg.type === 'Literal' && typeof arg.value === 'string') {
@@ -76,7 +106,6 @@ function analyzeFile(content, filePath, basePath) {
76
106
  }
77
107
  },
78
108
 
79
- // Detecte les acces process.env sensibles
80
109
  MemberExpression(node) {
81
110
  if (
82
111
  node.object?.object?.name === 'process' &&
@@ -94,7 +123,6 @@ function analyzeFile(content, filePath, basePath) {
94
123
  }
95
124
  });
96
125
 
97
- // Si on a des sources ET des sinks = flux suspect
98
126
  if (sources.length > 0 && sinks.length > 0) {
99
127
  threats.push({
100
128
  type: 'suspicious_dataflow',
@@ -126,7 +154,6 @@ function isCredentialPath(arg, content) {
126
154
  val.includes('.gitconfig') ||
127
155
  val.includes('.env');
128
156
  }
129
- // Verifie aussi les templates strings et concatenations
130
157
  if (arg.type === 'TemplateLiteral' || arg.type === 'BinaryExpression') {
131
158
  return content.includes('.npmrc') ||
132
159
  content.includes('.ssh') ||
@@ -136,7 +163,8 @@ function isCredentialPath(arg, content) {
136
163
  }
137
164
 
138
165
  function isSensitiveEnv(name) {
139
- const sensitive = ['TOKEN', 'SECRET', 'KEY', 'PASSWORD', 'CREDENTIAL', 'AUTH', 'NPM', 'GITHUB', 'AWS', 'AZURE', 'GCP'];
166
+ const sensitive = ['TOKEN', 'SECRET', 'KEY', 'PASSWORD', 'CREDENTIAL', 'AUTH', 'NPM', 'AWS', 'AZURE', 'GCP'];
167
+ // Ignore GITHUB — trop de faux positifs dans les scripts de release
140
168
  return sensitive.some(s => name.toUpperCase().includes(s));
141
169
  }
142
170
 
@@ -7,57 +7,63 @@ 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
+ '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
- 'twilio', 'firebase', 'graphql', 'apollo-server', 'next', 'nuxt',
14
- 'gatsby', 'vue', 'angular', 'svelte', 'electron', 'puppeteer', 'cheerio',
13
+ 'twilio', 'firebase', 'graphql', 'apollo-server', 'nuxt',
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', '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', 'next',
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',
46
+
47
+ // Packages CLI et outils legitimes
48
+ 'jest-cli', 'prettier-2', 'prettier-1', 'eslint-cli',
49
+ 'inquirer', 'enquirer', 'prompts',
50
+ 'mysql2', 'pg-native', 'sqlite3', 'better-sqlite3',
51
+ 'node-sass', 'sass', 'less',
52
+ 'esbuild', 'rollup', 'parcel', 'vite',
53
+ 'husky', 'lint-staged', 'commitlint',
54
+ 'nodemon', 'pm2', 'forever', 'concurrently',
55
+ 'lerna', 'turbo', 'nx',
56
+ 'chalk', 'colors', 'picocolors', 'colorette',
57
+ 'commander', 'yargs', 'meow', 'cac',
58
+ 'execa', 'shelljs', 'cross-spawn',
59
+ 'rimraf', 'del', 'trash-cli',
60
+ 'globby', 'fast-glob', 'tiny-glob',
61
+ 'chokidar', 'watchpack', 'nsfw',
62
+ 'dotenv', 'dotenv-expand', 'env-cmd'
50
63
  ];
51
64
 
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
- ];
65
+ // Seuil minimum de longueur pour eviter faux positifs
66
+ const MIN_PACKAGE_LENGTH = 4;
61
67
 
62
68
  async function scanTyposquatting(targetPath) {
63
69
  const threats = [];
@@ -97,19 +103,30 @@ async function scanTyposquatting(targetPath) {
97
103
  }
98
104
 
99
105
  function findTyposquatMatch(name) {
106
+ const nameLower = name.toLowerCase();
107
+
100
108
  // Ignore les packages whitelistes
101
- if (WHITELIST.includes(name)) return null;
109
+ if (WHITELIST.includes(nameLower)) return null;
102
110
 
103
111
  // Ignore les packages scoped (@org/package)
104
112
  if (name.startsWith('@')) return null;
105
113
 
114
+ // Ignore les packages tres courts (trop de faux positifs)
115
+ if (name.length < MIN_PACKAGE_LENGTH) return null;
116
+
117
+ // Ignore les packages avec suffixes legitimes courants
118
+ if (isLegitimateVariant(nameLower)) return null;
119
+
106
120
  for (const popular of POPULAR_PACKAGES) {
107
121
  // Ignore si c'est exactement le meme
108
- if (name === popular) continue;
122
+ if (nameLower === popular.toLowerCase()) continue;
109
123
 
110
- const distance = levenshteinDistance(name, popular);
124
+ // Ignore si le package populaire est trop court
125
+ if (popular.length < MIN_PACKAGE_LENGTH) continue;
126
+
127
+ const distance = levenshteinDistance(nameLower, popular.toLowerCase());
111
128
 
112
- // Distance de 1 ou 2 = tres suspect
129
+ // Distance de 1 = tres suspect (une seule lettre de difference)
113
130
  if (distance === 1) {
114
131
  return {
115
132
  original: popular,
@@ -118,33 +135,53 @@ function findTyposquatMatch(name) {
118
135
  };
119
136
  }
120
137
 
121
- // Distance de 2 avec nom court = suspect
122
- if (distance === 2 && popular.length <= 6) {
138
+ // Distance de 2 seulement si le package est assez long (>= 8 chars)
139
+ if (distance === 2 && popular.length >= 8) {
123
140
  return {
124
141
  original: popular,
125
142
  type: detectTyposquatType(name, popular),
126
143
  distance: distance
127
144
  };
128
145
  }
129
-
130
- // Verifie les tricks de suffixe
131
- if (isSuffixTrick(name, popular)) {
132
- return {
133
- original: popular,
134
- type: 'suffix_trick',
135
- distance: distance
136
- };
137
- }
138
146
  }
139
147
 
140
148
  return null;
141
149
  }
142
150
 
151
+ function isLegitimateVariant(name) {
152
+ // Suffixes legitimes qui ne sont PAS du typosquatting
153
+ const legitimateSuffixes = [
154
+ '-cli', '-core', '-utils', '-plugin', '-loader', '-webpack',
155
+ '-react', '-vue', '-angular', '-node', '-browser',
156
+ '-esm', '-cjs', '-umd',
157
+ '-types', '-typings',
158
+ '2', '3', '4', '5', // versions majeures (mysql2, etc)
159
+ '-v2', '-v3', '-next', '-latest', '-stable', '-lts'
160
+ ];
161
+
162
+ for (const suffix of legitimateSuffixes) {
163
+ if (name.endsWith(suffix)) return true;
164
+ }
165
+
166
+ // Prefixes legitimes
167
+ const legitimatePrefixes = [
168
+ '@types/', '@babel/', '@jest/', '@testing-library/',
169
+ 'eslint-plugin-', 'eslint-config-',
170
+ 'babel-plugin-', 'babel-preset-',
171
+ 'webpack-plugin-', 'rollup-plugin-', 'vite-plugin-'
172
+ ];
173
+
174
+ for (const prefix of legitimatePrefixes) {
175
+ if (name.startsWith(prefix)) return true;
176
+ }
177
+
178
+ return false;
179
+ }
180
+
143
181
  function detectTyposquatType(typo, original) {
144
182
  if (typo.length === original.length - 1) return 'missing_char';
145
183
  if (typo.length === original.length + 1) return 'extra_char';
146
184
  if (typo.length === original.length) {
147
- // Verifie si swap
148
185
  let diffs = 0;
149
186
  for (let i = 0; i < typo.length; i++) {
150
187
  if (typo[i] !== original[i]) diffs++;
@@ -155,20 +192,6 @@ function detectTyposquatType(typo, original) {
155
192
  return 'unknown';
156
193
  }
157
194
 
158
- function isSuffixTrick(name, popular) {
159
- const suffixes = ['-js', '.js', '-node', '-npm', '-cli', '-api', '-lib', '-pkg', '-dev', '-pro'];
160
- for (const suffix of suffixes) {
161
- if (name === popular + suffix) return true;
162
- if (name === popular.replace('-', '') + suffix) return true;
163
- }
164
- // Verifie aussi les prefixes
165
- const prefixes = ['node-', 'npm-', 'js-', 'get-', 'the-'];
166
- for (const prefix of prefixes) {
167
- if (name === prefix + popular) return true;
168
- }
169
- return false;
170
- }
171
-
172
195
  function levenshteinDistance(a, b) {
173
196
  const matrix = [];
174
197
 
@@ -186,9 +209,9 @@ function levenshteinDistance(a, b) {
186
209
  matrix[i][j] = matrix[i - 1][j - 1];
187
210
  } else {
188
211
  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
212
+ matrix[i - 1][j - 1] + 1,
213
+ matrix[i][j - 1] + 1,
214
+ matrix[i - 1][j] + 1
192
215
  );
193
216
  }
194
217
  }
@@ -197,70 +220,4 @@ function levenshteinDistance(a, b) {
197
220
  return matrix[b.length][a.length];
198
221
  }
199
222
 
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
223
  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