@miraj181/ipingyou 2.1.18 → 2.1.22
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/README.md +1 -2
- package/package.json +1 -1
- package/src/cli.js +22 -14
- package/src/lib/ai/safety.js +1 -1
- package/src/lib/{broker.js → client/broker.js} +25 -9
- package/src/lib/{path-browser.js → client/path-browser.js} +6 -2
- package/src/lib/{session-log.js → mod/session-log.js} +17 -8
- package/src/lib/{worker-runtime.js → mod/worker-runtime.js} +1 -1
- package/src/lib/{chat.js → services/chat.js} +2 -2
- package/src/lib/services/platform.js +364 -0
- package/src/lib/{tunnel.js → services/tunnel.js} +2 -2
- package/src/modes/ai.js +18 -9
- package/src/modes/client.js +99 -25
- package/src/modes/doctor.js +5 -7
- package/src/modes/host.js +326 -154
- package/src/server.js +55 -6
- package/src/lib/platform.js +0 -90
- /package/src/lib/{allowlist.js → mod/allowlist.js} +0 -0
- /package/src/lib/{animations.js → mod/animations.js} +0 -0
- /package/src/lib/{checksum.js → mod/checksum.js} +0 -0
- /package/src/lib/{cleanup.js → mod/cleanup.js} +0 -0
- /package/src/lib/{config.js → mod/config.js} +0 -0
- /package/src/lib/{crypto.js → mod/crypto.js} +0 -0
- /package/src/lib/{open-url.js → mod/open-url.js} +0 -0
- /package/src/lib/{secure-print.js → mod/secure-print.js} +0 -0
- /package/src/lib/{socket-firewall.js → mod/socket-firewall.js} +0 -0
- /package/src/lib/{tmux.js → mod/tmux.js} +0 -0
- /package/src/lib/{uid.js → mod/uid.js} +0 -0
- /package/src/lib/{ssh.js → services/ssh.js} +0 -0
package/src/server.js
CHANGED
|
@@ -13,7 +13,13 @@ import express from 'express';
|
|
|
13
13
|
import rateLimit from 'express-rate-limit';
|
|
14
14
|
import helmet from 'helmet';
|
|
15
15
|
import crypto from 'node:crypto';
|
|
16
|
-
import
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { cleanupSessionLog, initSessionLog, logSessionEvent } from './lib/mod/session-log.js';
|
|
20
|
+
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
17
23
|
|
|
18
24
|
const app = express();
|
|
19
25
|
const brokerLogPath = initSessionLog('broker');
|
|
@@ -359,6 +365,30 @@ app.get('/resolve/:uid', generalLimiter, (req, res) => {
|
|
|
359
365
|
if (!approved) {
|
|
360
366
|
return res.status(423).json({ error: 'Host approval required before resolving this session' });
|
|
361
367
|
}
|
|
368
|
+
|
|
369
|
+
// Client-specific E2E gating: verify client IP matches approved request IP
|
|
370
|
+
const clientIp = (req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').split(',')[0].trim();
|
|
371
|
+
if (approved.ip && approved.ip !== clientIp) {
|
|
372
|
+
return res.status(403).json({ error: 'Client IP mismatch — access denied' });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (approved.approvedPayload) {
|
|
376
|
+
entry.resolveCount = (entry.resolveCount || 0) + 1;
|
|
377
|
+
console.log(`🔍 [${new Date().toLocaleTimeString()}] Resolved UID (Client Specific): ${uid} (resolve #${entry.resolveCount})`);
|
|
378
|
+
res.json({
|
|
379
|
+
uid,
|
|
380
|
+
iv: approved.approvedPayload.iv,
|
|
381
|
+
ciphertext: approved.approvedPayload.ciphertext,
|
|
382
|
+
salt: approved.approvedPayload.salt,
|
|
383
|
+
isClientSpecific: true,
|
|
384
|
+
ip: clientIp
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
if (entry.oneTime) {
|
|
388
|
+
deleteStoreEntry(uid);
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
362
392
|
}
|
|
363
393
|
|
|
364
394
|
// Increment resolve counter atomically BEFORE sending response
|
|
@@ -400,6 +430,7 @@ app.post('/approval-request/:uid', generalLimiter, (req, res) => {
|
|
|
400
430
|
|
|
401
431
|
// Generate cryptographically secure request ID
|
|
402
432
|
const id = crypto.randomBytes(12).toString('hex');
|
|
433
|
+
const ip = (req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').split(',')[0].trim();
|
|
403
434
|
const request = {
|
|
404
435
|
id,
|
|
405
436
|
iv: req.body.iv,
|
|
@@ -408,6 +439,7 @@ app.post('/approval-request/:uid', generalLimiter, (req, res) => {
|
|
|
408
439
|
status: entry.approvalRequired ? 'pending' : 'approved',
|
|
409
440
|
createdAt: Date.now(),
|
|
410
441
|
decidedAt: entry.approvalRequired ? null : Date.now(),
|
|
442
|
+
ip,
|
|
411
443
|
};
|
|
412
444
|
|
|
413
445
|
const requestBytes = encryptedPayloadBytes(request);
|
|
@@ -435,8 +467,12 @@ app.get('/approval-requests/:uid', hostLimiter, (req, res) => {
|
|
|
435
467
|
if (!entry) return res.status(404).json({ error: 'UID not found' });
|
|
436
468
|
// Require host token to view approval list
|
|
437
469
|
if (!requireHostToken(req, res, entry)) return;
|
|
438
|
-
//
|
|
439
|
-
const sanitized = entry.approvals.map(a => ({
|
|
470
|
+
// Include encrypted payloads so the host can decrypt client details locally
|
|
471
|
+
const sanitized = entry.approvals.map(a => ({
|
|
472
|
+
id: a.id, status: a.status, createdAt: a.createdAt, decidedAt: a.decidedAt,
|
|
473
|
+
iv: a.iv, ciphertext: a.ciphertext, salt: a.salt,
|
|
474
|
+
ip: a.ip,
|
|
475
|
+
}));
|
|
440
476
|
res.json({ approvalRequired: entry.approvalRequired, approvals: sanitized });
|
|
441
477
|
});
|
|
442
478
|
|
|
@@ -458,6 +494,14 @@ app.post('/approval-requests/:uid/:requestId/:decision', strictLimiter, (req, re
|
|
|
458
494
|
|
|
459
495
|
request.status = req.params.decision;
|
|
460
496
|
request.decidedAt = Date.now();
|
|
497
|
+
|
|
498
|
+
if (req.params.decision === 'approved' && req.body && req.body.ciphertext) {
|
|
499
|
+
request.approvedPayload = {
|
|
500
|
+
iv: req.body.iv,
|
|
501
|
+
ciphertext: req.body.ciphertext,
|
|
502
|
+
salt: req.body.salt
|
|
503
|
+
};
|
|
504
|
+
}
|
|
461
505
|
res.json({ status: request.status });
|
|
462
506
|
});
|
|
463
507
|
|
|
@@ -470,8 +514,13 @@ app.get('/approval-status/:uid/:requestId', generalLimiter, (req, res) => {
|
|
|
470
514
|
if (!entry) return res.status(404).json({ error: 'UID not found' });
|
|
471
515
|
const request = entry.approvals.find(item => item.id === req.params.requestId);
|
|
472
516
|
if (!request) return res.status(404).json({ error: 'Request not found' });
|
|
473
|
-
|
|
474
|
-
|
|
517
|
+
|
|
518
|
+
const clientIp = (req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').split(',')[0].trim();
|
|
519
|
+
res.json({
|
|
520
|
+
status: request.status,
|
|
521
|
+
ip: clientIp,
|
|
522
|
+
approvedPayload: request.status === 'approved' ? request.approvedPayload : undefined
|
|
523
|
+
});
|
|
475
524
|
});
|
|
476
525
|
|
|
477
526
|
/**
|
|
@@ -568,7 +617,7 @@ const HOST = process.env.HOST || '0.0.0.0';
|
|
|
568
617
|
app.listen(PORT, HOST, () => {
|
|
569
618
|
console.log('');
|
|
570
619
|
console.log(' ╔══════════════════════════════════════════╗');
|
|
571
|
-
console.log(
|
|
620
|
+
console.log(` ║ 🔗 SecureLink Broker — Active (v${packageJson.version})`.padEnd(45) + '║');
|
|
572
621
|
console.log(` ║ 📡 Port: ${String(PORT).padEnd(29)}║`);
|
|
573
622
|
console.log(' ║ 🔒 Zero-Knowledge Encrypted Store ║');
|
|
574
623
|
console.log(' ║ ⏱️ TTL: 1 hour per UID ║');
|
package/src/lib/platform.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { execa } from 'execa';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
|
|
6
|
-
export function detectOS() {
|
|
7
|
-
const platform = process.platform;
|
|
8
|
-
return {
|
|
9
|
-
platform,
|
|
10
|
-
isLinux: platform === 'linux',
|
|
11
|
-
isMac: platform === 'darwin',
|
|
12
|
-
isWindows: platform === 'win32',
|
|
13
|
-
distro: null,
|
|
14
|
-
arch: os.arch(),
|
|
15
|
-
hostname: os.hostname(),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function detectLinuxDistro() {
|
|
20
|
-
try {
|
|
21
|
-
const data = await fs.promises.readFile('/etc/os-release', 'utf8');
|
|
22
|
-
const lower = data.toLowerCase();
|
|
23
|
-
if (/(ubuntu|debian|kali|mint)/.test(lower)) return 'debian';
|
|
24
|
-
if (/(arch|manjaro)/.test(lower)) return 'arch';
|
|
25
|
-
if (/(fedora|centos|rhel)/.test(lower)) return 'fedora';
|
|
26
|
-
} catch {
|
|
27
|
-
// Manual instructions fall back to the generic Linux guidance.
|
|
28
|
-
}
|
|
29
|
-
return 'unknown';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function commandExists(command) {
|
|
33
|
-
if (!/^[a-zA-Z0-9._+-]{1,64}$/.test(String(command || ''))) return false;
|
|
34
|
-
try {
|
|
35
|
-
const probe = process.platform === 'win32' ? ['where', command] : ['which', command];
|
|
36
|
-
await execa(probe[0], [probe[1]], {
|
|
37
|
-
reject: true,
|
|
38
|
-
timeout: 5000,
|
|
39
|
-
maxBuffer: 64 * 1024,
|
|
40
|
-
});
|
|
41
|
-
return true;
|
|
42
|
-
} catch {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function printInstallGuidance(osInfo, missing) {
|
|
48
|
-
console.log(chalk.yellow(' ⚠️ Required system tools are missing.'));
|
|
49
|
-
console.log(chalk.dim(' iPingYou does not download or execute native installers automatically.'));
|
|
50
|
-
console.log(chalk.dim(' Install the tools through your trusted OS package manager, then rerun iPingYou.'));
|
|
51
|
-
console.log('');
|
|
52
|
-
|
|
53
|
-
if (missing.includes('cloudflared')) {
|
|
54
|
-
if (osInfo.isMac) console.log(chalk.cyan(' brew install cloudflared'));
|
|
55
|
-
else if (osInfo.isWindows) console.log(chalk.cyan(' winget install --id Cloudflare.cloudflared -e'));
|
|
56
|
-
else console.log(chalk.cyan(' Follow: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/'));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (missing.includes('ssh')) {
|
|
60
|
-
if (osInfo.isMac) console.log(chalk.cyan(' Enable Remote Login in System Settings → General → Sharing'));
|
|
61
|
-
else if (osInfo.isWindows) console.log(chalk.cyan(' Add OpenSSH Client/Server from Windows Optional Features'));
|
|
62
|
-
else console.log(chalk.cyan(' Debian/Ubuntu: sudo apt-get install openssh-client openssh-server'));
|
|
63
|
-
}
|
|
64
|
-
console.log('');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function checkDependencies() {
|
|
68
|
-
const osInfo = detectOS();
|
|
69
|
-
const results = {
|
|
70
|
-
ssh: await commandExists('ssh'),
|
|
71
|
-
cloudflared: await commandExists('cloudflared'),
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
console.log('');
|
|
75
|
-
console.log(chalk.bold(' 🔍 Dependency Check'));
|
|
76
|
-
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
77
|
-
console.log(` ${results.ssh ? chalk.green('✓') : chalk.red('✗')} ssh ${results.ssh ? chalk.dim('found') : chalk.red('missing')}`);
|
|
78
|
-
console.log(` ${results.cloudflared ? chalk.green('✓') : chalk.red('✗')} cloudflared ${results.cloudflared ? chalk.dim('found') : chalk.red('missing')}`);
|
|
79
|
-
console.log('');
|
|
80
|
-
|
|
81
|
-
const missing = Object.entries(results)
|
|
82
|
-
.filter(([, available]) => !available)
|
|
83
|
-
.map(([name]) => name);
|
|
84
|
-
if (missing.length > 0) {
|
|
85
|
-
printInstallGuidance(osInfo, missing);
|
|
86
|
-
} else {
|
|
87
|
-
console.log(chalk.green(' ✅ All dependencies satisfied!\n'));
|
|
88
|
-
}
|
|
89
|
-
return results;
|
|
90
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|