@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;
|
|
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,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() {} }); }',
|