@wrongstack/tools 0.274.0 → 0.275.1

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/test.js CHANGED
@@ -268,6 +268,7 @@ var CircuitBreaker = class {
268
268
  if (this.state === "open") return;
269
269
  this.state = "open";
270
270
  this.openedAt = Date.now();
271
+ this.window = [];
271
272
  try {
272
273
  this.onTrip?.();
273
274
  } catch {
@@ -307,7 +308,7 @@ var SENSITIVE_FLAG_PATTERNS = [
307
308
  /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
308
309
  // -f "value" style short flags
309
310
  /(?<!\w)-t(?:\s+|\s*=\s*)[^\s,]+/,
310
- /(?<!\w)-p(?:ssword)?(?:\s+|\s*=\s*)[^\s,]+/gi,
311
+ /(?<!\w)-(?:p|password)(?:\s+|\s*=\s*)[^\s,]+/gi,
311
312
  // env var–style secrets: TOKEN=x, API_KEY=y, etc.
312
313
  /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
313
314
  // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
@@ -368,12 +369,35 @@ var ProcessRegistryImpl = class {
368
369
  register(info) {
369
370
  this.processes.set(info.pid, { ...info, killed: false, protected: info.protected ?? false });
370
371
  }
372
+ _isSafeSignalPid(pid) {
373
+ return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
374
+ }
375
+ _canSignalProcessGroup(p) {
376
+ return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
377
+ }
378
+ _killChildDirect(p, signal) {
379
+ try {
380
+ p.child.kill(signal);
381
+ } catch {
382
+ }
383
+ }
384
+ _killPosix(p, signal) {
385
+ if (this._canSignalProcessGroup(p)) {
386
+ try {
387
+ process.kill(-p.pid, signal);
388
+ return;
389
+ } catch {
390
+ }
391
+ }
392
+ this._killChildDirect(p, signal);
393
+ }
371
394
  /** Unregister a process by PID. Called on 'close' / 'exit' events. */
372
395
  unregister(pid) {
373
396
  this.processes.delete(pid);
374
397
  }
375
398
  /** Get a single process by PID. */
376
399
  get(pid) {
400
+ this._pruneStale(pid);
377
401
  return this.processes.get(pid);
378
402
  }
379
403
  /** Get all tracked processes. */
@@ -537,6 +561,7 @@ var ProcessRegistryImpl = class {
537
561
  * Returns true if the process was found and kill was attempted.
538
562
  */
539
563
  kill(pid, opts = {}) {
564
+ this._pruneStale(pid);
540
565
  const p = this.processes.get(pid);
541
566
  if (!p) return false;
542
567
  if (p.killed) return true;
@@ -566,27 +591,12 @@ var ProcessRegistryImpl = class {
566
591
  }
567
592
  try {
568
593
  if (force) {
569
- try {
570
- process.kill(-pid, "SIGKILL");
571
- } catch {
572
- p.child.kill("SIGKILL");
573
- }
594
+ this._killPosix(p, "SIGKILL");
574
595
  } else {
575
- try {
576
- process.kill(-pid, "SIGTERM");
577
- } catch {
578
- p.child.kill("SIGTERM");
579
- }
596
+ this._killPosix(p, "SIGTERM");
580
597
  const timer = setTimeout(() => {
581
598
  if (this.processes.has(pid) && !p.child.killed) {
582
- try {
583
- process.kill(-pid, "SIGKILL");
584
- } catch {
585
- try {
586
- p.child.kill("SIGKILL");
587
- } catch {
588
- }
589
- }
599
+ this._killPosix(p, "SIGKILL");
590
600
  }
591
601
  }, graceMs);
592
602
  timer.unref?.();
@@ -621,6 +631,34 @@ var ProcessRegistryImpl = class {
621
631
  }
622
632
  return killed;
623
633
  }
634
+ /**
635
+ * Check whether a tracked process entry is stale — the child has exited
636
+ * (exitCode !== null) AND it's been in the registry long enough that the
637
+ * OS may have reused the PID for a new, unrelated process.
638
+ *
639
+ * P3 #24 (before-release.md): on POSIX, PIDs are reused after process
640
+ * exit. If a tracked process exits but its 'close' event hasn't fired yet
641
+ * (or was missed), the registry still holds the entry. A new process
642
+ * gets the same PID, and PID-based lookups (get, kill, shouldBlockKill)
643
+ * may incorrectly protect or target the wrong process.
644
+ *
645
+ * The 60s threshold is conservative — the OS typically waits much longer
646
+ * before reusing a PID, but we want to clean up before that becomes a risk.
647
+ */
648
+ _isStaleEntry(entry) {
649
+ return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
650
+ }
651
+ /**
652
+ * Remove a stale entry for a specific PID before any PID-based lookup.
653
+ * This prevents PID reuse from causing the registry to act on a dead
654
+ * process that has been replaced by a new one with the same PID.
655
+ */
656
+ _pruneStale(pid) {
657
+ const entry = this.processes.get(pid);
658
+ if (entry && this._isStaleEntry(entry)) {
659
+ this.processes.delete(pid);
660
+ }
661
+ }
624
662
  };
625
663
  var _registry;
626
664
  function getProcessRegistry() {