pyre-cli 3.8.0 → 3.10.0
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/dist/index.js +35 -15
- package/package.json +1 -1
- package/src/p2p/client.ts +5 -1
- package/src/p2p/server.ts +33 -16
package/dist/index.js
CHANGED
|
@@ -2136,6 +2136,11 @@ var P2PServer = class {
|
|
|
2136
2136
|
}
|
|
2137
2137
|
onConnection(socket) {
|
|
2138
2138
|
const ip = this.getPeerIP(socket);
|
|
2139
|
+
if (!socket.remoteAddress) {
|
|
2140
|
+
this.audit(ip, "CONNECT", "No remote address");
|
|
2141
|
+
socket.destroy();
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2139
2144
|
const allowedIPs = this.options.allowedIPs ?? [];
|
|
2140
2145
|
const deniedIPs = this.options.deniedIPs ?? [];
|
|
2141
2146
|
if (!isIPAllowed(ip, allowedIPs, deniedIPs)) {
|
|
@@ -2164,7 +2169,7 @@ var P2PServer = class {
|
|
|
2164
2169
|
pendingNonce: null
|
|
2165
2170
|
};
|
|
2166
2171
|
this.peers.set(socket, peer);
|
|
2167
|
-
const remoteAddr =
|
|
2172
|
+
const remoteAddr = `${ip}:${socket.remotePort ?? "?"}`;
|
|
2168
2173
|
this.log(chalk6.cyan(`Peer connected: ${remoteAddr}`));
|
|
2169
2174
|
this.audit(ip, "CONNECT", remoteAddr);
|
|
2170
2175
|
this.emitPeerEvent("connect", remoteAddr);
|
|
@@ -2196,50 +2201,59 @@ var P2PServer = class {
|
|
|
2196
2201
|
const raw = body.toString("utf-8");
|
|
2197
2202
|
const msg = decodeMessage(body);
|
|
2198
2203
|
if (msg.kind !== "auth") {
|
|
2199
|
-
|
|
2204
|
+
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Expected auth message first" }, this.hmacKey));
|
|
2200
2205
|
this.disconnectPeer(peer, "Expected auth message");
|
|
2201
2206
|
this.emitPeerEvent("error", `Expected auth message from ${peer.ip}`);
|
|
2207
|
+
this.log(chalk6.red(`Auth failed from ${peer.ip}: expected auth message first`));
|
|
2202
2208
|
return;
|
|
2203
2209
|
}
|
|
2204
2210
|
const authPayload = msg.payload;
|
|
2205
2211
|
if (!authPayload.passwordHash || !authPayload.nonce) {
|
|
2206
|
-
|
|
2212
|
+
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Invalid auth payload" }, this.hmacKey));
|
|
2207
2213
|
this.disconnectPeer(peer, "Invalid auth payload");
|
|
2208
2214
|
this.emitPeerEvent("error", `Invalid auth payload from ${peer.ip}`);
|
|
2215
|
+
this.log(chalk6.red(`Auth failed from ${peer.ip}: invalid auth payload`));
|
|
2209
2216
|
return;
|
|
2210
2217
|
}
|
|
2211
2218
|
if (peer.pendingNonce && authPayload.nonce !== peer.pendingNonce) {
|
|
2212
2219
|
this.audit(peer.ip, "AUTH_FAIL", "Nonce mismatch");
|
|
2213
|
-
|
|
2220
|
+
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Invalid nonce" }, this.hmacKey));
|
|
2214
2221
|
this.disconnectPeer(peer, "Invalid nonce");
|
|
2215
2222
|
this.emitPeerEvent("error", `Invalid nonce from ${peer.ip}`);
|
|
2223
|
+
this.log(chalk6.red(`Auth failed from ${peer.ip}: invalid nonce`));
|
|
2216
2224
|
return;
|
|
2217
2225
|
}
|
|
2218
2226
|
const expectedHash = hashPassword(this.options.password, authPayload.nonce);
|
|
2219
2227
|
if (authPayload.passwordHash !== expectedHash) {
|
|
2220
2228
|
this.audit(peer.ip, "AUTH_FAIL", "Invalid password hash");
|
|
2221
|
-
|
|
2229
|
+
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Invalid password" }, this.hmacKey));
|
|
2222
2230
|
this.disconnectPeer(peer, "Authentication failed");
|
|
2223
2231
|
this.emitPeerEvent("error", `Invalid password from ${peer.ip}`);
|
|
2232
|
+
this.log(chalk6.red(`Auth failed from ${peer.ip}: invalid password`));
|
|
2224
2233
|
return;
|
|
2225
2234
|
}
|
|
2226
2235
|
peer.authenticated = true;
|
|
2227
2236
|
this.cleanupRateLimit(peer.ip);
|
|
2228
2237
|
peer.pendingNonce = null;
|
|
2229
|
-
|
|
2230
|
-
this.log(chalk6.green(`Peer authenticated: ${peer.
|
|
2238
|
+
this.sendSigned(peer, signMessage("auth-ok", { ok: true }, this.hmacKey));
|
|
2239
|
+
this.log(chalk6.green(`Peer authenticated: ${peer.ip}`));
|
|
2231
2240
|
this.audit(peer.ip, "AUTH_OK", "Challenge-response successful");
|
|
2232
|
-
this.emitPeerEvent("auth", peer.
|
|
2241
|
+
this.emitPeerEvent("auth", peer.ip);
|
|
2233
2242
|
this.startDataStream(peer);
|
|
2234
2243
|
this.startPing(peer);
|
|
2235
2244
|
} catch {
|
|
2236
|
-
|
|
2245
|
+
this.sendSigned(peer, signMessage("auth-fail", { ok: false, reason: "Invalid message format" }, this.hmacKey));
|
|
2237
2246
|
this.disconnectPeer(peer, "Invalid message format");
|
|
2247
|
+
this.log(chalk6.red(`Auth failed from ${peer.ip}: invalid message format`));
|
|
2238
2248
|
}
|
|
2239
2249
|
}
|
|
2240
2250
|
sendSigned(peer, message) {
|
|
2241
|
-
if (!peer.socket.
|
|
2251
|
+
if (peer.socket.destroyed || !peer.socket.writable) {
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
try {
|
|
2242
2255
|
peer.socket.write(message);
|
|
2256
|
+
} catch {
|
|
2243
2257
|
}
|
|
2244
2258
|
}
|
|
2245
2259
|
startDataStream(peer) {
|
|
@@ -2262,16 +2276,19 @@ var P2PServer = class {
|
|
|
2262
2276
|
}
|
|
2263
2277
|
onPeerClose(peer) {
|
|
2264
2278
|
this.cleanupPeer(peer);
|
|
2265
|
-
this.log(chalk6.yellow(`Peer disconnected: ${peer.
|
|
2266
|
-
this.emitPeerEvent("disconnect", peer.
|
|
2279
|
+
this.log(chalk6.yellow(`Peer disconnected: ${peer.ip}`));
|
|
2280
|
+
this.emitPeerEvent("disconnect", peer.ip);
|
|
2267
2281
|
this.audit(peer.ip, "DISCONNECT", "Peer closed connection");
|
|
2268
2282
|
}
|
|
2269
2283
|
onPeerError(peer, err) {
|
|
2284
|
+
const addr = peer.socket.remoteAddress ?? peer.ip;
|
|
2270
2285
|
const code = err.code;
|
|
2271
2286
|
if (code === "ECONNRESET") {
|
|
2272
|
-
this.log(chalk6.yellow(`Peer connection reset: ${
|
|
2287
|
+
this.log(chalk6.yellow(`Peer connection reset: ${addr}`));
|
|
2273
2288
|
this.audit(peer.ip, "ERROR", "Connection reset");
|
|
2274
|
-
this.emitPeerEvent("error", `connection reset: ${
|
|
2289
|
+
this.emitPeerEvent("error", `connection reset: ${addr}`);
|
|
2290
|
+
} else if (code === "EPIPE") {
|
|
2291
|
+
this.audit(peer.ip, "ERROR", "Broken pipe");
|
|
2275
2292
|
} else {
|
|
2276
2293
|
this.log(chalk6.red(`Peer error: ${err.message}`));
|
|
2277
2294
|
this.audit(peer.ip, "ERROR", err.message);
|
|
@@ -2480,7 +2497,10 @@ var P2PClient = class {
|
|
|
2480
2497
|
}
|
|
2481
2498
|
sendSigned(message) {
|
|
2482
2499
|
if (this.socket && !this.socket.destroyed) {
|
|
2483
|
-
|
|
2500
|
+
try {
|
|
2501
|
+
this.socket.write(message);
|
|
2502
|
+
} catch {
|
|
2503
|
+
}
|
|
2484
2504
|
}
|
|
2485
2505
|
}
|
|
2486
2506
|
startPing() {
|
package/package.json
CHANGED
package/src/p2p/client.ts
CHANGED
|
@@ -207,7 +207,11 @@ export class P2PClient {
|
|
|
207
207
|
|
|
208
208
|
private sendSigned(message: Buffer): void {
|
|
209
209
|
if (this.socket && !this.socket.destroyed) {
|
|
210
|
-
|
|
210
|
+
try {
|
|
211
|
+
this.socket.write(message);
|
|
212
|
+
} catch {
|
|
213
|
+
// ignore write errors; socket likely closed
|
|
214
|
+
}
|
|
211
215
|
}
|
|
212
216
|
}
|
|
213
217
|
|
package/src/p2p/server.ts
CHANGED
|
@@ -231,6 +231,12 @@ export class P2PServer {
|
|
|
231
231
|
private onConnection(socket: net.Socket): void {
|
|
232
232
|
const ip = this.getPeerIP(socket);
|
|
233
233
|
|
|
234
|
+
if (!socket.remoteAddress) {
|
|
235
|
+
this.audit(ip, 'CONNECT', 'No remote address');
|
|
236
|
+
socket.destroy();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
234
240
|
const allowedIPs = this.options.allowedIPs ?? [];
|
|
235
241
|
const deniedIPs = this.options.deniedIPs ?? [];
|
|
236
242
|
|
|
@@ -263,9 +269,7 @@ export class P2PServer {
|
|
|
263
269
|
};
|
|
264
270
|
this.peers.set(socket, peer);
|
|
265
271
|
|
|
266
|
-
const remoteAddr = socket.
|
|
267
|
-
? `${socket.remoteAddress}:${socket.remotePort}`
|
|
268
|
-
: 'unknown';
|
|
272
|
+
const remoteAddr = `${ip}:${socket.remotePort ?? '?'}`;
|
|
269
273
|
this.log(chalk.cyan(`Peer connected: ${remoteAddr}`));
|
|
270
274
|
this.audit(ip, 'CONNECT', remoteAddr);
|
|
271
275
|
this.emitPeerEvent('connect', remoteAddr);
|
|
@@ -303,55 +307,65 @@ export class P2PServer {
|
|
|
303
307
|
const raw = body.toString('utf-8');
|
|
304
308
|
const msg = decodeMessage(body);
|
|
305
309
|
if (msg.kind !== 'auth') {
|
|
306
|
-
|
|
310
|
+
this.sendSigned(peer, signMessage('auth-fail', { ok: false, reason: 'Expected auth message first' }, this.hmacKey));
|
|
307
311
|
this.disconnectPeer(peer, 'Expected auth message');
|
|
308
312
|
this.emitPeerEvent('error', `Expected auth message from ${peer.ip}`);
|
|
313
|
+
this.log(chalk.red(`Auth failed from ${peer.ip}: expected auth message first`));
|
|
309
314
|
return;
|
|
310
315
|
}
|
|
311
316
|
|
|
312
317
|
const authPayload = msg.payload as { passwordHash?: string; nonce?: string };
|
|
313
318
|
if (!authPayload.passwordHash || !authPayload.nonce) {
|
|
314
|
-
|
|
319
|
+
this.sendSigned(peer, signMessage('auth-fail', { ok: false, reason: 'Invalid auth payload' }, this.hmacKey));
|
|
315
320
|
this.disconnectPeer(peer, 'Invalid auth payload');
|
|
316
321
|
this.emitPeerEvent('error', `Invalid auth payload from ${peer.ip}`);
|
|
322
|
+
this.log(chalk.red(`Auth failed from ${peer.ip}: invalid auth payload`));
|
|
317
323
|
return;
|
|
318
324
|
}
|
|
319
325
|
|
|
320
326
|
if (peer.pendingNonce && authPayload.nonce !== peer.pendingNonce) {
|
|
321
327
|
this.audit(peer.ip, 'AUTH_FAIL', 'Nonce mismatch');
|
|
322
|
-
|
|
328
|
+
this.sendSigned(peer, signMessage('auth-fail', { ok: false, reason: 'Invalid nonce' }, this.hmacKey));
|
|
323
329
|
this.disconnectPeer(peer, 'Invalid nonce');
|
|
324
330
|
this.emitPeerEvent('error', `Invalid nonce from ${peer.ip}`);
|
|
331
|
+
this.log(chalk.red(`Auth failed from ${peer.ip}: invalid nonce`));
|
|
325
332
|
return;
|
|
326
333
|
}
|
|
327
334
|
|
|
328
335
|
const expectedHash = hashPassword(this.options.password, authPayload.nonce);
|
|
329
336
|
if (authPayload.passwordHash !== expectedHash) {
|
|
330
337
|
this.audit(peer.ip, 'AUTH_FAIL', 'Invalid password hash');
|
|
331
|
-
|
|
338
|
+
this.sendSigned(peer, signMessage('auth-fail', { ok: false, reason: 'Invalid password' }, this.hmacKey));
|
|
332
339
|
this.disconnectPeer(peer, 'Authentication failed');
|
|
333
340
|
this.emitPeerEvent('error', `Invalid password from ${peer.ip}`);
|
|
341
|
+
this.log(chalk.red(`Auth failed from ${peer.ip}: invalid password`));
|
|
334
342
|
return;
|
|
335
343
|
}
|
|
336
344
|
|
|
337
345
|
peer.authenticated = true;
|
|
338
346
|
this.cleanupRateLimit(peer.ip);
|
|
339
347
|
peer.pendingNonce = null;
|
|
340
|
-
|
|
341
|
-
this.log(chalk.green(`Peer authenticated: ${peer.
|
|
348
|
+
this.sendSigned(peer, signMessage('auth-ok', { ok: true }, this.hmacKey));
|
|
349
|
+
this.log(chalk.green(`Peer authenticated: ${peer.ip}`));
|
|
342
350
|
this.audit(peer.ip, 'AUTH_OK', 'Challenge-response successful');
|
|
343
|
-
this.emitPeerEvent('auth', peer.
|
|
351
|
+
this.emitPeerEvent('auth', peer.ip);
|
|
344
352
|
this.startDataStream(peer);
|
|
345
353
|
this.startPing(peer);
|
|
346
354
|
} catch {
|
|
347
|
-
|
|
355
|
+
this.sendSigned(peer, signMessage('auth-fail', { ok: false, reason: 'Invalid message format' }, this.hmacKey));
|
|
348
356
|
this.disconnectPeer(peer, 'Invalid message format');
|
|
357
|
+
this.log(chalk.red(`Auth failed from ${peer.ip}: invalid message format`));
|
|
349
358
|
}
|
|
350
359
|
}
|
|
351
360
|
|
|
352
361
|
private sendSigned(peer: Peer, message: Buffer): void {
|
|
353
|
-
if (!peer.socket.
|
|
362
|
+
if (peer.socket.destroyed || !peer.socket.writable) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
354
366
|
peer.socket.write(message);
|
|
367
|
+
} catch {
|
|
368
|
+
// ignore write errors; peer likely disconnected
|
|
355
369
|
}
|
|
356
370
|
}
|
|
357
371
|
|
|
@@ -379,17 +393,20 @@ export class P2PServer {
|
|
|
379
393
|
|
|
380
394
|
private onPeerClose(peer: Peer): void {
|
|
381
395
|
this.cleanupPeer(peer);
|
|
382
|
-
this.log(chalk.yellow(`Peer disconnected: ${peer.
|
|
383
|
-
this.emitPeerEvent('disconnect', peer.
|
|
396
|
+
this.log(chalk.yellow(`Peer disconnected: ${peer.ip}`));
|
|
397
|
+
this.emitPeerEvent('disconnect', peer.ip);
|
|
384
398
|
this.audit(peer.ip, 'DISCONNECT', 'Peer closed connection');
|
|
385
399
|
}
|
|
386
400
|
|
|
387
401
|
private onPeerError(peer: Peer, err: Error): void {
|
|
402
|
+
const addr = peer.socket.remoteAddress ?? peer.ip;
|
|
388
403
|
const code = (err as any).code;
|
|
389
404
|
if (code === 'ECONNRESET') {
|
|
390
|
-
this.log(chalk.yellow(`Peer connection reset: ${
|
|
405
|
+
this.log(chalk.yellow(`Peer connection reset: ${addr}`));
|
|
391
406
|
this.audit(peer.ip, 'ERROR', 'Connection reset');
|
|
392
|
-
this.emitPeerEvent('error', `connection reset: ${
|
|
407
|
+
this.emitPeerEvent('error', `connection reset: ${addr}`);
|
|
408
|
+
} else if (code === 'EPIPE') {
|
|
409
|
+
this.audit(peer.ip, 'ERROR', 'Broken pipe');
|
|
393
410
|
} else {
|
|
394
411
|
this.log(chalk.red(`Peer error: ${err.message}`));
|
|
395
412
|
this.audit(peer.ip, 'ERROR', err.message);
|