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 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 = socket.remoteAddress ? `${socket.remoteAddress}:${socket.remotePort}` : "unknown";
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
- peer.socket.write(signMessage("auth-fail", { ok: false, reason: "Expected auth message first" }, this.hmacKey));
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
- peer.socket.write(signMessage("auth-fail", { ok: false, reason: "Invalid auth payload" }, this.hmacKey));
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
- peer.socket.write(signMessage("auth-fail", { ok: false, reason: "Invalid nonce" }, this.hmacKey));
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
- peer.socket.write(signMessage("auth-fail", { ok: false, reason: "Invalid password" }, this.hmacKey));
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
- peer.socket.write(signMessage("auth-ok", { ok: true }, this.hmacKey));
2230
- this.log(chalk6.green(`Peer authenticated: ${peer.socket.remoteAddress}`));
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.socket.remoteAddress ?? peer.ip);
2241
+ this.emitPeerEvent("auth", peer.ip);
2233
2242
  this.startDataStream(peer);
2234
2243
  this.startPing(peer);
2235
2244
  } catch {
2236
- peer.socket.write(signMessage("auth-fail", { ok: false, reason: "Invalid message format" }, this.hmacKey));
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.destroyed) {
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.socket.remoteAddress}`));
2266
- this.emitPeerEvent("disconnect", peer.socket.remoteAddress ?? peer.ip);
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: ${peer.socket.remoteAddress}`));
2287
+ this.log(chalk6.yellow(`Peer connection reset: ${addr}`));
2273
2288
  this.audit(peer.ip, "ERROR", "Connection reset");
2274
- this.emitPeerEvent("error", `connection reset: ${peer.socket.remoteAddress}`);
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
- this.socket.write(message);
2500
+ try {
2501
+ this.socket.write(message);
2502
+ } catch {
2503
+ }
2484
2504
  }
2485
2505
  }
2486
2506
  startPing() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyre-cli",
3
- "version": "3.8.0",
3
+ "version": "3.10.0",
4
4
  "description": "CLI Mac monitoring tool: temps, system stats, live dashboard, packet monitor, battery predictor, export",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
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
- this.socket.write(message);
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.remoteAddress
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
- peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Expected auth message first' }, this.hmacKey));
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
- peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Invalid auth payload' }, this.hmacKey));
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
- peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Invalid nonce' }, this.hmacKey));
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
- peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Invalid password' }, this.hmacKey));
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
- peer.socket.write(signMessage('auth-ok', { ok: true }, this.hmacKey));
341
- this.log(chalk.green(`Peer authenticated: ${peer.socket.remoteAddress}`));
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.socket.remoteAddress ?? peer.ip);
351
+ this.emitPeerEvent('auth', peer.ip);
344
352
  this.startDataStream(peer);
345
353
  this.startPing(peer);
346
354
  } catch {
347
- peer.socket.write(signMessage('auth-fail', { ok: false, reason: 'Invalid message format' }, this.hmacKey));
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.destroyed) {
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.socket.remoteAddress}`));
383
- this.emitPeerEvent('disconnect', peer.socket.remoteAddress ?? peer.ip);
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: ${peer.socket.remoteAddress}`));
405
+ this.log(chalk.yellow(`Peer connection reset: ${addr}`));
391
406
  this.audit(peer.ip, 'ERROR', 'Connection reset');
392
- this.emitPeerEvent('error', `connection reset: ${peer.socket.remoteAddress}`);
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);