@miraj181/ipingyou 2.0.5 → 2.0.6

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": "@miraj181/ipingyou",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "SecureLink-CLI — Secure peer-to-peer remote access via SSH & Cloudflare Tunnels",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -9,13 +9,14 @@
9
9
  },
10
10
  "files": [
11
11
  "src/cli.js",
12
+ "src/server.js",
12
13
  "src/lib/",
13
14
  "src/modes/",
14
15
  "README.md",
15
16
  "LICENSE"
16
17
  ],
17
18
  "scripts": {
18
- "start": "node src/cli.js",
19
+ "start": "node src/server.js",
19
20
  "dev:cli": "node src/cli.js",
20
21
  "prepublishOnly": "node -e \"require('fs').accessSync('LICENSE')\" && node src/cli.js --help"
21
22
  },
@@ -50,7 +51,10 @@
50
51
  "open": "^11.0.0",
51
52
  "ora": "^8.1.1",
52
53
  "tree-kill": "^1.2.2",
53
- "ws": "^8.20.1"
54
+ "ws": "^8.20.1",
55
+ "express": "^4.19.2",
56
+ "express-rate-limit": "^7.3.1",
57
+ "helmet": "^7.1.0"
54
58
  },
55
59
  "devDependencies": {
56
60
  "nodemon": "^3.1.4"
@@ -59,4 +63,4 @@
59
63
  "node": ">=18.0.0"
60
64
  },
61
65
  "type": "module"
62
- }
66
+ }
package/src/modes/host.js CHANGED
@@ -449,14 +449,12 @@ async function hostDashboard(uid, tunnelUrl, password, serviceConfig, tunnelProc
449
449
  }
450
450
 
451
451
  case 'exit':
452
- clearInterval(monitorInterval);
453
452
  if (tunnelProcess) tunnelProcess.kill();
454
453
  if (chatTunnelProcess) chatTunnelProcess.kill();
455
454
  if (global.privateBrokerInstance) global.privateBrokerInstance.kill();
456
455
  return;
457
456
  }
458
457
  } catch (err) {
459
- clearInterval(monitorInterval);
460
458
  throw err;
461
459
  }
462
460
  };
