muaddib-scanner 2.2.23 → 2.2.24
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/package.json +1 -1
- package/src/daemon.js +178 -178
- package/src/ioc/scraper.js +7 -1
- package/src/ioc/updater.js +424 -424
- package/src/safe-install.js +294 -294
package/package.json
CHANGED
package/src/daemon.js
CHANGED
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { run } = require('./index.js');
|
|
4
|
-
|
|
5
|
-
let webhookUrl = null;
|
|
6
|
-
|
|
7
|
-
async function startDaemon(options = {}) {
|
|
8
|
-
webhookUrl = options.webhook || null;
|
|
9
|
-
|
|
10
|
-
console.log(`
|
|
11
|
-
╔════════════════════════════════════════════╗
|
|
12
|
-
║ MUAD'DIB Security Daemon ║
|
|
13
|
-
║ Surveillance npm install active ║
|
|
14
|
-
╚════════════════════════════════════════════╝
|
|
15
|
-
`);
|
|
16
|
-
|
|
17
|
-
console.log('[DAEMON] Demarrage...');
|
|
18
|
-
console.log(`[DAEMON] Webhook: ${webhookUrl ? 'Configure' : 'Non configure'}`);
|
|
19
|
-
console.log('[DAEMON] Ctrl+C pour arreter\n');
|
|
20
|
-
|
|
21
|
-
// Surveille le dossier courant
|
|
22
|
-
const cwd = process.cwd();
|
|
23
|
-
const watchers = watchDirectory(cwd);
|
|
24
|
-
|
|
25
|
-
// Cleanup function to close all watchers
|
|
26
|
-
function cleanup() {
|
|
27
|
-
for (const w of watchers) {
|
|
28
|
-
try { w.close(); } catch { /* ignore */ }
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Keep process alive until SIGINT
|
|
33
|
-
await new Promise((resolve) => {
|
|
34
|
-
process.once('SIGINT', () => {
|
|
35
|
-
console.log('\n[DAEMON] Arret...');
|
|
36
|
-
cleanup();
|
|
37
|
-
resolve();
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
process.exit(0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function watchDirectory(dir) {
|
|
45
|
-
const watchers = [];
|
|
46
|
-
const nodeModulesPath = path.join(dir, 'node_modules');
|
|
47
|
-
const packageLockPath = path.join(dir, 'package-lock.json');
|
|
48
|
-
const yarnLockPath = path.join(dir, 'yarn.lock');
|
|
49
|
-
|
|
50
|
-
console.log(`[DAEMON] Surveillance de ${dir}`);
|
|
51
|
-
|
|
52
|
-
// Surveille package-lock.json
|
|
53
|
-
if (fs.existsSync(packageLockPath)) {
|
|
54
|
-
const w = watchFile(packageLockPath, dir);
|
|
55
|
-
if (w) watchers.push(w);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Surveille yarn.lock
|
|
59
|
-
if (fs.existsSync(yarnLockPath)) {
|
|
60
|
-
const w = watchFile(yarnLockPath, dir);
|
|
61
|
-
if (w) watchers.push(w);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Surveille node_modules
|
|
65
|
-
if (fs.existsSync(nodeModulesPath)) {
|
|
66
|
-
watchers.push(watchNodeModules(nodeModulesPath, dir));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Surveille la creation de node_modules
|
|
70
|
-
if (process.platform === 'linux') {
|
|
71
|
-
console.log('[DAEMON] Note: recursive fs.watch may not work on Linux');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const dirWatcher = fs.watch(dir, (eventType, filename) => {
|
|
75
|
-
if (filename === 'node_modules' && eventType === 'rename') {
|
|
76
|
-
const nmPath = path.join(dir, 'node_modules');
|
|
77
|
-
if (fs.existsSync(nmPath)) {
|
|
78
|
-
console.log('[DAEMON] node_modules detecte, scan en cours...');
|
|
79
|
-
triggerScan(dir);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (filename === 'package-lock.json' || filename === 'yarn.lock') {
|
|
83
|
-
console.log(`[DAEMON] ${filename} modifie, scan en cours...`);
|
|
84
|
-
triggerScan(dir);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
dirWatcher.on('error', (err) => {
|
|
88
|
-
console.log(`[DAEMON] Watcher error on ${dir}: ${err.message}`);
|
|
89
|
-
});
|
|
90
|
-
watchers.push(dirWatcher);
|
|
91
|
-
|
|
92
|
-
return watchers;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function watchFile(filePath, projectDir) {
|
|
96
|
-
let lastMtime;
|
|
97
|
-
try {
|
|
98
|
-
lastMtime = fs.statSync(filePath).mtime.getTime();
|
|
99
|
-
} catch {
|
|
100
|
-
return null; // File deleted between existsSync and statSync
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const watcher = fs.watch(filePath, (eventType) => {
|
|
104
|
-
if (eventType === 'change') {
|
|
105
|
-
try {
|
|
106
|
-
const currentMtime = fs.statSync(filePath).mtime.getTime();
|
|
107
|
-
if (currentMtime !== lastMtime) {
|
|
108
|
-
lastMtime = currentMtime;
|
|
109
|
-
console.log(`[DAEMON] ${path.basename(filePath)} modifie`);
|
|
110
|
-
triggerScan(projectDir);
|
|
111
|
-
}
|
|
112
|
-
} catch {
|
|
113
|
-
// File may have been deleted between watch trigger and stat
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
watcher.on('error', (err) => {
|
|
118
|
-
console.log(`[DAEMON] Watcher error on ${filePath}: ${err.message}`);
|
|
119
|
-
});
|
|
120
|
-
return watcher;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function watchNodeModules(nodeModulesPath, projectDir) {
|
|
124
|
-
const watcher = fs.watch(nodeModulesPath, { recursive: true }, (eventType, filename) => {
|
|
125
|
-
if (filename && filename.includes('package.json')) {
|
|
126
|
-
console.log(`[DAEMON] Nouveau package detecte: ${filename}`);
|
|
127
|
-
triggerScan(projectDir);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
watcher.on('error', (err) => {
|
|
131
|
-
console.log(`[DAEMON] Watcher error on ${nodeModulesPath}: ${err.message}`);
|
|
132
|
-
});
|
|
133
|
-
return watcher;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Per-directory scan state to prevent cross-directory scan suppression
|
|
137
|
-
const scanState = new Map();
|
|
138
|
-
|
|
139
|
-
function getScanState(dir) {
|
|
140
|
-
if (!scanState.has(dir)) {
|
|
141
|
-
scanState.set(dir, { timeout: null, lastScanTime: 0 });
|
|
142
|
-
}
|
|
143
|
-
return scanState.get(dir);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function triggerScan(dir) {
|
|
147
|
-
const now = Date.now();
|
|
148
|
-
const state = getScanState(dir);
|
|
149
|
-
|
|
150
|
-
// Debounce: attend 3 secondes avant de scanner
|
|
151
|
-
if (state.timeout) {
|
|
152
|
-
clearTimeout(state.timeout);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Evite les scans trop frequents (minimum 10 secondes entre chaque)
|
|
156
|
-
if (now - state.lastScanTime < 10000) {
|
|
157
|
-
state.timeout = setTimeout(() => triggerScan(dir), 10000 - (now - state.lastScanTime));
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
state.timeout = setTimeout(async () => {
|
|
162
|
-
state.lastScanTime = Date.now();
|
|
163
|
-
console.log(`\n[DAEMON] ========== SCAN AUTOMATIQUE ==========`);
|
|
164
|
-
console.log(`[DAEMON] Cible: ${dir}`);
|
|
165
|
-
console.log(`[DAEMON] Heure: ${new Date().toLocaleTimeString()}\n`);
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
await run(dir, { webhook: webhookUrl });
|
|
169
|
-
} catch (err) {
|
|
170
|
-
console.log(`[DAEMON] Erreur scan: ${err.message}`);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
console.log(`\n[DAEMON] ======================================\n`);
|
|
174
|
-
console.log('[DAEMON] En attente de modifications...');
|
|
175
|
-
}, 3000);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
module.exports = { startDaemon };
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { run } = require('./index.js');
|
|
4
|
+
|
|
5
|
+
let webhookUrl = null;
|
|
6
|
+
|
|
7
|
+
async function startDaemon(options = {}) {
|
|
8
|
+
webhookUrl = options.webhook || null;
|
|
9
|
+
|
|
10
|
+
console.log(`
|
|
11
|
+
╔════════════════════════════════════════════╗
|
|
12
|
+
║ MUAD'DIB Security Daemon ║
|
|
13
|
+
║ Surveillance npm install active ║
|
|
14
|
+
╚════════════════════════════════════════════╝
|
|
15
|
+
`);
|
|
16
|
+
|
|
17
|
+
console.log('[DAEMON] Demarrage...');
|
|
18
|
+
console.log(`[DAEMON] Webhook: ${webhookUrl ? 'Configure' : 'Non configure'}`);
|
|
19
|
+
console.log('[DAEMON] Ctrl+C pour arreter\n');
|
|
20
|
+
|
|
21
|
+
// Surveille le dossier courant
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
const watchers = watchDirectory(cwd);
|
|
24
|
+
|
|
25
|
+
// Cleanup function to close all watchers
|
|
26
|
+
function cleanup() {
|
|
27
|
+
for (const w of watchers) {
|
|
28
|
+
try { w.close(); } catch { /* ignore */ }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Keep process alive until SIGINT
|
|
33
|
+
await new Promise((resolve) => {
|
|
34
|
+
process.once('SIGINT', () => {
|
|
35
|
+
console.log('\n[DAEMON] Arret...');
|
|
36
|
+
cleanup();
|
|
37
|
+
resolve();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function watchDirectory(dir) {
|
|
45
|
+
const watchers = [];
|
|
46
|
+
const nodeModulesPath = path.join(dir, 'node_modules');
|
|
47
|
+
const packageLockPath = path.join(dir, 'package-lock.json');
|
|
48
|
+
const yarnLockPath = path.join(dir, 'yarn.lock');
|
|
49
|
+
|
|
50
|
+
console.log(`[DAEMON] Surveillance de ${dir}`);
|
|
51
|
+
|
|
52
|
+
// Surveille package-lock.json
|
|
53
|
+
if (fs.existsSync(packageLockPath)) {
|
|
54
|
+
const w = watchFile(packageLockPath, dir);
|
|
55
|
+
if (w) watchers.push(w);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Surveille yarn.lock
|
|
59
|
+
if (fs.existsSync(yarnLockPath)) {
|
|
60
|
+
const w = watchFile(yarnLockPath, dir);
|
|
61
|
+
if (w) watchers.push(w);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Surveille node_modules
|
|
65
|
+
if (fs.existsSync(nodeModulesPath)) {
|
|
66
|
+
watchers.push(watchNodeModules(nodeModulesPath, dir));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Surveille la creation de node_modules
|
|
70
|
+
if (process.platform === 'linux') {
|
|
71
|
+
console.log('[DAEMON] Note: recursive fs.watch may not work on Linux');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const dirWatcher = fs.watch(dir, (eventType, filename) => {
|
|
75
|
+
if (filename === 'node_modules' && eventType === 'rename') {
|
|
76
|
+
const nmPath = path.join(dir, 'node_modules');
|
|
77
|
+
if (fs.existsSync(nmPath)) {
|
|
78
|
+
console.log('[DAEMON] node_modules detecte, scan en cours...');
|
|
79
|
+
triggerScan(dir);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (filename === 'package-lock.json' || filename === 'yarn.lock') {
|
|
83
|
+
console.log(`[DAEMON] ${filename} modifie, scan en cours...`);
|
|
84
|
+
triggerScan(dir);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
dirWatcher.on('error', (err) => {
|
|
88
|
+
console.log(`[DAEMON] Watcher error on ${dir}: ${err.message}`);
|
|
89
|
+
});
|
|
90
|
+
watchers.push(dirWatcher);
|
|
91
|
+
|
|
92
|
+
return watchers;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function watchFile(filePath, projectDir) {
|
|
96
|
+
let lastMtime;
|
|
97
|
+
try {
|
|
98
|
+
lastMtime = fs.statSync(filePath).mtime.getTime();
|
|
99
|
+
} catch {
|
|
100
|
+
return null; // File deleted between existsSync and statSync
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const watcher = fs.watch(filePath, (eventType) => {
|
|
104
|
+
if (eventType === 'change') {
|
|
105
|
+
try {
|
|
106
|
+
const currentMtime = fs.statSync(filePath).mtime.getTime();
|
|
107
|
+
if (currentMtime !== lastMtime) {
|
|
108
|
+
lastMtime = currentMtime;
|
|
109
|
+
console.log(`[DAEMON] ${path.basename(filePath)} modifie`);
|
|
110
|
+
triggerScan(projectDir);
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// File may have been deleted between watch trigger and stat
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
watcher.on('error', (err) => {
|
|
118
|
+
console.log(`[DAEMON] Watcher error on ${filePath}: ${err.message}`);
|
|
119
|
+
});
|
|
120
|
+
return watcher;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function watchNodeModules(nodeModulesPath, projectDir) {
|
|
124
|
+
const watcher = fs.watch(nodeModulesPath, { recursive: true }, (eventType, filename) => {
|
|
125
|
+
if (filename && filename.includes('package.json')) {
|
|
126
|
+
console.log(`[DAEMON] Nouveau package detecte: ${filename}`);
|
|
127
|
+
triggerScan(projectDir);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
watcher.on('error', (err) => {
|
|
131
|
+
console.log(`[DAEMON] Watcher error on ${nodeModulesPath}: ${err.message}`);
|
|
132
|
+
});
|
|
133
|
+
return watcher;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Per-directory scan state to prevent cross-directory scan suppression
|
|
137
|
+
const scanState = new Map();
|
|
138
|
+
|
|
139
|
+
function getScanState(dir) {
|
|
140
|
+
if (!scanState.has(dir)) {
|
|
141
|
+
scanState.set(dir, { timeout: null, lastScanTime: 0 });
|
|
142
|
+
}
|
|
143
|
+
return scanState.get(dir);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function triggerScan(dir) {
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
const state = getScanState(dir);
|
|
149
|
+
|
|
150
|
+
// Debounce: attend 3 secondes avant de scanner
|
|
151
|
+
if (state.timeout) {
|
|
152
|
+
clearTimeout(state.timeout);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Evite les scans trop frequents (minimum 10 secondes entre chaque)
|
|
156
|
+
if (now - state.lastScanTime < 10000) {
|
|
157
|
+
state.timeout = setTimeout(() => triggerScan(dir), 10000 - (now - state.lastScanTime));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
state.timeout = setTimeout(async () => {
|
|
162
|
+
state.lastScanTime = Date.now();
|
|
163
|
+
console.log(`\n[DAEMON] ========== SCAN AUTOMATIQUE ==========`);
|
|
164
|
+
console.log(`[DAEMON] Cible: ${dir}`);
|
|
165
|
+
console.log(`[DAEMON] Heure: ${new Date().toLocaleTimeString()}\n`);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await run(dir, { webhook: webhookUrl });
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.log(`[DAEMON] Erreur scan: ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(`\n[DAEMON] ======================================\n`);
|
|
174
|
+
console.log('[DAEMON] En attente de modifications...');
|
|
175
|
+
}, 3000);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = { startDaemon, watchDirectory, watchFile, watchNodeModules, triggerScan, getScanState };
|
package/src/ioc/scraper.js
CHANGED
|
@@ -1278,7 +1278,13 @@ async function runScraper() {
|
|
|
1278
1278
|
};
|
|
1279
1279
|
}
|
|
1280
1280
|
|
|
1281
|
-
module.exports = {
|
|
1281
|
+
module.exports = {
|
|
1282
|
+
runScraper, scrapeShaiHuludDetector, scrapeDatadogIOCs,
|
|
1283
|
+
// Pure utility functions (exported for testing)
|
|
1284
|
+
parseCSVLine, parseCSV, extractVersions, parseOSVEntry,
|
|
1285
|
+
createFreshness, isAllowedRedirect, loadStaticIOCs,
|
|
1286
|
+
CONFIDENCE_ORDER, ALLOWED_REDIRECT_DOMAINS
|
|
1287
|
+
};
|
|
1282
1288
|
|
|
1283
1289
|
// Direct execution if called as CLI
|
|
1284
1290
|
if (require.main === module) {
|