@danypops/pi-packed 0.28.2 → 0.28.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"smoke.d.ts","sourceRoot":"","sources":["../../service/src/adoption/smoke.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,mBAAmB,GAAG,cAAc,GAAG,qBAAqB,CAAC;AAEpH,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,kBAAkB,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAuDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO7F;AAmJD,wBAAsB,iBAAiB,CACtC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAoF/B"}
1
+ {"version":3,"file":"smoke.d.ts","sourceRoot":"","sources":["../../service/src/adoption/smoke.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,mBAAmB,GAAG,cAAc,GAAG,qBAAqB,CAAC;AAEpH,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,kBAAkB,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAuDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO7F;AAsLD,wBAAsB,iBAAiB,CACtC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAoF/B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-packed",
3
- "version": "0.28.2",
3
+ "version": "0.28.3",
4
4
  "description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- import { existsSync, realpathSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import { dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -105,6 +105,41 @@ export function resolveDependencyModulesDir(fromDir: string, name: string): stri
105
105
  }
106
106
  }
107
107
 
108
+ const MAX_PROCESS_SCAN_ENTRIES = 100_000;
109
+
110
+ /**
111
+ * RLIMIT_NPROC (prlimit's own --nproc) is a per-real-UID, system-wide limit -- NOT scoped to
112
+ * this sandbox's own process tree. A small absolute constant breaks the instant the invoking
113
+ * user's REAL total process count already exceeds it (routine on a heavily-used workstation
114
+ * with several long-running agent sessions): any further thread/fork Bun's own runtime needs
115
+ * internally (confirmed live: its native TS transpiler spawns one) immediately fails, surfacing
116
+ * as an unrecoverable SIGABRT (exit 134) with no output at all, not a catchable error. Correct
117
+ * enforcement of "at most N NEW processes the sandboxed extension can spawn" requires an offset
118
+ * from the CURRENT real count, not an absolute cap. Returns 0 (never throws) if /proc can't be
119
+ * read at all -- the caller's own maxProcesses then applies as an absolute floor, same as before
120
+ * this existed.
121
+ */
122
+ function currentProcessCountForUid(uid: number): number {
123
+ let entries: string[];
124
+ try {
125
+ entries = readdirSync("/proc");
126
+ } catch {
127
+ return 0;
128
+ }
129
+ let count = 0;
130
+ for (const entry of entries.slice(0, MAX_PROCESS_SCAN_ENTRIES)) {
131
+ if (!/^\d+$/.test(entry)) continue;
132
+ try {
133
+ const status = readFileSync(`/proc/${entry}/status`, "utf8");
134
+ const match = status.match(/^Uid:\s+(\d+)/m);
135
+ if (match && Number(match[1]) === uid) count++;
136
+ } catch {
137
+ // The process exited mid-scan, or is otherwise unreadable -- not this sandbox's concern.
138
+ }
139
+ }
140
+ return count;
141
+ }
142
+
108
143
  function sandboxCommand(packageRoot: string, extensionPath: string, maxProcesses: number): string[] | undefined {
109
144
  if (process.platform !== "linux" || !existsSync("/usr/bin/bwrap") || !existsSync("/usr/bin/prlimit")) return undefined;
110
145
  const serviceRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
@@ -185,7 +220,7 @@ function sandboxCommand(packageRoot: string, extensionPath: string, maxProcesses
185
220
  packageRoot,
186
221
  "--",
187
222
  "/usr/bin/prlimit",
188
- `--nproc=${maxProcesses}`,
223
+ `--nproc=${(typeof process.getuid === "function" ? currentProcessCountForUid(process.getuid()) : 0) + maxProcesses}`,
189
224
  "--nofile=64",
190
225
  "--fsize=16777216",
191
226
  "--as=8589934592",
@@ -213,6 +213,24 @@ describeIfSandboxed("isolated extension smoke runner", () => {
213
213
  expect(result.registrations.commands).toEqual(["object"]);
214
214
  });
215
215
 
216
+ it("never breaks under a real per-UID process count that already exceeds the sandbox's own maxProcesses budget -- confirmed live: prlimit's --nproc is a per-real-UID, system-wide limit, not scoped to this sandbox's own process tree, so a small absolute cap unconditionally breaks the instant the invoking user's REAL total process count exceeds it (routine on a busy multi-session workstation) -- any further thread Bun's own runtime needs internally (its native TS transpiler spawns one) immediately fails, aborting with SIGABRT and zero output, not a catchable error", async () => {
217
+ const dummyProcessCount = 40;
218
+ const dummies = Array.from({ length: dummyProcessCount }, () => Bun.spawn(["/usr/bin/sleep", "20"], { stdin: "ignore", stdout: "ignore", stderr: "ignore" }));
219
+ try {
220
+ const fixture = extension(`export default function (pi: any) {
221
+ pi.registerCommand("hello", { handler() {} });
222
+ }`);
223
+
224
+ const result = await runExtensionSmoke(fixture.root, fixture.path, { maxProcesses: 5 });
225
+
226
+ expect(result.status).toBe("ok");
227
+ expect(result.registrations.commands).toEqual(["hello"]);
228
+ } finally {
229
+ for (const dummy of dummies) dummy.kill("SIGKILL");
230
+ await Promise.all(dummies.map((dummy) => dummy.exited));
231
+ }
232
+ });
233
+
216
234
  it("keeps default package checks static and adds smoke results only when requested", async () => {
217
235
  const fixture = extension(
218
236
  'import { writeFileSync } from "node:fs"; export default function (pi: any) { writeFileSync("executed", "yes"); pi.registerCommand("x", { handler() {} }); }',