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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.2.23",
3
+ "version": "2.2.24",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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 };
@@ -1278,7 +1278,13 @@ async function runScraper() {
1278
1278
  };
1279
1279
  }
1280
1280
 
1281
- module.exports = { runScraper, scrapeShaiHuludDetector, scrapeDatadogIOCs };
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) {