@miraj181/ipingyou 2.1.6 → 2.1.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.
- package/package.json +8 -7
- package/src/cli.js +31 -28
- package/src/lib/broker.js +18 -9
- package/src/lib/chat.js +9 -2
- package/src/lib/checksum.js +22 -2
- package/src/lib/cleanup.js +18 -8
- package/src/lib/crypto.js +27 -0
- package/src/lib/platform.js +33 -10
- package/src/lib/secure-print.js +86 -0
- package/src/lib/session-log.js +78 -3
- package/src/lib/ssh.js +25 -5
- package/src/lib/tunnel.js +1 -0
- package/src/lib/worker-runtime.js +81 -0
- package/src/lib/workers/crypto-checksum-worker.js +70 -0
- package/src/modes/ai.js +11 -8
- package/src/modes/client.js +27 -15
- package/src/modes/doctor.js +11 -7
- package/src/modes/host.js +254 -99
- package/src/server.js +95 -18
package/src/server.js
CHANGED
|
@@ -96,8 +96,8 @@ const hostLimiter = rateLimit({
|
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
// ─── Active Defense & IP Blacklisting (IDS) ──────────────────
|
|
99
|
-
const ipViolations = new Map(); // ip →
|
|
100
|
-
const blacklistedIPs = new
|
|
99
|
+
const ipViolations = new Map(); // ip → { count, lastSeen }
|
|
100
|
+
const blacklistedIPs = new Map(); // ip → bannedAt
|
|
101
101
|
const VIOLATION_THRESHOLD = 5; // Block IP after 5 malicious requests
|
|
102
102
|
|
|
103
103
|
app.use((req, res, next) => {
|
|
@@ -111,12 +111,12 @@ app.use((req, res, next) => {
|
|
|
111
111
|
|
|
112
112
|
function recordViolation(req) {
|
|
113
113
|
const ip = req.ip || req.connection.remoteAddress;
|
|
114
|
-
const count = (ipViolations.get(ip) || 0) + 1;
|
|
115
|
-
ipViolations.set(ip, count);
|
|
114
|
+
const count = (ipViolations.get(ip)?.count || 0) + 1;
|
|
115
|
+
ipViolations.set(ip, { count, lastSeen: Date.now() });
|
|
116
116
|
console.warn(`🚨 Violation recorded for IP ${ip} (${count}/${VIOLATION_THRESHOLD})`);
|
|
117
117
|
|
|
118
118
|
if (count >= VIOLATION_THRESHOLD) {
|
|
119
|
-
blacklistedIPs.
|
|
119
|
+
blacklistedIPs.set(ip, Date.now());
|
|
120
120
|
console.error(`💥 HACKING DETECTED: Auto-banned IP ${ip} to defend server.`);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -126,9 +126,19 @@ const TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
|
126
126
|
const MAX_UIDS = 50000; // Max concurrent tunnels (prevent memory leak)
|
|
127
127
|
const MAX_VIOLATIONS = 50000; // Max tracked malicious IPs before reset
|
|
128
128
|
const MAX_RESOLVES_PER_UID = 100; // Max resolves before auto-revoke (anti-scraping)
|
|
129
|
+
const MAX_APPROVALS_PER_UID = 50;
|
|
130
|
+
const MAX_CLIENTS_PER_UID = 50;
|
|
131
|
+
const configuredPayloadMb = Number.parseInt(process.env.BROKER_MAX_PAYLOAD_MB || '64', 10);
|
|
132
|
+
const MAX_BUFFERED_PAYLOAD_BYTES = (
|
|
133
|
+
Number.isFinite(configuredPayloadMb) && configuredPayloadMb > 0 ? configuredPayloadMb : 64
|
|
134
|
+
) * 1024 * 1024;
|
|
135
|
+
const APPROVAL_TTL_MS = TTL_MS;
|
|
136
|
+
const TELEMETRY_TTL_MS = 30 * 60 * 1000;
|
|
137
|
+
const VIOLATION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
129
138
|
const BROKER_SECRET = process.env.BROKER_HMAC_SECRET || crypto.randomBytes(32).toString('hex');
|
|
130
139
|
|
|
131
140
|
const store = new Map(); // uid → { iv, ciphertext, salt, createdAt, clients: [], approvals: [], hostToken, resolveCount }
|
|
141
|
+
let bufferedPayloadBytes = 0;
|
|
132
142
|
|
|
133
143
|
// ─── Security Helpers ────────────────────────────────────────
|
|
134
144
|
const SAFE_PARAM = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
@@ -161,22 +171,61 @@ function isEncryptedPayload(body) {
|
|
|
161
171
|
&& /^[A-Za-z0-9+/]+={0,2}$/.test(body.ciphertext);
|
|
162
172
|
}
|
|
163
173
|
|
|
174
|
+
function encryptedPayloadBytes(payload) {
|
|
175
|
+
return Buffer.byteLength(payload?.ciphertext || '')
|
|
176
|
+
+ Buffer.byteLength(payload?.iv || '')
|
|
177
|
+
+ Buffer.byteLength(payload?.salt || '');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function entryPayloadBytes(entry) {
|
|
181
|
+
if (!entry) return 0;
|
|
182
|
+
return encryptedPayloadBytes(entry)
|
|
183
|
+
+ entry.clients.reduce((sum, item) => sum + encryptedPayloadBytes(item), 0)
|
|
184
|
+
+ entry.approvals.reduce((sum, item) => sum + encryptedPayloadBytes(item), 0);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function deleteStoreEntry(uid) {
|
|
188
|
+
const entry = store.get(uid);
|
|
189
|
+
if (!entry) return false;
|
|
190
|
+
bufferedPayloadBytes = Math.max(0, bufferedPayloadBytes - entryPayloadBytes(entry));
|
|
191
|
+
return store.delete(uid);
|
|
192
|
+
}
|
|
193
|
+
|
|
164
194
|
function pruneExpired() {
|
|
165
195
|
const now = Date.now();
|
|
166
196
|
for (const [uid, entry] of store) {
|
|
167
197
|
if (now - entry.createdAt > TTL_MS) {
|
|
168
|
-
|
|
198
|
+
deleteStoreEntry(uid);
|
|
169
199
|
console.log(`🗑️ Expired UID: ${uid}`);
|
|
200
|
+
continue;
|
|
170
201
|
}
|
|
202
|
+
const previousBytes = entryPayloadBytes(entry);
|
|
203
|
+
entry.approvals = entry.approvals
|
|
204
|
+
.filter(item => now - item.createdAt <= APPROVAL_TTL_MS)
|
|
205
|
+
.slice(-MAX_APPROVALS_PER_UID);
|
|
206
|
+
entry.clients = entry.clients
|
|
207
|
+
.filter(item => now - item.seenAt <= TELEMETRY_TTL_MS)
|
|
208
|
+
.slice(-MAX_CLIENTS_PER_UID);
|
|
209
|
+
bufferedPayloadBytes = Math.max(0, bufferedPayloadBytes - previousBytes + entryPayloadBytes(entry));
|
|
171
210
|
}
|
|
172
211
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
212
|
+
for (const [ip, violation] of ipViolations) {
|
|
213
|
+
if (now - violation.lastSeen > VIOLATION_TTL_MS) ipViolations.delete(ip);
|
|
214
|
+
}
|
|
215
|
+
for (const [ip, bannedAt] of blacklistedIPs) {
|
|
216
|
+
if (now - bannedAt > VIOLATION_TTL_MS) blacklistedIPs.delete(ip);
|
|
217
|
+
}
|
|
218
|
+
while (ipViolations.size > MAX_VIOLATIONS) {
|
|
219
|
+
ipViolations.delete(ipViolations.keys().next().value);
|
|
220
|
+
}
|
|
221
|
+
while (blacklistedIPs.size > MAX_VIOLATIONS) {
|
|
222
|
+
blacklistedIPs.delete(blacklistedIPs.keys().next().value);
|
|
223
|
+
}
|
|
176
224
|
}
|
|
177
225
|
|
|
178
226
|
// Run pruning every 5 minutes
|
|
179
|
-
setInterval(pruneExpired, 5 * 60 * 1000);
|
|
227
|
+
const pruneTimer = setInterval(pruneExpired, 5 * 60 * 1000);
|
|
228
|
+
pruneTimer.unref?.();
|
|
180
229
|
|
|
181
230
|
// ─── Routes ──────────────────────────────────────────────────
|
|
182
231
|
|
|
@@ -227,6 +276,11 @@ app.post('/register', strictLimiter, (req, res) => {
|
|
|
227
276
|
if (store.size >= MAX_UIDS && !store.has(uid)) {
|
|
228
277
|
return res.status(503).json({ error: 'Broker is at maximum capacity. Please try again later.' });
|
|
229
278
|
}
|
|
279
|
+
const incomingBytes = encryptedPayloadBytes({ iv, ciphertext, salt });
|
|
280
|
+
const replacedBytes = entryPayloadBytes(store.get(uid));
|
|
281
|
+
if (bufferedPayloadBytes - replacedBytes + incomingBytes > MAX_BUFFERED_PAYLOAD_BYTES) {
|
|
282
|
+
return res.status(503).json({ error: 'Broker encrypted payload capacity reached. Please try again later.' });
|
|
283
|
+
}
|
|
230
284
|
|
|
231
285
|
// Use provided host token if valid; otherwise generate a fresh one
|
|
232
286
|
const hostToken = (typeof providedHostToken === 'string' && HOST_TOKEN_FORMAT.test(providedHostToken))
|
|
@@ -234,6 +288,7 @@ app.post('/register', strictLimiter, (req, res) => {
|
|
|
234
288
|
: generateHostToken(uid + Date.now().toString());
|
|
235
289
|
|
|
236
290
|
// Store the encrypted blob as-is — broker NEVER decrypts
|
|
291
|
+
if (store.has(uid)) deleteStoreEntry(uid);
|
|
237
292
|
store.set(uid, {
|
|
238
293
|
iv,
|
|
239
294
|
ciphertext,
|
|
@@ -246,6 +301,7 @@ app.post('/register', strictLimiter, (req, res) => {
|
|
|
246
301
|
hostToken,
|
|
247
302
|
resolveCount: 0,
|
|
248
303
|
});
|
|
304
|
+
bufferedPayloadBytes += incomingBytes;
|
|
249
305
|
|
|
250
306
|
console.log(`✅ [${new Date().toLocaleTimeString()}] Registered UID: ${uid} (encrypted, ${ciphertext.length} bytes)`);
|
|
251
307
|
// Return host token — this is the ONLY time it's sent; host must store it
|
|
@@ -277,19 +333,19 @@ app.get('/resolve/:uid', generalLimiter, (req, res) => {
|
|
|
277
333
|
|
|
278
334
|
// Check TTL
|
|
279
335
|
if (Date.now() - entry.createdAt > TTL_MS) {
|
|
280
|
-
|
|
336
|
+
deleteStoreEntry(uid);
|
|
281
337
|
return res.status(410).json({ error: 'UID expired' });
|
|
282
338
|
}
|
|
283
339
|
|
|
284
340
|
// Enforce one-time sharing: check BEFORE sending response (prevents race condition)
|
|
285
341
|
if (entry.oneTime && entry.resolveCount > 0) {
|
|
286
|
-
|
|
342
|
+
deleteStoreEntry(uid);
|
|
287
343
|
return res.status(410).json({ error: 'One-time session already consumed' });
|
|
288
344
|
}
|
|
289
345
|
|
|
290
346
|
// Anti-scraping: cap total resolves per UID
|
|
291
347
|
if (entry.resolveCount >= MAX_RESOLVES_PER_UID) {
|
|
292
|
-
|
|
348
|
+
deleteStoreEntry(uid);
|
|
293
349
|
return res.status(429).json({ error: 'Resolve limit exceeded — session revoked for security' });
|
|
294
350
|
}
|
|
295
351
|
|
|
@@ -315,7 +371,7 @@ app.get('/resolve/:uid', generalLimiter, (req, res) => {
|
|
|
315
371
|
|
|
316
372
|
// Enforce one-time sharing: auto-delete AFTER response sent
|
|
317
373
|
if (entry.oneTime) {
|
|
318
|
-
|
|
374
|
+
deleteStoreEntry(uid);
|
|
319
375
|
console.log(`🔒 [${new Date().toLocaleTimeString()}] One-time UID ${uid} auto-revoked after first resolve`);
|
|
320
376
|
}
|
|
321
377
|
} catch (err) {
|
|
@@ -354,8 +410,18 @@ app.post('/approval-request/:uid', generalLimiter, (req, res) => {
|
|
|
354
410
|
decidedAt: entry.approvalRequired ? null : Date.now(),
|
|
355
411
|
};
|
|
356
412
|
|
|
413
|
+
const requestBytes = encryptedPayloadBytes(request);
|
|
414
|
+
const replacedBytes = entry.approvals.length >= MAX_APPROVALS_PER_UID
|
|
415
|
+
? encryptedPayloadBytes(entry.approvals[0])
|
|
416
|
+
: 0;
|
|
417
|
+
if (bufferedPayloadBytes - replacedBytes + requestBytes > MAX_BUFFERED_PAYLOAD_BYTES) {
|
|
418
|
+
return res.status(503).json({ error: 'Broker encrypted payload capacity reached. Please try again later.' });
|
|
419
|
+
}
|
|
357
420
|
entry.approvals.push(request);
|
|
358
|
-
|
|
421
|
+
bufferedPayloadBytes += requestBytes;
|
|
422
|
+
if (entry.approvals.length > MAX_APPROVALS_PER_UID) {
|
|
423
|
+
bufferedPayloadBytes -= encryptedPayloadBytes(entry.approvals.shift());
|
|
424
|
+
}
|
|
359
425
|
res.json({ requestId: id, status: request.status, approvalRequired: entry.approvalRequired });
|
|
360
426
|
} catch {
|
|
361
427
|
res.status(500).json({ error: 'Internal server error' });
|
|
@@ -426,10 +492,21 @@ app.post('/client-info/:uid', generalLimiter, (req, res) => {
|
|
|
426
492
|
return res.status(400).json({ error: 'Invalid encrypted telemetry payload' });
|
|
427
493
|
}
|
|
428
494
|
|
|
429
|
-
|
|
495
|
+
const clientRecord = { iv: req.body.iv, ciphertext: req.body.ciphertext, salt: req.body.salt, seenAt: Date.now() };
|
|
496
|
+
const clientBytes = encryptedPayloadBytes(clientRecord);
|
|
497
|
+
const replacedBytes = entry.clients.length >= MAX_CLIENTS_PER_UID
|
|
498
|
+
? encryptedPayloadBytes(entry.clients[0])
|
|
499
|
+
: 0;
|
|
500
|
+
if (bufferedPayloadBytes - replacedBytes + clientBytes > MAX_BUFFERED_PAYLOAD_BYTES) {
|
|
501
|
+
return res.status(503).json({ error: 'Broker encrypted payload capacity reached. Please try again later.' });
|
|
502
|
+
}
|
|
503
|
+
entry.clients.push(clientRecord);
|
|
504
|
+
bufferedPayloadBytes += clientBytes;
|
|
430
505
|
|
|
431
506
|
// Keep max 50 recent client pings to prevent memory leaks
|
|
432
|
-
if (entry.clients.length >
|
|
507
|
+
if (entry.clients.length > MAX_CLIENTS_PER_UID) {
|
|
508
|
+
bufferedPayloadBytes -= encryptedPayloadBytes(entry.clients.shift());
|
|
509
|
+
}
|
|
433
510
|
|
|
434
511
|
res.json({ status: 'ok' });
|
|
435
512
|
} catch (err) {
|
|
@@ -470,7 +547,7 @@ app.delete('/revoke/:uid', strictLimiter, (req, res) => {
|
|
|
470
547
|
const entry = store.get(uid);
|
|
471
548
|
if (!entry) return res.json({ status: 'not_found' });
|
|
472
549
|
if (!requireHostToken(req, res, entry)) return;
|
|
473
|
-
|
|
550
|
+
deleteStoreEntry(uid);
|
|
474
551
|
console.log(`🚫 [${new Date().toLocaleTimeString()}] Revoked UID: ${uid} (authenticated)`);
|
|
475
552
|
res.json({ status: 'revoked' });
|
|
476
553
|
});
|