muaddib-scanner 2.4.13 → 2.4.15

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.
@@ -1,224 +1,224 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { loadCachedIOCs } = require('../ioc/updater.js');
4
- const { REHABILITATED_PACKAGES } = require('../shared/constants.js');
5
-
6
- // Packages legitimes avec lifecycle scripts (ne pas alerter)
7
- const TRUSTED_PACKAGES = [
8
- 'esbuild', 'sharp', 'bcrypt', 'node-sass', 'puppeteer',
9
- 'playwright', 'sqlite3', 'better-sqlite3', 'canvas',
10
- 'grpc', 'fsevents', 'msgpackr-extract', 'lmdb', 'parcel',
11
- 'electron', 'node-gyp', 'prebuild-install', 'nan'
12
- ];
13
-
14
- // Fichiers legitimes qui ressemblent a des fichiers suspects
15
- const SAFE_FILES = {
16
- 'inject.js': ['async', 'awilix', 'inversify', 'bottlejs'],
17
- 'install.js': ['esbuild', 'sharp', 'bcrypt', 'node-sass', 'puppeteer', 'playwright', 'electron']
18
- };
19
-
20
- /**
21
- * Verifie si un package est dans la whitelist des packages rehabilites
22
- * @returns {boolean|null} true = safe, false = compromis, null = pas dans whitelist
23
- */
24
- function checkRehabilitatedPackage(pkgName, pkgVersion) {
25
- const rehab = REHABILITATED_PACKAGES[pkgName];
26
- if (!rehab) return null; // Pas dans la whitelist
27
-
28
- // Si marque comme safe = toutes versions sont OK
29
- if (rehab.safe === true) return true;
30
-
31
- // Sinon, verifier si la version est dans la liste des compromises
32
- if (rehab.compromised && rehab.compromised.includes(pkgVersion)) {
33
- return false; // Version specifiquement compromise
34
- }
35
-
36
- return true; // Version pas dans la liste des compromises = safe
37
- }
38
-
39
- async function scanDependencies(targetPath) {
40
- const threats = [];
41
- const nodeModulesPath = path.join(targetPath, 'node_modules');
42
- const iocs = loadCachedIOCs();
43
-
44
- if (!fs.existsSync(nodeModulesPath)) {
45
- return threats;
46
- }
47
-
48
- const packages = listPackages(nodeModulesPath);
49
-
50
- // Pre-compute files and markers lists once (outside the loop)
51
- const suspiciousFilesRaw = iocs.filesSet || iocs.files || [];
52
- const filesToCheck = suspiciousFilesRaw instanceof Set
53
- ? Array.from(suspiciousFilesRaw)
54
- : suspiciousFilesRaw;
55
-
56
- const markersRaw = iocs.markersSet || iocs.markers || [];
57
- const markersToCheck = markersRaw instanceof Set
58
- ? Array.from(markersRaw)
59
- : markersRaw;
60
-
61
- for (const pkg of packages) {
62
- // D'abord verifier la whitelist des packages rehabilites
63
- const rehabStatus = checkRehabilitatedPackage(pkg.name, pkg.version);
64
-
65
- if (rehabStatus === true) {
66
- // Package rehabilite et version safe, skip
67
- continue;
68
- }
69
-
70
- if (rehabStatus === false) {
71
- // Package rehabilite mais version specifiquement compromise
72
- const rehab = REHABILITATED_PACKAGES[pkg.name];
73
- threats.push({
74
- type: 'known_malicious_package',
75
- severity: 'CRITICAL',
76
- message: `Version compromise: ${pkg.name}@${pkg.version} (${rehab.note})`,
77
- file: `node_modules/${pkg.name}`,
78
- source: 'rehabilitated'
79
- });
80
- continue;
81
- }
82
-
83
- // rehabStatus === null : pas dans whitelist, continuer verification normale
84
-
85
- // Verifie si package connu malveillant (IOCs caches) AVEC VERSION
86
- // Utilise Map/Set pour lookup O(1) au lieu de O(n)
87
- let maliciousPkg = null;
88
-
89
- // Check 1: Package avec wildcard (toutes versions malveillantes)
90
- if (iocs.wildcardPackages && iocs.wildcardPackages.has(pkg.name) && iocs.packagesMap) {
91
- const pkgList = iocs.packagesMap.get(pkg.name);
92
- maliciousPkg = pkgList ? pkgList.find(p => p.version === '*') : null;
93
- }
94
- // Check 2: Version specifique via Map
95
- else if (iocs.packagesMap && iocs.packagesMap.has(pkg.name)) {
96
- const pkgList = iocs.packagesMap.get(pkg.name);
97
- maliciousPkg = pkgList ? pkgList.find(p => p.version === pkg.version) : null;
98
- }
99
- // Fallback: recherche lineaire (compatibilite ancienne API)
100
- else if (!iocs.packagesMap) {
101
- maliciousPkg = iocs.packages.find(p => {
102
- if (p.name !== pkg.name) return false;
103
- if (p.version === '*') return true;
104
- return p.version === pkg.version;
105
- });
106
- }
107
-
108
- if (maliciousPkg) {
109
- threats.push({
110
- type: 'known_malicious_package',
111
- severity: 'CRITICAL',
112
- message: `Package malveillant connu: ${pkg.name}@${maliciousPkg.version} (source: ${maliciousPkg.source})`,
113
- file: `node_modules/${pkg.name}`
114
- });
115
- continue;
116
- }
117
-
118
- // Skip trusted packages pour les checks suivants
119
- if (TRUSTED_PACKAGES.includes(pkg.name)) continue;
120
-
121
- // Verifie les fichiers suspects (IOCs caches) avec whitelist
122
- for (const suspFile of filesToCheck) {
123
- // Skip si fichier legitime pour ce package
124
- if (SAFE_FILES[suspFile] && SAFE_FILES[suspFile].includes(pkg.name)) {
125
- continue;
126
- }
127
-
128
- const filePath = path.join(pkg.path, suspFile);
129
- if (fs.existsSync(filePath)) {
130
- threats.push({
131
- type: 'suspicious_file',
132
- severity: 'HIGH',
133
- message: `Fichier suspect "${suspFile}" dans ${pkg.name}`,
134
- file: `node_modules/${pkg.name}/${suspFile}`
135
- });
136
- }
137
- }
138
-
139
- // Verifie les lifecycle scripts
140
- const pkgJsonPath = path.join(pkg.path, 'package.json');
141
- if (fs.existsSync(pkgJsonPath)) {
142
- try {
143
- const pkgContent = fs.readFileSync(pkgJsonPath, 'utf8');
144
-
145
- // Verifie les marqueurs Shai-Hulud
146
- for (const marker of markersToCheck) {
147
- if (pkgContent.includes(marker)) {
148
- threats.push({
149
- type: 'shai_hulud_marker',
150
- severity: 'CRITICAL',
151
- message: `Marqueur "${marker}" detecte dans ${pkg.name}`,
152
- file: `node_modules/${pkg.name}/package.json`
153
- });
154
- }
155
- }
156
- } catch {
157
- // JSON parse error, skip
158
- }
159
- }
160
- }
161
-
162
- return threats;
163
- }
164
-
165
- function listPackages(nodeModulesPath) {
166
- const packages = [];
167
- const items = fs.readdirSync(nodeModulesPath);
168
-
169
- for (const item of items) {
170
- if (item.startsWith('.')) continue;
171
-
172
- const itemPath = path.join(nodeModulesPath, item);
173
-
174
- try {
175
- const stat = fs.lstatSync(itemPath);
176
- if (stat.isSymbolicLink()) continue;
177
- if (!stat.isDirectory()) continue;
178
-
179
- if (item.startsWith('@')) {
180
- const scopedItems = fs.readdirSync(itemPath);
181
- for (const scopedItem of scopedItems) {
182
- const scopedPath = path.join(itemPath, scopedItem);
183
- const scopedStat = fs.lstatSync(scopedPath);
184
- if (scopedStat.isSymbolicLink()) continue;
185
- if (scopedStat.isDirectory()) {
186
- const version = getPackageVersion(scopedPath);
187
- packages.push({
188
- name: `${item}/${scopedItem}`,
189
- path: scopedPath,
190
- version: version
191
- });
192
- }
193
- }
194
- } else {
195
- const version = getPackageVersion(itemPath);
196
- packages.push({
197
- name: item,
198
- path: itemPath,
199
- version: version
200
- });
201
- }
202
- } catch {
203
- // Skip inaccessible
204
- }
205
- }
206
-
207
- return packages;
208
- }
209
-
210
- function getPackageVersion(pkgPath) {
211
- try {
212
- const pkgJson = JSON.parse(fs.readFileSync(path.join(pkgPath, 'package.json'), 'utf8'));
213
- return pkgJson.version || '*';
214
- } catch {
215
- return '*';
216
- }
217
- }
218
-
219
- module.exports = {
220
- scanDependencies,
221
- checkRehabilitatedPackage,
222
- TRUSTED_PACKAGES,
223
- SAFE_FILES
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { loadCachedIOCs } = require('../ioc/updater.js');
4
+ const { REHABILITATED_PACKAGES } = require('../shared/constants.js');
5
+
6
+ // Packages legitimes avec lifecycle scripts (ne pas alerter)
7
+ const TRUSTED_PACKAGES = [
8
+ 'esbuild', 'sharp', 'bcrypt', 'node-sass', 'puppeteer',
9
+ 'playwright', 'sqlite3', 'better-sqlite3', 'canvas',
10
+ 'grpc', 'fsevents', 'msgpackr-extract', 'lmdb', 'parcel',
11
+ 'electron', 'node-gyp', 'prebuild-install', 'nan'
12
+ ];
13
+
14
+ // Fichiers legitimes qui ressemblent a des fichiers suspects
15
+ const SAFE_FILES = {
16
+ 'inject.js': ['async', 'awilix', 'inversify', 'bottlejs'],
17
+ 'install.js': ['esbuild', 'sharp', 'bcrypt', 'node-sass', 'puppeteer', 'playwright', 'electron']
18
+ };
19
+
20
+ /**
21
+ * Verifie si un package est dans la whitelist des packages rehabilites
22
+ * @returns {boolean|null} true = safe, false = compromis, null = pas dans whitelist
23
+ */
24
+ function checkRehabilitatedPackage(pkgName, pkgVersion) {
25
+ const rehab = REHABILITATED_PACKAGES[pkgName];
26
+ if (!rehab) return null; // Pas dans la whitelist
27
+
28
+ // Si marque comme safe = toutes versions sont OK
29
+ if (rehab.safe === true) return true;
30
+
31
+ // Sinon, verifier si la version est dans la liste des compromises
32
+ if (rehab.compromised && rehab.compromised.includes(pkgVersion)) {
33
+ return false; // Version specifiquement compromise
34
+ }
35
+
36
+ return true; // Version pas dans la liste des compromises = safe
37
+ }
38
+
39
+ async function scanDependencies(targetPath) {
40
+ const threats = [];
41
+ const nodeModulesPath = path.join(targetPath, 'node_modules');
42
+ const iocs = loadCachedIOCs();
43
+
44
+ if (!fs.existsSync(nodeModulesPath)) {
45
+ return threats;
46
+ }
47
+
48
+ const packages = listPackages(nodeModulesPath);
49
+
50
+ // Pre-compute files and markers lists once (outside the loop)
51
+ const suspiciousFilesRaw = iocs.filesSet || iocs.files || [];
52
+ const filesToCheck = suspiciousFilesRaw instanceof Set
53
+ ? Array.from(suspiciousFilesRaw)
54
+ : suspiciousFilesRaw;
55
+
56
+ const markersRaw = iocs.markersSet || iocs.markers || [];
57
+ const markersToCheck = markersRaw instanceof Set
58
+ ? Array.from(markersRaw)
59
+ : markersRaw;
60
+
61
+ for (const pkg of packages) {
62
+ // D'abord verifier la whitelist des packages rehabilites
63
+ const rehabStatus = checkRehabilitatedPackage(pkg.name, pkg.version);
64
+
65
+ if (rehabStatus === true) {
66
+ // Package rehabilite et version safe, skip
67
+ continue;
68
+ }
69
+
70
+ if (rehabStatus === false) {
71
+ // Package rehabilite mais version specifiquement compromise
72
+ const rehab = REHABILITATED_PACKAGES[pkg.name];
73
+ threats.push({
74
+ type: 'known_malicious_package',
75
+ severity: 'CRITICAL',
76
+ message: `Version compromise: ${pkg.name}@${pkg.version} (${rehab.note})`,
77
+ file: `node_modules/${pkg.name}`,
78
+ source: 'rehabilitated'
79
+ });
80
+ continue;
81
+ }
82
+
83
+ // rehabStatus === null : pas dans whitelist, continuer verification normale
84
+
85
+ // Verifie si package connu malveillant (IOCs caches) AVEC VERSION
86
+ // Utilise Map/Set pour lookup O(1) au lieu de O(n)
87
+ let maliciousPkg = null;
88
+
89
+ // Check 1: Package avec wildcard (toutes versions malveillantes)
90
+ if (iocs.wildcardPackages && iocs.wildcardPackages.has(pkg.name) && iocs.packagesMap) {
91
+ const pkgList = iocs.packagesMap.get(pkg.name);
92
+ maliciousPkg = pkgList ? pkgList.find(p => p.version === '*') : null;
93
+ }
94
+ // Check 2: Version specifique via Map
95
+ else if (iocs.packagesMap && iocs.packagesMap.has(pkg.name)) {
96
+ const pkgList = iocs.packagesMap.get(pkg.name);
97
+ maliciousPkg = pkgList ? pkgList.find(p => p.version === pkg.version) : null;
98
+ }
99
+ // Fallback: recherche lineaire (compatibilite ancienne API)
100
+ else if (!iocs.packagesMap) {
101
+ maliciousPkg = iocs.packages.find(p => {
102
+ if (p.name !== pkg.name) return false;
103
+ if (p.version === '*') return true;
104
+ return p.version === pkg.version;
105
+ });
106
+ }
107
+
108
+ if (maliciousPkg) {
109
+ threats.push({
110
+ type: 'known_malicious_package',
111
+ severity: 'CRITICAL',
112
+ message: `Package malveillant connu: ${pkg.name}@${maliciousPkg.version} (source: ${maliciousPkg.source})`,
113
+ file: `node_modules/${pkg.name}`
114
+ });
115
+ continue;
116
+ }
117
+
118
+ // Skip trusted packages pour les checks suivants
119
+ if (TRUSTED_PACKAGES.includes(pkg.name)) continue;
120
+
121
+ // Verifie les fichiers suspects (IOCs caches) avec whitelist
122
+ for (const suspFile of filesToCheck) {
123
+ // Skip si fichier legitime pour ce package
124
+ if (SAFE_FILES[suspFile] && SAFE_FILES[suspFile].includes(pkg.name)) {
125
+ continue;
126
+ }
127
+
128
+ const filePath = path.join(pkg.path, suspFile);
129
+ if (fs.existsSync(filePath)) {
130
+ threats.push({
131
+ type: 'suspicious_file',
132
+ severity: 'HIGH',
133
+ message: `Fichier suspect "${suspFile}" dans ${pkg.name}`,
134
+ file: `node_modules/${pkg.name}/${suspFile}`
135
+ });
136
+ }
137
+ }
138
+
139
+ // Verifie les lifecycle scripts
140
+ const pkgJsonPath = path.join(pkg.path, 'package.json');
141
+ if (fs.existsSync(pkgJsonPath)) {
142
+ try {
143
+ const pkgContent = fs.readFileSync(pkgJsonPath, 'utf8');
144
+
145
+ // Verifie les marqueurs Shai-Hulud
146
+ for (const marker of markersToCheck) {
147
+ if (pkgContent.includes(marker)) {
148
+ threats.push({
149
+ type: 'shai_hulud_marker',
150
+ severity: 'CRITICAL',
151
+ message: `Marqueur "${marker}" detecte dans ${pkg.name}`,
152
+ file: `node_modules/${pkg.name}/package.json`
153
+ });
154
+ }
155
+ }
156
+ } catch {
157
+ // JSON parse error, skip
158
+ }
159
+ }
160
+ }
161
+
162
+ return threats;
163
+ }
164
+
165
+ function listPackages(nodeModulesPath) {
166
+ const packages = [];
167
+ const items = fs.readdirSync(nodeModulesPath);
168
+
169
+ for (const item of items) {
170
+ if (item.startsWith('.')) continue;
171
+
172
+ const itemPath = path.join(nodeModulesPath, item);
173
+
174
+ try {
175
+ const stat = fs.lstatSync(itemPath);
176
+ if (stat.isSymbolicLink()) continue;
177
+ if (!stat.isDirectory()) continue;
178
+
179
+ if (item.startsWith('@')) {
180
+ const scopedItems = fs.readdirSync(itemPath);
181
+ for (const scopedItem of scopedItems) {
182
+ const scopedPath = path.join(itemPath, scopedItem);
183
+ const scopedStat = fs.lstatSync(scopedPath);
184
+ if (scopedStat.isSymbolicLink()) continue;
185
+ if (scopedStat.isDirectory()) {
186
+ const version = getPackageVersion(scopedPath);
187
+ packages.push({
188
+ name: `${item}/${scopedItem}`,
189
+ path: scopedPath,
190
+ version: version
191
+ });
192
+ }
193
+ }
194
+ } else {
195
+ const version = getPackageVersion(itemPath);
196
+ packages.push({
197
+ name: item,
198
+ path: itemPath,
199
+ version: version
200
+ });
201
+ }
202
+ } catch {
203
+ // Skip inaccessible
204
+ }
205
+ }
206
+
207
+ return packages;
208
+ }
209
+
210
+ function getPackageVersion(pkgPath) {
211
+ try {
212
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(pkgPath, 'package.json'), 'utf8'));
213
+ return pkgJson.version || '*';
214
+ } catch {
215
+ return '*';
216
+ }
217
+ }
218
+
219
+ module.exports = {
220
+ scanDependencies,
221
+ checkRehabilitatedPackage,
222
+ TRUSTED_PACKAGES,
223
+ SAFE_FILES
224
224
  };