package/src/server.js ADDED
@@ -0,0 +1,263 @@
1
+ /**
2
+ * ============================================================
3
+ * SecureLink-CLI — Central Broker Server
4
+ * ============================================================
5
+ * A lightweight REST relay that pairs hosts and clients via
6
+ * temporary UIDs. The broker is a DUMB PIPE — it stores and
7
+ * returns encrypted blobs without ever seeing plaintext.
8
+ * Encryption/decryption happens ONLY on the CLI side.
9
+ * ============================================================
10
+ */
11
+
12
+ import express from 'express';
13
+ import rateLimit from 'express-rate-limit';
14
+ import helmet from 'helmet';
15
+
16
+ const app = express();
17
+
18
+ // ─── Process-Level Error Handling ────────────────────────────
19
+ process.on('uncaughtException', (err) => {
20
+ console.error('💥 Uncaught Exception:', err.message);
21
+ if (err.stack) console.error(err.stack);
22
+ process.exit(1);
23
+ });
24
+
25
+ process.on('unhandledRejection', (reason, promise) => {
26
+ console.error('💥 Unhandled Rejection at:', promise, 'reason:', reason);
27
+ });
28
+
29
+ // ─── Basic Security Headers ──────────────────────────────────
30
+ app.use(helmet());
31
+ app.use(express.json({ limit: '10kb' })); // Limit JSON payload size to prevent payload-based DoS
32
+
33
+ // ─── Rate Limiters ───────────────────────────────────────────
34
+ // Trust proxy is required if the server is behind a reverse proxy (like Render, Heroku, Cloudflare)
35
+ app.set('trust proxy', 1);
36
+
37
+ // General rate limiter for all requests (100 reqs per 15 minutes per IP)
38
+ const generalLimiter = rateLimit({
39
+ windowMs: 15 * 60 * 1000,
40
+ max: 100,
41
+ message: { error: 'Too many requests from this IP, please try again after 15 minutes.' },
42
+ standardHeaders: true,
43
+ legacyHeaders: false,
44
+ });
45
+ app.use(generalLimiter);
46
+
47
+ // Stricter rate limiter for registration/revocation endpoints (e.g. 20 reqs per 15 mins)
48
+ const strictLimiter = rateLimit({
49
+ windowMs: 15 * 60 * 1000,
50
+ max: 20,
51
+ message: { error: 'Too many registration/revocation requests. Please try again later.' },
52
+ standardHeaders: true,
53
+ legacyHeaders: false,
54
+ });
55
+
56
+ // ─── Active Defense & IP Blacklisting (IDS) ──────────────────
57
+ const ipViolations = new Map(); // ip → violation_count
58
+ const blacklistedIPs = new Set();
59
+ const VIOLATION_THRESHOLD = 5; // Block IP after 5 malicious requests
60
+
61
+ app.use((req, res, next) => {
62
+ const ip = req.ip || req.connection.remoteAddress;
63
+ if (blacklistedIPs.has(ip)) {
64
+ console.warn(`🛡️ Dropped traffic from blacklisted IP: ${ip}`);
65
+ return res.status(403).json({ error: 'Your IP has been banned due to suspicious activity.' });
66
+ }
67
+ next();
68
+ });
69
+
70
+ function recordViolation(req) {
71
+ const ip = req.ip || req.connection.remoteAddress;
72
+ const count = (ipViolations.get(ip) || 0) + 1;
73
+ ipViolations.set(ip, count);
74
+ console.warn(`🚨 Violation recorded for IP ${ip} (${count}/${VIOLATION_THRESHOLD})`);
75
+
76
+ if (count >= VIOLATION_THRESHOLD) {
77
+ blacklistedIPs.add(ip);
78
+ console.error(`💥 HACKING DETECTED: Auto-banned IP ${ip} to defend server.`);
79
+ }
80
+ }
81
+
82
+ // ─── In-memory store — auto-expire after TTL ─────────────────
83
+ const TTL_MS = 60 * 60 * 1000; // 1 hour
84
+ const store = new Map(); // uid → { iv, ciphertext, salt, createdAt, clients: [] }
85
+
86
+ function pruneExpired() {
87
+ const now = Date.now();
88
+ for (const [uid, entry] of store) {
89
+ if (now - entry.createdAt > TTL_MS) {
90
+ store.delete(uid);
91
+ console.log(`🗑️ Expired UID: ${uid}`);
92
+ }
93
+ }
94
+ }
95
+
96
+ // Run pruning every 5 minutes
97
+ setInterval(pruneExpired, 5 * 60 * 1000);
98
+
99
+ // ─── Routes ──────────────────────────────────────────────────
100
+
101
+ // Health
102
+ app.get('/health', (_req, res) => {
103
+ res.json({ status: 'ok', uptime: process.uptime(), activeUIDs: store.size });
104
+ });
105
+
106
+ /**
107
+ * POST /register
108
+ * Body: { uid: string, iv: string, ciphertext: string, salt: string }
109
+ *
110
+ * The broker receives an ALREADY-ENCRYPTED blob from the host CLI.
111
+ * It never decrypts — just stores the { iv, ciphertext } pair.
112
+ */
113
+ app.post('/register', strictLimiter, (req, res) => {
114
+ try {
115
+ const { uid, iv, ciphertext, salt } = req.body;
116
+
117
+ if (!uid || !iv || !ciphertext || !salt) {
118
+ recordViolation(req);
119
+ return res.status(400).json({ error: 'Missing uid, iv, ciphertext, or salt' });
120
+ }
121
+
122
+ if (typeof uid !== 'string' || uid.length < 6 || uid.length > 16) {
123
+ recordViolation(req);
124
+ return res.status(400).json({ error: 'Invalid UID format (6-16 chars)' });
125
+ }
126
+
127
+ // Validate IV format — must be 32 hex chars (16 bytes)
128
+ if (!/^[a-f0-9]{32}$/i.test(iv)) {
129
+ recordViolation(req);
130
+ return res.status(400).json({ error: 'Invalid IV format (expected 32 hex chars)' });
131
+ }
132
+
133
+ // Validate ciphertext is non-empty base64
134
+ if (typeof ciphertext !== 'string' || ciphertext.length === 0) {
135
+ recordViolation(req);
136
+ return res.status(400).json({ error: 'Invalid ciphertext' });
137
+ }
138
+
139
+ // Store the encrypted blob as-is — broker NEVER decrypts
140
+ store.set(uid, {
141
+ iv,
142
+ ciphertext,
143
+ salt,
144
+ createdAt: Date.now(),
145
+ clients: []
146
+ });
147
+
148
+ console.log(`✅ [${new Date().toLocaleTimeString()}] Registered UID: ${uid} (encrypted, ${ciphertext.length} bytes)`);
149
+ res.json({ status: 'registered', uid, expiresIn: '1 hour' });
150
+ } catch (err) {
151
+ console.error('❌ Register error:', err.message);
152
+ res.status(500).json({ error: 'Internal server error' });
153
+ }
154
+ });
155
+
156
+ /**
157
+ * GET /resolve/:uid
158
+ * Returns the ENCRYPTED blob { iv, ciphertext } for a given UID.
159
+ * The client CLI decrypts it locally — the broker never sees plaintext.
160
+ */
161
+ app.get('/resolve/:uid', (req, res) => {
162
+ try {
163
+ const { uid } = req.params;
164
+ const entry = store.get(uid);
165
+
166
+ if (!entry) {
167
+ // Don't strictly record a violation for one typo, but repeated 404s will be caught by rate limiter
168
+ return res.status(404).json({ error: 'UID not found or expired' });
169
+ }
170
+
171
+ // Check TTL
172
+ if (Date.now() - entry.createdAt > TTL_MS) {
173
+ store.delete(uid);
174
+ return res.status(410).json({ error: 'UID expired' });
175
+ }
176
+
177
+ console.log(`🔍 [${new Date().toLocaleTimeString()}] Resolved UID: ${uid} (returning encrypted blob)`);
178
+
179
+ // Return encrypted blob — client decrypts
180
+ res.json({ uid, iv: entry.iv, ciphertext: entry.ciphertext, salt: entry.salt });
181
+ } catch (err) {
182
+ console.error('❌ Resolve error:', err.message);
183
+ res.status(500).json({ error: 'Internal server error' });
184
+ }
185
+ });
186
+
187
+ /**
188
+ * POST /client-info/:uid
189
+ * Clients securely post their encrypted hardware telemetry.
190
+ */
191
+ app.post('/client-info/:uid', generalLimiter, (req, res) => {
192
+ try {
193
+ const { uid } = req.params;
194
+ const { iv, ciphertext, salt } = req.body;
195
+
196
+ const entry = store.get(uid);
197
+ if (!entry) return res.status(404).json({ error: 'UID not found' });
198
+ if (!iv || !ciphertext || !salt) return res.status(400).json({ error: 'Missing encrypted telemetry payload' });
199
+
200
+ entry.clients.push({ iv, ciphertext, salt, seenAt: Date.now() });
201
+
202
+ // Keep max 50 recent client pings to prevent memory leaks
203
+ if (entry.clients.length > 50) entry.clients.shift();
204
+
205
+ res.json({ status: 'ok' });
206
+ } catch (err) {
207
+ res.status(500).json({ error: 'Internal server error' });
208
+ }
209
+ });
210
+
211
+ /**
212
+ * GET /clients/:uid
213
+ * Host retrieves all securely encrypted client telemetry blobs.
214
+ */
215
+ app.get('/clients/:uid', generalLimiter, (req, res) => {
216
+ try {
217
+ const { uid } = req.params;
218
+ const entry = store.get(uid);
219
+
220
+ if (!entry) return res.status(404).json({ error: 'UID not found' });
221
+
222
+ res.json({ clients: entry.clients });
223
+ } catch (err) {
224
+ res.status(500).json({ error: 'Internal server error' });
225
+ }
226
+ });
227
+
228
+ /**
229
+ * DELETE /revoke/:uid
230
+ * Allows host to explicitly remove their UID before expiry.
231
+ */
232
+ app.delete('/revoke/:uid', strictLimiter, (req, res) => {
233
+ const { uid } = req.params;
234
+ const existed = store.delete(uid);
235
+ console.log(`🚫 [${new Date().toLocaleTimeString()}] Revoked UID: ${uid} (existed: ${existed})`);
236
+ res.json({ status: existed ? 'revoked' : 'not_found' });
237
+ });
238
+
239
+ // ─── Global Error Handler ────────────────────────────────────
240
+ app.use((err, req, res, next) => {
241
+ console.error('❌ Express Error:', err.message);
242
+ if (err.stack) console.error(err.stack);
243
+ res.status(err.status || 500).json({ error: 'Internal Server Error' });
244
+ });
245
+
246
+ // ─── Launch ──────────────────────────────────────────────────
247
+ // Render injects PORT; locally we use BROKER_PORT; fallback 4000
248
+ const PORT = process.env.PORT || process.env.BROKER_PORT || 4000;
249
+ const HOST = process.env.HOST || '0.0.0.0';
250
+
251
+ app.listen(PORT, HOST, () => {
252
+ console.log('');
253
+ console.log(' ╔══════════════════════════════════════════╗');
254
+ console.log(' ║ 🔗 SecureLink Broker — Active ║');
255
+ console.log(` ║ 📡 Port: ${String(PORT).padEnd(29)}║`);
256
+ console.log(' ║ 🔒 Zero-Knowledge Encrypted Store ║');
257
+ console.log(' ║ ⏱️ TTL: 1 hour per UID ║');
258
+ console.log(' ║ 🚫 Broker NEVER sees plaintext ║');
259
+ console.log(' ╚══════════════════════════════════════════╝');
260
+ console.log('');
261
+ });
262
+
263
+ export default app;