@indigoai-us/hq-cli 5.173.0 → 5.174.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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.174.0] — 2026-09-24
6
+
7
+ ### Fixed
8
+
9
+ - Outpost worktree pruning now tolerates cwd `EACCES` only for the verified
10
+ same-user systemd `(sd-pam)` helper. Other same-user cwd failures and all fd
11
+ scan failures remain fail-closed. This entry was listed under 5.173.0 by
12
+ mistake: 5.173.0 was tagged before the change merged, so it first ships here.
13
+
5
14
  ## [5.173.0] — 2026-09-24
6
15
 
7
16
  ### Fixed
@@ -46,6 +55,7 @@
46
55
 
47
56
  - Switching the HQ session company (including `hq-session.sh set company_slug`) clears the Work Mesh project and task, posts `session_end` to the previous company, and holds a personal target as `needs_company`.
48
57
 
58
+
49
59
  ## [5.169.0] — 2026-09-24
50
60
 
51
61
  ### Fixed
@@ -22,6 +22,8 @@ export interface ProcessPathReader {
22
22
  readdirSync(path: string): string[];
23
23
  readlinkSync(path: string): string;
24
24
  uid(path: string): number;
25
+ /** Test seam for read-only process identity checks used by the cwd exception. */
26
+ readFileSync?(path: string): string;
25
27
  }
26
28
  export interface PackageStorePruneResult {
27
29
  attempted: boolean;
@@ -521,7 +521,71 @@ const defaultProcessPathReader = {
521
521
  readdirSync: (entryPath) => fs.readdirSync(entryPath),
522
522
  readlinkSync: (entryPath) => fs.readlinkSync(entryPath),
523
523
  uid: (entryPath) => fs.statSync(entryPath).uid,
524
+ readFileSync: (entryPath) => fs.readFileSync(entryPath, "utf8"),
524
525
  };
526
+ function readProcFile(reader, procRoot, pid, name) {
527
+ if (!reader.readFileSync)
528
+ return null;
529
+ try {
530
+ return reader.readFileSync(path.join(procRoot, pid, name));
531
+ }
532
+ catch {
533
+ // Missing or unreadable identity metadata disables the exception.
534
+ return null;
535
+ }
536
+ }
537
+ function procStatusIdentity(contents) {
538
+ if (contents === null)
539
+ return null;
540
+ const value = (name) => {
541
+ const prefix = `${name}:`;
542
+ const line = contents.split(/\r?\n/).find((entry) => entry.startsWith(prefix));
543
+ return line?.slice(prefix.length).trim() || null;
544
+ };
545
+ const numeric = (name) => {
546
+ const raw = value(name);
547
+ if (!raw)
548
+ return null;
549
+ const parsed = Number(raw.split(/\s+/)[0]);
550
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
551
+ };
552
+ const name = value("Name");
553
+ const pid = numeric("Pid");
554
+ const parentPid = numeric("PPid");
555
+ const uid = numeric("Uid");
556
+ if (name === null || pid === null || parentPid === null || uid === null)
557
+ return null;
558
+ return { name, pid, parentPid, uid };
559
+ }
560
+ /**
561
+ * `sd-pam` is systemd's non-dumpable PAM-session helper. Its cwd link can return
562
+ * EACCES to its own UID, so require the exact proc identity and user-manager
563
+ * ancestry before treating that one cwd as harmless.
564
+ */
565
+ function isSystemdUserSdPamProcess(procRoot, pid, currentUid, ownerUid, reader) {
566
+ if (currentUid === null || ownerUid !== currentUid)
567
+ return false;
568
+ const helper = procStatusIdentity(readProcFile(reader, procRoot, pid, "status"));
569
+ if (!helper || helper.name !== "(sd-pam)" || helper.pid !== Number(pid) || helper.uid !== currentUid ||
570
+ readProcFile(reader, procRoot, pid, "comm")?.trim() !== "(sd-pam)")
571
+ return false;
572
+ const managerPid = String(helper.parentPid);
573
+ const manager = procStatusIdentity(readProcFile(reader, procRoot, managerPid, "status"));
574
+ if (!manager || manager.name !== "systemd" || manager.pid !== helper.parentPid || manager.parentPid !== 1 ||
575
+ manager.uid !== currentUid || readProcFile(reader, procRoot, managerPid, "comm")?.trim() !== "systemd")
576
+ return false;
577
+ const init = procStatusIdentity(readProcFile(reader, procRoot, "1", "status"));
578
+ if (!init || init.name !== "systemd" || init.pid !== 1 || init.parentPid !== 0 || init.uid !== 0 ||
579
+ readProcFile(reader, procRoot, "1", "comm")?.trim() !== "systemd")
580
+ return false;
581
+ try {
582
+ return reader.uid(path.join(procRoot, managerPid)) === currentUid && reader.uid(path.join(procRoot, "1")) === 0;
583
+ }
584
+ catch {
585
+ // A vanished or inaccessible parent does not prove the helper identity.
586
+ return false;
587
+ }
588
+ }
525
589
  function scanProcessPaths(procRoot, childName, currentUid = currentProcessUserId(), reader = defaultProcessPathReader) {
526
590
  const paths = [];
527
591
  let entries;
@@ -547,7 +611,7 @@ function scanProcessPaths(procRoot, childName, currentUid = currentProcessUserId
547
611
  continue;
548
612
  }
549
613
  const base = path.join(processDirectory, childName);
550
- // An inaccessible foreign UID cannot own this user's worktree; same-user failures stay fail-closed.
614
+ // Foreign UID access stays ignorable; same-UID EACCES is excepted only for a verified sd-pam cwd below.
551
615
  let descriptors;
552
616
  if (childName === "fd") {
553
617
  try {
@@ -573,8 +637,10 @@ function scanProcessPaths(procRoot, childName, currentUid = currentProcessUserId
573
637
  }
574
638
  catch (err) {
575
639
  const code = err.code;
576
- if (code !== "ENOENT" && code !== "ESRCH" &&
577
- !(code === "EACCES" && currentUid !== null && ownerUid !== currentUid))
640
+ const inaccessibleForeign = code === "EACCES" && currentUid !== null && ownerUid !== currentUid;
641
+ const knownSystemdCwd = code === "EACCES" && childName === "cwd" &&
642
+ isSystemdUserSdPamProcess(procRoot, pid, currentUid, ownerUid, reader);
643
+ if (code !== "ENOENT" && code !== "ESRCH" && !inaccessibleForeign && !knownSystemdCwd)
578
644
  complete = false;
579
645
  }
580
646
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.173.0",
3
+ "version": "5.174.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {