@smthrs/platform-node 1.0.0-rc.3 → 1.0.0-rc.4
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/bin/darwin-arm64/smithers-jj-export +0 -0
- package/bin/darwin-x64/smithers-jj-export +0 -0
- package/bin/linux-arm64/smithers-jj-export +0 -0
- package/bin/linux-x64/smithers-jj-export +2 -2
- package/bin/win32-x64/smithers-jj-export.exe +0 -0
- package/dist/cjs/ProcessConfinement.d.ts.map +1 -1
- package/dist/cjs/ProcessConfinement.js +6 -1
- package/dist/cjs/ProcessConfinement.js.map +2 -2
- package/dist/cjs/ProcessSandbox.d.ts +16 -2
- package/dist/cjs/ProcessSandbox.d.ts.map +1 -1
- package/dist/cjs/ProcessSandbox.js +17 -2
- package/dist/cjs/ProcessSandbox.js.map +2 -2
- package/dist/cjs/ScopedProcess.d.ts +2 -1
- package/dist/cjs/ScopedProcess.d.ts.map +1 -1
- package/dist/cjs/ScopedProcess.js.map +2 -2
- package/dist/cjs/internal/ProcessSupervisor.d.ts +2 -0
- package/dist/cjs/internal/ProcessSupervisor.d.ts.map +1 -1
- package/dist/cjs/internal/ProcessSupervisor.js +5 -2
- package/dist/cjs/internal/ProcessSupervisor.js.map +2 -2
- package/dist/cjs/internal/SupervisorProgram.d.ts.map +1 -1
- package/dist/cjs/internal/SupervisorProgram.js +23 -3
- package/dist/cjs/internal/SupervisorProgram.js.map +2 -2
- package/dist/esm/ProcessConfinement.d.ts.map +1 -1
- package/dist/esm/ProcessConfinement.js.map +1 -1
- package/dist/esm/ProcessSandbox.d.ts +16 -2
- package/dist/esm/ProcessSandbox.d.ts.map +1 -1
- package/dist/esm/ProcessSandbox.js +33 -4
- package/dist/esm/ProcessSandbox.js.map +1 -1
- package/dist/esm/ScopedProcess.d.ts +2 -1
- package/dist/esm/ScopedProcess.d.ts.map +1 -1
- package/dist/esm/ScopedProcess.js +2 -1
- package/dist/esm/ScopedProcess.js.map +1 -1
- package/dist/esm/internal/ProcessSupervisor.d.ts +2 -0
- package/dist/esm/internal/ProcessSupervisor.d.ts.map +1 -1
- package/dist/esm/internal/ProcessSupervisor.js +9 -2
- package/dist/esm/internal/ProcessSupervisor.js.map +1 -1
- package/dist/esm/internal/SupervisorProgram.d.ts.map +1 -1
- package/dist/esm/internal/SupervisorProgram.js +23 -3
- package/dist/esm/internal/SupervisorProgram.js.map +1 -1
- package/docs/api.md +2 -1
- package/docs/concepts/process-containment.md +8 -0
- package/package.json +4 -4
- package/src/ProcessConfinement.ts +10 -3
- package/src/ProcessSandbox.ts +38 -4
- package/src/ScopedProcess.ts +2 -1
- package/src/internal/ProcessSupervisor.ts +8 -2
- package/src/internal/SupervisorProgram.ts +23 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ProcessSandbox.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * The one operating-system sandbox every confined process goes through.\n *\n * A caller resolves what a run may touch, a {@link Request}: the network\n * posture, the read and write sets, and optionally which mechanism it wants.\n * This module turns the request into an argv the host enforces, or into an\n * {@link Unenforceable} refusal naming what the host lacks. Two callers share\n * it: the build graph confines every target's tool run under its declared\n * inputs and outputs (`@smthrs/targets/ExecSandbox`), and the Node\n * `ProcessConfinement` confines every approved agent command under the\n * grants in force.\n *\n * Three mechanisms, one contract:\n *\n * - Linux: bubblewrap. An empty root contains only enumerated system/runtime\n * paths and declared reads and writes. The workspace and root are remounted\n * read-only after mounting grants. A private tmp supplies HOME. The network\n * namespace is unshared unless explicitly opened; loopback-only is refused.\n * - macOS: seatbelt (`sandbox-exec`). Host reads are denied except enumerated\n * system/runtime paths, declared inputs, external reads, writes and private\n * tmp. Host credential locations are re-closed under broad grants. Writes\n * and network access are denied except what the request opens.\n * - Docker: `docker run` with a read-only root, declared reads mounted\n * read-only at their host paths, declared writes read-write, `--network\n * none` unless the request opens it. The image supplies the toolchain. It is\n * the path on Windows and on any host without the native mechanism.\n *\n * Every declared confinement is enforced or the caller fails closed. Nothing\n * here logs \"unenforced\" and carries on; the one caller that may run\n * unconfined when no mechanism exists, `ProcessConfinement`, says so in its\n * own options and never on this module's behalf.\n *\n * @since 1.0.0-rc.1\n */\n\nimport { execFileSync } from \"node:child_process\"\nimport { randomUUID } from \"node:crypto\"\nimport * as NodeFs from \"node:fs\"\nimport * as NodeOs from \"node:os\"\nimport * as NodePath from \"node:path\"\n\n/**\n * The network posture a run is confined to. `\"none\"` closes IP networking.\n * Build tools retain host Unix sockets by default. Native agent confinement\n * additionally closes host Unix sockets regardless of the IP posture, except\n * macOS's system DNS resolver endpoint when IP networking is open, and\n * refuses all Linux socket creation when IP networking is closed;\n * socketpair remains available for communication inside the process family.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport type Network = \"none\" | \"loopback\" | \"open\"\n\n/**\n * Which mechanism a request asks for. Absent means the platform's native\n * mechanism: bubblewrap on Linux, seatbelt on macOS.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport type Selection =\n | { readonly _tag: \"bubblewrap\" }\n | { readonly _tag: \"docker\"; readonly image: string }\n\n/**\n * What a caller asks for: the network posture, the mechanism choice, and the\n * read and write sets. Every path is workspace-relative and posix-shaped;\n * a read may name a file or a directory, a write names a directory. A\n * `readOnly` entry re-closes a subtree under a declared write.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Request {\n readonly network: Network\n /** Whether host Unix sockets are permitted; defaults to true for build tools. */\n readonly unixSockets?: boolean | undefined\n /** Closed-by-default process and IPC policy for untrusted native commands. */\n readonly strict?: boolean | undefined\n readonly mechanism?: Selection | undefined\n readonly reads: ReadonlyArray<string>\n readonly writes: ReadonlyArray<string>\n /**\n * Declared outputs whose leaf is a file rather than a directory. A file\n * cannot be bound writable on its own and does not exist before the tool\n * writes it, so its parent directory becomes the writable bind. Kept apart\n * from `writes` because only the caller knows which of the two a\n * declaration meant: inferring it from the path would hand a not-yet-created\n * `.cargo-home` or `dist.new` its parent, and a parent that is the\n * workspace root opens the whole workspace for the run.\n */\n readonly writeFiles?: ReadonlyArray<string> | undefined\n readonly readOnly?: ReadonlyArray<string> | undefined\n /**\n * Absolute host paths outside the workspace the tool reads: a git\n * submodule's local source repository, for one. Bound read-only where the\n * mechanism would otherwise hide them; a path that does not exist, or that\n * sits inside the workspace, is dropped.\n */\n readonly externalReads?: ReadonlyArray<string> | undefined\n}\n\n/**\n * The host facts mechanism selection reads. Injectable so every platform's\n * argv can be tested on every other platform.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Host {\n readonly platform: NodeJS.Platform\n /** Selected Xcode developer tree on a macOS runner. */\n readonly developerDirectory?: string | undefined\n /** The real host home, used to mask credentials and locate runtime caches. */\n readonly home?: string | undefined\n /** Resolves an executable name on `PATH` to an absolute path, or nothing. */\n readonly executable: (name: string) => string | undefined\n /** Whether a host path exists. */\n readonly exists: (path: string) => boolean\n /** Whether a host path is a directory. */\n readonly isDirectory: (path: string) => boolean\n /** Tests the entry itself, including dangling links; defaults to the real host. */\n readonly isSymbolicLink?: ((path: string) => boolean) | undefined\n /**\n * The names of a directory's direct children, or undefined when the host\n * cannot list it. Optional: a host without it keeps every declared file as\n * its own entry, which is correct and merely long.\n */\n readonly entries?: ((directory: string) => ReadonlyArray<string> | undefined) | undefined\n /**\n * Where a path's bytes actually live once every symbolic link on it is\n * followed, or `undefined` when the host cannot say. Write validation uses\n * the real host when this probe is omitted and refuses unresolved existing\n * paths. Read aliases are only collected when this probe is supplied.\n */\n readonly realpath?: ((path: string) => string | undefined) | undefined\n readonly uid: number | undefined\n readonly gid: number | undefined\n}\n\n/**\n * One selected enforcement mechanism.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport type Mechanism =\n | { readonly _tag: \"bubblewrap\"; readonly executable: string }\n | { readonly _tag: \"seatbelt\"; readonly executable: string }\n | { readonly _tag: \"docker\"; readonly executable: string; readonly image: string }\n | { readonly _tag: \"none\" }\n\n/**\n * The refusal a host that cannot enforce a declared confinement produces.\n * `mechanism` names what was selected, `missing` what the host lacks.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Unenforceable {\n readonly _tag: \"@smthrs/platform-node/SandboxUnenforceable\"\n readonly platform: NodeJS.Platform\n readonly mechanism: string\n readonly missing: string\n readonly message: string\n}\n\n/**\n * Checks for an {@link Unenforceable} refusal.\n *\n * @category guards\n * @since 1.0.0-rc.1\n */\nexport const isUnenforceable = (value: unknown): value is Unenforceable =>\n typeof value === \"object\" && value !== null &&\n (value as { readonly _tag?: unknown })._tag === \"@smthrs/platform-node/SandboxUnenforceable\"\n\n/**\n * Builds an {@link Unenforceable} refusal.\n *\n * @category constructors\n * @since 1.0.0-rc.1\n */\nexport const unenforceable = (\n platform: NodeJS.Platform,\n mechanism: string,\n missing: string,\n detail: string\n): Unenforceable => ({\n _tag: \"@smthrs/platform-node/SandboxUnenforceable\",\n platform,\n mechanism,\n missing,\n message: `sandbox: the declared confinement cannot be enforced on this ${platform} host: ${detail}.`\n})\n\n/**\n * The resolved confinement one spawn runs under. Every path is absolute and\n * canonical.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Plan {\n readonly mechanism: Exclude<Mechanism, { readonly _tag: \"none\" }>\n readonly network: Network\n /** Whether host Unix sockets are permitted; defaults to true for build tools. */\n readonly unixSockets?: boolean | undefined\n /** Closed-by-default process and IPC policy for untrusted native commands. */\n readonly strict?: boolean | undefined\n readonly workspaceRoot: string\n readonly cwd: string\n readonly reads: ReadonlyArray<string>\n /**\n * Paths inside a granted directory that the declaration did not cover,\n * re-closed after the grant. Only a mechanism with deny rules (seatbelt)\n * folds; the others keep every declared file as its own entry.\n */\n readonly readDenies: ReadonlyArray<string>\n readonly writes: ReadonlyArray<string>\n readonly readOnly: ReadonlyArray<string>\n /**\n * Where a declared read's bytes actually live when the path reaches them\n * through a symbolic link that leaves the workspace.\n *\n * Bubblewrap needs them because it replaces `/tmp` with a private tmpfs, so\n * a link into the host's `/tmp` (the scratch tree's `node_modules`, which\n * points back at the real workspace) resolves to nothing inside the sandbox.\n * Binding the real location read-only makes the link resolve to the same\n * bytes the host sees, which is what declaring the read asked for. Docker\n * mounts them for the same reason: the image supplies everything outside\n * the workspace, and these paths are on the host. Seatbelt leaves the host\n * filesystem in place but must grant the resolved spelling too. The\n * request's own `externalReads` land here too.\n */\n readonly externalReads: ReadonlyArray<string>\n /** A host directory the run may scribble in; created by the caller, removed after. */\n readonly tmp: string\n readonly uid: number | undefined\n readonly gid: number | undefined\n}\n\nconst pathSeparator = (platform: NodeJS.Platform): string => platform === \"win32\" ? \";\" : \":\"\n\n/** Ambient host environment for runtime lookup and cache locations. */\nconst ambientEnvironment = (): Readonly<Record<string, string | undefined>> => process.env\n\n/**\n * The real host: platform, `PATH` lookup, and filesystem probes.\n *\n * @category resolution\n * @since 0.1.0\n */\nexport const host = (env: Readonly<Record<string, string | undefined>> = ambientEnvironment()): Host => {\n const platform = process.platform\n let developerDirectory = env[\"DEVELOPER_DIR\"]\n if (platform === \"darwin\" && developerDirectory === undefined) {\n try {\n developerDirectory = execFileSync(\"/usr/bin/xcode-select\", [\"-p\"], { encoding: \"utf8\", timeout: 2_000 }).trim()\n } catch {\n // A host without Xcode needs no developer-tree grant.\n }\n }\n const entries = (env[\"PATH\"] ?? \"\").split(pathSeparator(platform)).filter((entry) => entry !== \"\")\n const extensions = platform === \"win32\" ? (env[\"PATHEXT\"] ?? \".EXE;.CMD;.BAT\").split(\";\") : [\"\"]\n return {\n platform,\n developerDirectory,\n home: NodeOs.homedir(),\n executable: (name) => {\n if (NodePath.isAbsolute(name)) return isExecutable(name) ? name : undefined\n for (const entry of entries) {\n for (const extension of extensions) {\n const candidate = NodePath.join(entry, name + extension)\n if (isExecutable(candidate)) return candidate\n }\n }\n return undefined\n },\n exists: (path) => NodeFs.existsSync(path),\n isSymbolicLink,\n isDirectory: (path) => {\n try {\n return NodeFs.statSync(path).isDirectory()\n } catch {\n return false\n }\n },\n entries: (directory) => {\n try {\n return NodeFs.readdirSync(directory)\n } catch {\n return undefined\n }\n },\n realpath: (path) => {\n try {\n return NodeFs.realpathSync(path)\n } catch {\n return undefined\n }\n },\n uid: typeof process.getuid === \"function\" ? process.getuid() : undefined,\n gid: typeof process.getgid === \"function\" ? process.getgid() : undefined\n }\n}\n\nconst isExecutable = (path: string): boolean => {\n try {\n NodeFs.accessSync(path, NodeFs.constants.X_OK)\n return NodeFs.statSync(path).isFile()\n } catch {\n return false\n }\n}\n\n/**\n * Selects the mechanism a request runs under on a host.\n *\n * A docker selection is honored when `docker` is on `PATH` and refused\n * otherwise; a bubblewrap selection is refused off Linux. With no selection\n * the platform decides: bubblewrap on Linux, seatbelt on macOS, and a refusal\n * elsewhere, because Windows has no user-level process sandbox and Docker\n * needs an image only the caller can name.\n *\n * @category resolution\n * @since 1.0.0-rc.1\n */\nexport const select = (\n request: Pick<Request, \"network\" | \"mechanism\">,\n hostFacts: Host\n): Exclude<Mechanism, { readonly _tag: \"none\" }> | Unenforceable => {\n const declared = request.mechanism\n const platform = hostFacts.platform\n if (declared !== undefined && declared._tag === \"docker\") {\n const executable = hostFacts.executable(\"docker\")\n if (executable === undefined) {\n return unenforceable(platform, \"docker\", \"docker\", \"a docker mechanism is selected but docker is not on PATH\")\n }\n return { _tag: \"docker\", executable, image: declared.image }\n }\n if (declared !== undefined && platform !== \"linux\") {\n return unenforceable(\n platform,\n \"bubblewrap\",\n \"linux\",\n \"bubblewrap is selected but runs only on Linux; on macOS the seatbelt mechanism is selected when no mechanism is named\"\n )\n }\n if (platform === \"linux\") {\n const executable = hostFacts.executable(\"bwrap\")\n if (executable === undefined) {\n return unenforceable(platform, \"bubblewrap\", \"bwrap\", \"bwrap is not on PATH (install the bubblewrap package)\")\n }\n if (request.network === \"loopback\") {\n return unenforceable(\n platform,\n \"bubblewrap\",\n \"network: true\",\n \"bubblewrap cannot expose only the host loopback interface: sharing that interface also grants full host networking; \" +\n \"open the whole network to opt into that access\"\n )\n }\n return { _tag: \"bubblewrap\", executable }\n }\n if (platform === \"darwin\") {\n const executable = hostFacts.executable(\"/usr/bin/sandbox-exec\")\n if (executable === undefined) {\n return unenforceable(platform, \"seatbelt\", \"/usr/bin/sandbox-exec\", \"/usr/bin/sandbox-exec is missing\")\n }\n return { _tag: \"seatbelt\", executable }\n }\n return unenforceable(\n platform,\n \"docker\",\n \"docker image\",\n `${platform} has no process sandbox of its own; select a docker mechanism with an image`\n )\n}\n\nconst toPosix = (path: string): string => path.split(NodePath.sep).join(\"/\")\n\n/**\n * The path with the symlinks of its existing ancestors resolved. A path that\n * does not exist yet keeps its unresolved tail below the deepest ancestor\n * that does; a path with no existing ancestor is returned as given.\n */\nconst realized = (path: string): string => {\n let head = path\n const tail: Array<string> = []\n while (head !== NodePath.dirname(head)) {\n try {\n const real = NodeFs.realpathSync(head)\n return tail.length === 0 ? real : NodePath.join(real, ...tail)\n } catch {\n tail.unshift(NodePath.basename(head))\n head = NodePath.dirname(head)\n }\n }\n return path\n}\n\nconst insideRoot = (root: string, candidate: string): boolean => {\n const relative = NodePath.relative(root, candidate)\n return relative === \"\" ||\n (relative !== \"..\" && !relative.startsWith(`..${NodePath.sep}`) && !NodePath.isAbsolute(relative))\n}\n\n/** Unlike exists/stat, lstat sees dangling links and does not follow them. */\nconst isSymbolicLink = (path: string): boolean => {\n try {\n return NodeFs.lstatSync(path).isSymbolicLink()\n } catch (cause) {\n if ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return false\n throw cause\n }\n}\n\n/** Check each component, including existing ancestors of a missing output. */\nconst safeWrite = (root: string, path: string, hostFacts: Host): boolean => {\n if (!insideRoot(root, path)) return false\n try {\n const realpath = hostFacts.realpath ?? NodeFs.realpathSync\n const link = hostFacts.isSymbolicLink ?? isSymbolicLink\n if (realpath(root) !== root) return false\n let current = root\n for (const component of NodePath.relative(root, path).split(NodePath.sep).filter(Boolean)) {\n current = NodePath.join(current, component)\n if (link(current)) return false\n if (hostFacts.exists(current) && realpath(current) !== current) return false\n }\n return true\n } catch {\n return false\n }\n}\n\nconst unsafeWrite = (platform: NodeJS.Platform, mechanism: string, path: string): Unenforceable =>\n unenforceable(\n platform,\n mechanism,\n \"canonical workspace write path\",\n `write path ${path} has a symbolic link, an unresolved ancestor, or leaves the canonical workspace`\n )\n\n/**\n * Rechecks write grants before directory creation and before rendering. Throws\n * an {@link Unenforceable} refusal if a path changed since planning. Callers\n * must keep the workspace stable until the operating system consumes mounts.\n *\n * @category resolution\n * @since 0.1.0\n */\nexport const validateWrites = (confinement: Plan, hostFacts: Host = host()): void => {\n for (const write of confinement.writes) {\n if (!safeWrite(confinement.workspaceRoot, write, hostFacts)) {\n throw unsafeWrite(hostFacts.platform, confinement.mechanism._tag, write)\n }\n }\n}\n\n/**\n * Folds a declared file set into directories plus the entries to re-close.\n *\n * The seatbelt compiler caps one rule's data at 64 KiB, so a workspace with a\n * few thousand declared sources cannot be granted file by file. Deepest\n * first, a directory in which at least as many entries are covered as not is\n * granted whole, and its uncovered entries are listed for a deny rule that\n * follows the grant. The result admits exactly the declared set plus the\n * granted directories' future entries. A directory whose uncovered entry\n * still holds a covered path deeper down is never folded over that entry,\n * the workspace root is never folded, and a host that cannot list a\n * directory leaves its files as they were. Covered means a declared read, a\n * declared write, or the private tmp.\n */\nconst fold = (\n root: string,\n declared: ReadonlyArray<string>,\n alsoCovered: ReadonlyArray<string>,\n hostFacts: Host\n): { readonly reads: ReadonlyArray<string>; readonly denies: ReadonlyArray<string> } => {\n const entries = hostFacts.entries\n if (entries === undefined) return { reads: declared, denies: [] }\n const covered = new Set([...declared, ...alsoCovered])\n const candidates = new Set<string>()\n for (const path of declared) {\n let current = NodePath.dirname(path)\n while (insideRoot(root, current) && current !== root && !candidates.has(current)) {\n candidates.add(current)\n current = NodePath.dirname(current)\n }\n }\n // An undeclared directory on the way down to a covered write or read-only\n // path is not a candidate, so nothing else would stop its parent being\n // folded over it. The deny that follows the grant names the directory, and\n // because later rules win in SBPL it closes reads on the very subtree the\n // plan grants a write to.\n const holdsCovered = new Set<string>()\n for (const path of alsoCovered) {\n let current = NodePath.dirname(path)\n while (insideRoot(root, current) && current !== root && !holdsCovered.has(current)) {\n holdsCovered.add(current)\n current = NodePath.dirname(current)\n }\n }\n const deepestFirst = [...candidates].sort((left, right) => right.length - left.length || (left < right ? -1 : 1))\n const promoted: Array<string> = []\n const denies: Array<string> = []\n for (const directory of deepestFirst) {\n if (covered.has(directory)) continue\n const children = entries(directory)\n if (children === undefined || children.length === 0) continue\n const uncovered: Array<string> = []\n let coveredCount = 0\n let partial = false\n for (const child of children) {\n const path = NodePath.join(directory, child)\n if (covered.has(path)) coveredCount += 1\n else if (candidates.has(path) || holdsCovered.has(path)) partial = true\n else uncovered.push(path)\n }\n if (partial || coveredCount === 0 || coveredCount < uncovered.length) continue\n covered.add(directory)\n promoted.push(directory)\n denies.push(...uncovered)\n }\n return { reads: [...declared, ...promoted], denies }\n}\n\n/**\n * Orders paths so that every subtree is contiguous and its root comes first.\n *\n * Comparing on the path plus its separator is what makes that hold: `a/b` and\n * `a/b/c` sort around `a/b.ts`, because `.` precedes `/`, and a plain\n * comparison would then put a sibling between an ancestor and its child.\n */\nconst bySubtree = (left: string, right: string): number => {\n const leftKey = left + NodePath.sep\n const rightKey = right + NodePath.sep\n return leftKey < rightKey ? -1 : 1\n}\n\n/**\n * Drops a path covered by another entry of the same set, keeping the set ordered.\n *\n * One sweep in subtree order carries the shallowest kept ancestor forward,\n * because searching the kept set per path is quadratic in the sibling files a\n * bubblewrap or Docker plan declares one by one.\n */\nconst collapse = (paths: ReadonlyArray<string>): ReadonlyArray<string> => {\n const unique = [...new Set(paths)]\n const kept = new Set<string>()\n let ancestor: string | undefined\n for (const path of [...unique].sort(bySubtree)) {\n if (ancestor !== undefined && path.startsWith(ancestor + NodePath.sep)) continue\n kept.add(path)\n ancestor = path\n }\n return unique.filter((path) => kept.has(path)).sort((left, right) =>\n left.length - right.length || (left < right ? -1 : 1)\n )\n}\n\n/**\n * Resolves a request against a workspace into a {@link Plan}, or refuses.\n *\n * Every relative path is anchored at the canonical workspace root; one that\n * escapes the workspace is dropped, because the sandbox never grants anything\n * outside the tree the key covers. Reads that do not exist are dropped too:\n * bubblewrap cannot bind a missing path, and a declared input that vanished is\n * the executor's measurement failure, not this module's. A write containing\n * any symbolic link below the canonical root is refused, including a missing\n * output below a linked ancestor.\n *\n * @category resolution\n * @since 0.1.0\n */\nexport const plan = (\n request: Request,\n location: { readonly workspaceRoot: string; readonly cwd: string; readonly tmp: string },\n hostFacts: Host\n): Plan | Unenforceable => {\n const selected = select(request, hostFacts)\n if (isUnenforceable(selected)) return selected\n let root: string\n try {\n const canonical = (hostFacts.realpath ?? NodeFs.realpathSync)(location.workspaceRoot)\n if (canonical === undefined) return unsafeWrite(hostFacts.platform, selected._tag, location.workspaceRoot)\n root = canonical\n } catch {\n return unsafeWrite(hostFacts.platform, selected._tag, location.workspaceRoot)\n }\n const anchor = (relative: string): string | undefined => {\n const absolute = NodePath.resolve(root, ...relative.split(\"/\"))\n return insideRoot(root, absolute) ? absolute : undefined\n }\n const declared: Array<string> = []\n // A declared link grants its target as well: native read allowlists and\n // Docker's image filesystem otherwise hide bytes outside the workspace.\n const externalReads: Array<string> = []\n for (const relative of request.reads) {\n const absolute = anchor(relative)\n if (absolute === undefined || !hostFacts.exists(absolute)) continue\n declared.push(absolute)\n const real = hostFacts.realpath?.(absolute)\n if (real !== undefined && real !== absolute && !insideRoot(root, real)) externalReads.push(real)\n }\n for (const path of request.externalReads ?? []) {\n if (!NodePath.isAbsolute(path) || insideRoot(root, path) || !hostFacts.exists(path)) continue\n externalReads.push(hostFacts.realpath?.(path) ?? path)\n }\n // A write names a directory the tool may fill, and is bound as declared\n // whether or not it exists yet: a name is never read as a file, because a\n // dot in `.cargo-home` or `dist.new` would otherwise widen the bind to the\n // parent, and a parent that is the workspace root opens every file in it.\n // A path that is already a file on the host is the one exception, since a\n // file cannot be bound as a writable directory.\n const writes: Array<string> = []\n const add = (absolute: string): Unenforceable | undefined => {\n if (!safeWrite(root, absolute, hostFacts)) return unsafeWrite(hostFacts.platform, selected._tag, absolute)\n writes.push(absolute)\n return undefined\n }\n for (const relative of request.writes) {\n const absolute = anchor(relative)\n if (absolute === undefined) continue\n if (!safeWrite(root, absolute, hostFacts)) return unsafeWrite(hostFacts.platform, selected._tag, absolute)\n const refusal = add(\n hostFacts.exists(absolute) && !hostFacts.isDirectory(absolute) ? NodePath.dirname(absolute) : absolute\n )\n if (refusal !== undefined) return refusal\n }\n // A declared output file opens its parent: a file cannot be bound before it\n // exists, and a tool that writes by rename needs the directory anyway.\n for (const relative of request.writeFiles ?? []) {\n const absolute = anchor(relative)\n if (absolute === undefined) continue\n if (!safeWrite(root, absolute, hostFacts)) return unsafeWrite(hostFacts.platform, selected._tag, absolute)\n const refusal = add(\n hostFacts.exists(absolute) && hostFacts.isDirectory(absolute) ? absolute : NodePath.dirname(absolute)\n )\n if (refusal !== undefined) return refusal\n }\n const readOnly: Array<string> = []\n for (const relative of request.readOnly ?? []) {\n const absolute = anchor(relative)\n if (absolute !== undefined) readOnly.push(absolute)\n }\n const folded = selected._tag === \"seatbelt\"\n ? fold(root, declared, [...writes, ...readOnly, location.tmp], hostFacts)\n : { reads: declared, denies: [] }\n return {\n mechanism: selected,\n network: request.network,\n unixSockets: request.unixSockets,\n strict: request.strict,\n workspaceRoot: root,\n cwd: location.cwd,\n reads: collapse(folded.reads),\n readDenies: collapse(folded.denies),\n writes: collapse(writes),\n readOnly: collapse(readOnly),\n externalReads: collapse(externalReads),\n tmp: location.tmp,\n uid: hostFacts.uid,\n gid: hostFacts.gid\n }\n}\n\n/**\n * Where corepack keeps the package-manager binaries it manages, resolved the\n * way corepack itself resolves it from a host environment.\n *\n * A `pnpm`, `yarn`, or `npm` on `PATH` is often corepack's shim, and the\n * program it execs lives in this directory, not on `PATH`. Redirecting `HOME`\n * and `XDG_CACHE_HOME` to the private tmp moves that directory out from under\n * the shim, which then tries to re-download the package manager from the\n * registry \u2014 and a confined run has no network, so every rule that drives the\n * package manager fails with a connect error that names the registry rather\n * than the missing cache.\n */\nconst corepackHome = (env: Readonly<Record<string, string | undefined>>, home: string): string => {\n const declared = env[\"COREPACK_HOME\"]\n if (declared !== undefined && declared !== \"\") return declared\n const base = env[\"XDG_CACHE_HOME\"] ?? env[\"LOCALAPPDATA\"] ??\n NodePath.join(home, process.platform === \"win32\" ? \"AppData/Local\" : \".cache\")\n return NodePath.join(base, \"node\", \"corepack\")\n}\n\n/**\n * Environment the confined process receives on top of the tool environment:\n * a private temporary directory and home, so nothing a tool caches lands in\n * the real home or the shared temp directory.\n *\n * `COREPACK_HOME` is the one host cache kept: it holds the package-manager\n * program a corepack shim execs, the run may only read it (the mechanisms\n * deny writes outside the workspace), and without it a confined `pnpm` is a\n * shim with nothing to run. A non-default cache location requires an explicit\n * `externalReads` grant; preserving the variable does not grant access.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const environment = (\n confinement: Plan,\n hostEnv: Readonly<Record<string, string | undefined>> = ambientEnvironment(),\n hostHome: string = NodeOs.homedir()\n): Readonly<Record<string, string>> => {\n const tmp = confinement.mechanism._tag === \"seatbelt\" ? confinement.tmp : \"/tmp\"\n const home = NodePath.posix.join(toPosix(tmp), \"home\")\n return {\n COREPACK_HOME: corepackHome(hostEnv, hostHome),\n HOME: home,\n TMPDIR: tmp,\n TMP: tmp,\n TEMP: tmp,\n XDG_CACHE_HOME: NodePath.posix.join(toPosix(tmp), \"cache\"),\n XDG_CONFIG_HOME: NodePath.posix.join(toPosix(tmp), \"config\"),\n XDG_DATA_HOME: NodePath.posix.join(toPosix(tmp), \"data\"),\n XDG_STATE_HOME: NodePath.posix.join(toPosix(tmp), \"state\")\n }\n}\n\n/** The fixed runtime surface, never the host root, home, PATH, or arbitrary env paths. */\nconst runtimeReads = (hostFacts: Host): ReadonlyArray<string> => {\n const paths = [\n \"/usr\",\n \"/bin\",\n \"/sbin\",\n \"/lib\",\n \"/lib64\",\n \"/nix/store\",\n \"/opt/homebrew\",\n // A Smithers machine's toolchain layer: the repository's Node, pnpm, Go,\n // Rust and Python, which the machine's environment puts first on PATH\n // (packages/backend/microsandbox layers.go toolchainRoot). Never all of\n // /opt/smithers, which holds the machine's environment and helpers.\n \"/opt/smithers/toolchain\",\n \"/opt/smithers/rust\",\n \"/System/Library\",\n \"/System/Volumes/Preboot/Cryptexes/OS\",\n \"/private/var/db/dyld\",\n // macOS shell and developer-tool dispatch read these system selectors.\n \"/var/select/sh\",\n \"/private/var/select/sh\",\n \"/var/select/developer_dir\",\n \"/private/var/select/developer_dir\",\n \"/Library/Apple\",\n \"/Library/Developer\",\n \"/Applications/Xcode.app/Contents/Developer\",\n \"/Applications/Xcode.app/Contents/Info.plist\",\n \"/Applications/Xcode.app/Contents/Frameworks\",\n \"/Applications/Xcode.app/Contents/SharedFrameworks\",\n \"/Applications/Xcode.app/Contents/PlugIns\",\n \"/etc/ld.so.cache\",\n \"/etc/ld.so.conf\",\n \"/etc/ld.so.conf.d\",\n \"/etc/localtime\",\n \"/etc/ssl\",\n \"/etc/hosts\",\n \"/etc/resolv.conf\",\n \"/etc/nsswitch.conf\"\n ]\n const developerDirectory = hostFacts.developerDirectory\n if (\n hostFacts.platform === \"darwin\" && developerDirectory !== undefined &&\n /^\\/Applications\\/Xcode[^/]*\\.app\\/Contents\\/Developer$/.test(developerDirectory)\n ) {\n paths.push(developerDirectory)\n }\n if (hostFacts.home !== undefined) paths.push(NodePath.join(hostFacts.home, \".cache/node/corepack\"))\n for (const name of [\"node\", \"bun\"]) {\n const executable = hostFacts.executable(name)\n if (executable !== undefined) {\n paths.push(executable)\n const real = hostFacts.realpath?.(executable) ?? executable\n paths.push(real)\n // Node distributions keep npm/corepack here; never grant the install\n // prefix itself, which can be the real home for a custom installation.\n paths.push(NodePath.resolve(NodePath.dirname(real), \"../lib/node_modules\"))\n }\n }\n const present = paths.filter((path) => hostFacts.exists(path))\n return collapse(present.flatMap((path) => [path, hostFacts.realpath?.(path) ?? path]))\n}\n\n/** Credentials remain closed even when a workspace or runtime grant contains the home. */\nconst credentialPaths = (hostFacts: Host): ReadonlyArray<string> => {\n const home = hostFacts.home\n if (home === undefined) return []\n return collapse([\n \".aws\",\n \".ssh\",\n \".gnupg\",\n \".azure\",\n \".kube\",\n \".docker\",\n \".config/gcloud\",\n \".config/gh\",\n \".npmrc\",\n \".netrc\",\n \".git-credentials\"\n ].flatMap((relative) => {\n const path = NodePath.join(home, relative)\n return [path, hostFacts.realpath?.(path) ?? path]\n }))\n}\n\n/**\n * The program a confined run execs, made reachable inside its empty root.\n *\n * @internal\n *\n * Bubblewrap resolves a bare program name with `execvp` against `PATH` inside\n * the new root, and that root holds only the runtime paths and the declared\n * sets. A package manager installed outside them \u2014 pnpm under the\n * `pnpm/action-setup` prefix on a CI runner, a `.bin` link into a\n * `node_modules` tree \u2014 is on the host `PATH` and absent from the sandbox, so\n * the run died with `bwrap: execvp pnpm` before the tool started (#3140).\n *\n * A bare name is resolved on the tool's `PATH` (the host's when none is\n * given). A program already inside a granted path is left exactly as spelled,\n * so a multi-call binary keeps the name it dispatches on, and so is one inside\n * the workspace, whose reads only the declaration may open. Otherwise the run\n * execs the program's real file, and the nearest package directory holding it\n * (its `package.json`, at most three levels up, never the home or the root)\n * is bound read-only, because a script like `pnpm.cjs` loads its siblings.\n * With no package directory only the real file is bound. A name that resolves\n * nowhere is left for `execvp` to report.\n */\nexport const launcher = (\n program: string,\n searchPath: string | undefined,\n granted: ReadonlyArray<string>,\n workspaceRoot: string,\n hostFacts: Host\n): { readonly program: string; readonly reads: ReadonlyArray<string> } => {\n const unchanged = { program, reads: [] }\n if (program.includes(\"/\") && !NodePath.isAbsolute(program)) return unchanged\n const found = NodePath.isAbsolute(program)\n ? hostFacts.executable(program)\n : searchPath === undefined\n ? hostFacts.executable(program)\n : searchPath.split(\":\").filter((entry) => NodePath.isAbsolute(entry))\n .map((entry) => hostFacts.executable(NodePath.join(entry, program)))\n .find((candidate) => candidate !== undefined)\n if (found === undefined || granted.some((parent) => insideRoot(parent, found))) return unchanged\n const real = hostFacts.realpath?.(found) ?? found\n if (insideRoot(workspaceRoot, found) || insideRoot(workspaceRoot, real)) return unchanged\n if (granted.some((parent) => insideRoot(parent, real))) return { program: real, reads: [] }\n let directory = NodePath.dirname(real)\n for (let level = 0; level < 3; level++) {\n if (directory === NodePath.dirname(directory) || directory === hostFacts.home) break\n if (hostFacts.exists(NodePath.join(directory, \"package.json\"))) return { program: real, reads: [directory] }\n directory = NodePath.dirname(directory)\n }\n return { program: real, reads: [real] }\n}\n\n/**\n * The bubblewrap argv for a plan.\n *\n * Order matters: bubblewrap applies operations in argument order, so the\n * empty root goes first, runtime paths are bound, the workspace tmpfs shadows\n * any broad runtime grant, declared paths are bound on top, and re-closed\n * subtrees are bound read-only over their writable parents. The tmpfs is\n * remounted read-only last so an undeclared write\n * under the workspace fails with `EROFS` instead of vanishing into the tmpfs.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const bubblewrap = (\n confinement: Plan,\n argv: ReadonlyArray<string>,\n hostFacts: Host = host(),\n searchPath?: string | undefined\n): ReadonlyArray<string> => {\n if (confinement.mechanism._tag !== \"bubblewrap\") throw new Error(\"bubblewrap argv needs a bubblewrap plan\")\n validateWrites(confinement, hostFacts)\n const home = \"/tmp/home\"\n const out: Array<string> = [\n confinement.mechanism.executable,\n \"--tmpfs\",\n \"/\"\n ]\n const runtime = runtimeReads(hostFacts)\n for (const read of runtime) out.push(\"--ro-bind\", read, read)\n out.push(\n \"--dev\",\n \"/dev\",\n \"--proc\",\n \"/proc\",\n \"--tmpfs\",\n \"/tmp\",\n \"--dir\",\n home,\n \"--dir\",\n \"/tmp/cache\",\n \"--tmpfs\",\n confinement.workspaceRoot,\n \"--dir\",\n confinement.cwd\n )\n // The target of a link that leaves the workspace is bound first, so the\n // link a declared read then binds resolves to the same bytes the host sees\n // even when `/tmp` above the target has just become a private tmpfs.\n for (const real of confinement.externalReads) out.push(\"--ro-bind\", real, real)\n const [program, ...args] = argv\n const launch = program === undefined ? undefined : launcher(\n program,\n searchPath,\n [...runtime, ...confinement.reads, ...confinement.writes, ...confinement.externalReads],\n confinement.workspaceRoot,\n hostFacts\n )\n for (const read of launch?.reads ?? []) out.push(\"--ro-bind\", read, read)\n for (const read of confinement.reads) out.push(\"--ro-bind\", read, read)\n for (const write of confinement.writes) out.push(\"--bind\", write, write)\n for (const closed of confinement.readOnly) {\n if (confinement.writes.some((write) => closed === write || closed.startsWith(write + NodePath.sep))) {\n out.push(\"--ro-bind-try\", closed, closed)\n }\n }\n const granted = [\n ...runtime,\n ...(launch?.reads ?? []),\n ...confinement.reads,\n ...confinement.writes,\n ...confinement.externalReads\n ]\n for (const path of credentialPaths(hostFacts)) {\n if (!hostFacts.exists(path) || !granted.some((parent) => insideRoot(parent, path))) continue\n // An explicit external read can name a credential or a file inside it.\n // Mask first, then restore only those explicit paths below the mask.\n if (confinement.externalReads.some((parent) => insideRoot(parent, path))) continue\n const directory = hostFacts.isDirectory(path)\n if (directory) out.push(\"--tmpfs\", path)\n else out.push(\"--ro-bind\", \"/dev/null\", path)\n for (const read of confinement.externalReads) {\n if (insideRoot(path, read)) out.push(\"--ro-bind\", read, read)\n }\n if (directory) out.push(\"--remount-ro\", path)\n }\n // `--remount-ro` acts on the mount at that exact path. When the root itself\n // is a declared write (a declared output file at the top level opens its\n // parent), that mount is the writable bind, and remounting it would deny\n // every write the declaration admitted; the tmpfs it replaced needs no\n // re-closing.\n if (!confinement.writes.includes(confinement.workspaceRoot)) out.push(\"--remount-ro\", confinement.workspaceRoot)\n out.push(\"--remount-ro\", \"/\")\n out.push(\"--chdir\", confinement.cwd, \"--unshare-all\", \"--new-session\", \"--die-with-parent\")\n // Bubblewrap cannot expose only the host loopback interface. Planning\n // refuses that posture on Linux, so only an explicit full-network opening\n // reaches `--share-net` here.\n if (confinement.network === \"loopback\") {\n throw new Error(\"bubblewrap cannot render loopback-only networking\")\n }\n if (confinement.network === \"open\") out.push(\"--share-net\")\n out.push(\"--\", ...(launch === undefined ? argv : [launch.program, ...args]))\n return out\n}\n\n/** Escapes one string for a seatbelt profile literal. */\nconst sbpl = (value: string): string => `\"${value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"\\\"\", \"\\\\\\\"\")}\"`\n\n/** Every directory from the workspace root down to the parent of each path. */\nconst ancestors = (root: string, paths: ReadonlyArray<string>): ReadonlyArray<string> => {\n const found = new Set<string>([root])\n for (const path of paths) {\n let current = NodePath.dirname(path)\n while (current.length >= root.length && current !== NodePath.dirname(current)) {\n found.add(current)\n if (current === root) break\n current = NodePath.dirname(current)\n }\n }\n return [...found].sort()\n}\n\n/**\n * The seatbelt profile for a plan.\n *\n * File reads start closed, then enumerated runtime paths and declared sets\n * are opened. The workspace is re-closed before its grants so a workspace\n * under a runtime root does not inherit the broad runtime grant. Credentials\n * are re-closed before explicit external reads, which can opt into them.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const seatbelt = (confinement: Plan, hostFacts: Host = host()): string => {\n if (confinement.mechanism._tag !== \"seatbelt\") throw new Error(\"a seatbelt profile needs a seatbelt plan\")\n validateWrites(confinement, hostFacts)\n const lines: Array<string> = [\"(version 1)\", confinement.strict === true ? \"(deny default)\" : \"(allow default)\"]\n if (confinement.strict === true) {\n // No Mach lookup, Apple Events or LaunchServices grants: work must stay\n // in descendants inheriting this sandbox, never delegated to a host daemon.\n lines.push(\n \"(allow process-exec)\",\n \"(allow process-fork)\",\n \"(allow signal (target same-sandbox))\",\n \"(allow process-info* (target same-sandbox))\",\n \"(allow sysctl-read (sysctl-name-prefix \\\"hw.\\\") (sysctl-name-prefix \\\"machdep.cpu.\\\") (sysctl-name \\\"kern.argmax\\\") (sysctl-name \\\"kern.hostname\\\") (sysctl-name \\\"kern.maxfilesperproc\\\") (sysctl-name \\\"kern.maxproc\\\") (sysctl-name \\\"kern.osproductversion\\\") (sysctl-name \\\"kern.osrelease\\\") (sysctl-name \\\"kern.ostype\\\") (sysctl-name \\\"kern.osversion\\\") (sysctl-name \\\"kern.secure_kernel\\\") (sysctl-name \\\"kern.usrstack64\\\") (sysctl-name \\\"kern.version\\\") (sysctl-name \\\"sysctl.proc_cputype\\\") (sysctl-name \\\"vm.loadavg\\\"))\",\n \"(allow mach-host*)\"\n )\n if (confinement.network === \"open\") lines.push(\"(allow network*)\")\n }\n if (confinement.network !== \"open\") {\n lines.push(\"(deny network*)\")\n if (confinement.unixSockets !== false) lines.push(\"(allow network* (local unix-socket))\")\n if (confinement.network === \"loopback\") {\n lines.push(\n \"(allow network-bind (local ip \\\"localhost:*\\\"))\",\n \"(allow network-inbound (local ip \\\"localhost:*\\\"))\",\n \"(allow network-outbound (remote ip \\\"localhost:*\\\"))\"\n )\n }\n }\n if (confinement.network === \"open\" && confinement.unixSockets === false) {\n lines.push(\"(deny network* (local unix-socket))\")\n if (confinement.strict === true) {\n // Darwin resolves DNS through this trusted endpoint. Full IP authority\n // permits DNS only; other host daemons remain closed, including Mach IPC.\n lines.push(\"(allow network-outbound (literal \\\"/private/var/run/mDNSResponder\\\"))\")\n }\n }\n const writable = [...confinement.writes, confinement.tmp]\n lines.push(\n \"(deny file-write*)\",\n `(allow file-write* ${confinement.strict === true ? \"(literal \\\"/dev/null\\\")\" : \"(subpath \\\"/dev\\\")\"} ${\n writable.map((path) => `(subpath ${sbpl(path)})`).join(\" \")\n })`\n )\n if (confinement.readOnly.length > 0) {\n lines.push(`(deny file-write* ${confinement.readOnly.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n }\n lines.push(\n \"(deny file-read*)\",\n confinement.strict === true\n ? \"(allow file-read* (literal \\\"/\\\") (literal \\\"/dev/null\\\") (literal \\\"/dev/zero\\\") (literal \\\"/dev/random\\\") (literal \\\"/dev/urandom\\\"))\"\n : \"(allow file-read* (literal \\\"/\\\") (subpath \\\"/dev\\\"))\",\n // macOS's system aliases need readlink access before a shell can execute\n // an admitted file through them. Literal grants expose the links only;\n // their target directories still cannot be listed or read wholesale.\n \"(allow file-read* (literal \\\"/tmp\\\") (literal \\\"/var\\\") (literal \\\"/etc\\\"))\"\n )\n const runtime = runtimeReads(hostFacts)\n if (runtime.length > 0) lines.push(`(allow file-read* ${runtime.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n const readable = [...confinement.reads, ...confinement.writes, confinement.tmp]\n // Node realpath walks every ancestor, including parents outside the\n // workspace. Grant only their metadata, without listing or reading bytes.\n const traversable = ancestors(\"/\", [...runtime, ...readable, ...confinement.externalReads])\n .filter((path) => !insideRoot(confinement.workspaceRoot, path))\n lines.push(`(allow file-read-metadata ${traversable.map((path) => `(literal ${sbpl(path)})`).join(\" \")})`)\n const listable = ancestors(\n confinement.workspaceRoot,\n readable.filter((path) => insideRoot(confinement.workspaceRoot, path))\n )\n lines.push(\n `(deny file-read* (subpath ${sbpl(confinement.workspaceRoot)}))`\n )\n if (confinement.strict === true) {\n const metadata = [\n ...new Set([\n ...listable,\n ...ancestors(confinement.workspaceRoot, [confinement.cwd]),\n confinement.cwd\n ])\n ].sort()\n lines.push(`(allow file-read-metadata ${metadata.map((path) => `(literal ${sbpl(path)})`).join(\" \")})`)\n } else {\n lines.push(`(allow file-read-metadata (subpath ${sbpl(confinement.workspaceRoot)}))`)\n }\n lines.push(\n `(allow file-read* ${\n [\n ...(confinement.strict === true ? [] : listable.map((path) => `(literal ${sbpl(path)})`)),\n ...readable.map((path) => `(subpath ${sbpl(path)})`)\n ].join(\" \")\n })`\n )\n if (confinement.readDenies.length > 0) {\n lines.push(`(deny file-read* ${confinement.readDenies.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n }\n const credentials = credentialPaths(hostFacts)\n if (credentials.length > 0) {\n lines.push(`(deny file-read* ${credentials.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n if (confinement.strict === true) {\n lines.push(`(deny file-write* ${credentials.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n }\n }\n if (confinement.externalReads.length > 0) {\n lines.push(`(allow file-read* ${confinement.externalReads.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n }\n return lines.join(\"\")\n}\n\n/**\n * One `key=value` field of a `docker run --mount` value. Docker parses the\n * value as one CSV record, so a field containing a comma, a quote, or a line\n * break is wrapped in quotes with inner quotes doubled; any other field keeps\n * its plain spelling.\n */\nconst mountField = (key: string, value: string): string => {\n const field = `${key}=${value}`\n return /[\",\\r\\n]/.test(field) ? `\"${field.replaceAll(\"\\\"\", \"\\\"\\\"\")}\"` : field\n}\n\nconst bindMount = (path: string, readonly: boolean): string =>\n [\"type=bind\", mountField(\"src\", path), mountField(\"dst\", toPosix(path)), ...(readonly ? [\"readonly\"] : [])].join(\",\")\n\n/**\n * The `docker run` argv for a plan. Paths keep their host spelling inside the\n * container so an argv that names workspace paths works unchanged; the image\n * supplies everything outside the workspace.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const docker = (\n confinement: Plan,\n argv: ReadonlyArray<string>,\n env: Readonly<Record<string, string>>,\n hostFacts: Host = host(),\n containerName: string = `smithers-${randomUUID()}`\n): ReadonlyArray<string> => {\n if (confinement.mechanism._tag !== \"docker\") throw new Error(\"docker argv needs a docker plan\")\n validateWrites(confinement, hostFacts)\n const out: Array<string> = [\n confinement.mechanism.executable,\n \"run\",\n \"--rm\",\n \"--init\",\n \"--name\",\n containerName,\n \"--read-only\",\n \"--tmpfs\",\n \"/tmp:rw,exec\",\n \"--network\",\n confinement.network === \"open\" ? \"bridge\" : \"none\",\n \"--workdir\",\n toPosix(confinement.cwd)\n ]\n if (confinement.uid !== undefined && confinement.gid !== undefined) {\n out.push(\"--user\", `${confinement.uid}:${confinement.gid}`)\n }\n for (const real of confinement.externalReads) out.push(\"--mount\", bindMount(real, true))\n for (const read of confinement.reads) out.push(\"--mount\", bindMount(read, true))\n for (const write of confinement.writes) out.push(\"--mount\", bindMount(write, false))\n for (const closed of confinement.readOnly) {\n if (confinement.writes.some((write) => closed === write || closed.startsWith(write + NodePath.sep))) {\n out.push(\"--mount\", bindMount(closed, true))\n }\n }\n for (const [name, value] of Object.entries(env).sort(([left], [right]) => (left < right ? -1 : 1))) {\n if (name === \"PATH\" || name === \"HOME\") continue\n out.push(\"--env\", `${name}=${value}`)\n }\n out.push(\"--env\", \"HOME=/tmp/home\", confinement.mechanism.image, ...argv)\n return out\n}\n\n/**\n * Wraps a tool argv in the plan's mechanism and reports the environment the\n * wrapper needs the child to carry. Docker runs also return the unique\n * container name the caller must forcibly remove when the client scope closes.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const wrap = (\n confinement: Plan,\n argv: ReadonlyArray<string>,\n env: Readonly<Record<string, string>>,\n hostFacts: Host = host()\n): {\n readonly argv: ReadonlyArray<string>\n readonly env: Readonly<Record<string, string>>\n readonly containerName?: string\n} => {\n const extra = environment(confinement)\n switch (confinement.mechanism._tag) {\n case \"bubblewrap\":\n return { argv: bubblewrap(confinement, argv, hostFacts, env[\"PATH\"]), env: extra }\n case \"seatbelt\": {\n const profile = seatbelt(confinement, hostFacts)\n return { argv: [confinement.mechanism.executable, \"-p\", profile, ...argv], env: extra }\n }\n case \"docker\": {\n const containerName = `smithers-${randomUUID()}`\n return { argv: docker(confinement, argv, { ...env, ...extra }, hostFacts, containerName), env: {}, containerName }\n }\n }\n}\n\n/**\n * Each pattern names the path a tool complained about and whether the text\n * proves the denied operation was a write. A confined process never sees an\n * undeclared path: seatbelt answers EPERM, bubblewrap answers ENOENT because\n * the path was never mounted, and dash spells that \"Directory nonexistent\".\n */\nconst denialPatterns: ReadonlyArray<{ readonly pattern: RegExp; readonly write: boolean }> = [\n { pattern: /\\b(?:EACCES|EPERM|ENOENT)\\b[^'\"\\n]*['\"]([^'\"\\n]+)['\"]/g, write: false },\n { pattern: /\\bEROFS\\b[^'\"\\n]*['\"]([^'\"\\n]+)['\"]/g, write: true },\n {\n pattern:\n /(?:^|[\\s:])((?:\\/|\\.{0,2}\\/?)[^\\s:'\"]+): (?:Operation not permitted|Permission denied|No such file or directory|Directory nonexistent|Not a directory)/gm,\n write: false\n },\n { pattern: /(?:^|[\\s:])((?:\\/|\\.{0,2}\\/?)[^\\s:'\"]+): Read-only file system/gm, write: true },\n {\n pattern:\n /(?:Operation not permitted|Permission denied|No such file or directory|Directory nonexistent)[^\\n]*?[:\\s]['\"]?((?:\\/|[A-Za-z]:\\\\)[^'\"\\s:]+)/g,\n write: false\n },\n { pattern: /Read-only file system[^\\n]*?[:\\s]['\"]?((?:\\/|[A-Za-z]:\\\\)[^'\"\\s:]+)/g, write: true },\n {\n pattern: /cannot (?:create|write|touch|mkdir|remove) (?:directory )?['\"]?((?:\\/|[A-Za-z]:\\\\|\\.{0,2}\\/?)[^'\":\\s]+)/g,\n write: true\n },\n { pattern: /cannot (?:open|access|stat|read) ['\"]?((?:\\/|[A-Za-z]:\\\\|\\.{0,2}\\/?)[^'\":\\s]+)/g, write: false }\n]\n\n/**\n * Names, from a failed tool's own output, the paths it touched outside the\n * declared set. The sandbox denies at the kernel, so the only witness is the\n * tool's error text; this reads it back and says which side of the boundary\n * each path fell on.\n *\n * @category diagnostics\n * @since 0.1.0\n */\nexport const diagnose = (confinement: Plan, text: string): string | undefined => {\n // Path \u2192 whether the text proved the denied operation was a write.\n const named = new Map<string, boolean>()\n for (const { pattern, write } of denialPatterns) {\n for (const match of text.matchAll(pattern)) {\n const raw = match[1]!\n const absolute = NodePath.isAbsolute(raw) ? raw : NodePath.resolve(confinement.cwd, raw)\n if (insideRoot(confinement.workspaceRoot, absolute)) named.set(absolute, (named.get(absolute) ?? false) || write)\n if (named.size >= 5) break\n }\n if (named.size >= 5) break\n }\n if (named.size === 0) return undefined\n const covered = (path: string, set: ReadonlyArray<string>): boolean =>\n set.some((entry) => entry === path || path.startsWith(entry + NodePath.sep))\n const lines = [...named.entries()].sort(([left], [right]) => (left < right ? -1 : 1)).map(([path, write]) => {\n const relative = toPosix(NodePath.relative(confinement.workspaceRoot, path))\n // A path that lexically sits in the workspace can still leave it through\n // a symlink in an existing ancestor; the declaration covers the tree, not\n // wherever a link points.\n const real = realized(path)\n const escapes = !insideRoot(confinement.workspaceRoot, real)\n const readable = !escapes && (covered(path, confinement.reads) || covered(path, confinement.writes))\n const writable = !escapes && covered(path, confinement.writes) && !covered(path, confinement.readOnly)\n if (escapes) {\n return `sandbox: ${relative} resolves to ${real}, outside the declared ${write ? \"write\" : \"read\"} set`\n }\n if (write) {\n return writable\n ? `sandbox: ${relative} was denied inside the declared set`\n : `sandbox: ${relative} is outside the declared write set`\n }\n if (!readable) return `sandbox: ${relative} is outside the declared read set`\n if (!writable) return `sandbox: ${relative} is outside the declared write set`\n return `sandbox: ${relative} was denied inside the declared set`\n })\n return `${lines.join(\"\\n\")}\\nsandbox: ${confinement.mechanism._tag}, network ${confinement.network}, ` +\n `${confinement.reads.length} read path(s), ${confinement.writes.length} write path(s)`\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * The one operating-system sandbox every confined process goes through.\n *\n * A caller resolves what a run may touch, a {@link Request}: the network\n * posture, the read and write sets, and optionally which mechanism it wants.\n * This module turns the request into an argv the host enforces, or into an\n * {@link Unenforceable} refusal naming what the host lacks. Two callers share\n * it: the build graph confines every target's tool run under its declared\n * inputs and outputs (`@smthrs/targets/ExecSandbox`), and the Node\n * `ProcessConfinement` confines every approved agent command under the\n * grants in force.\n *\n * Three mechanisms, one contract:\n *\n * - Linux: bubblewrap. An empty root contains only enumerated system/runtime\n * paths and declared reads and writes. The workspace and root are remounted\n * read-only after mounting grants. A private tmp supplies HOME. The network\n * namespace is unshared unless explicitly opened; loopback-only is refused.\n * - macOS: seatbelt (`sandbox-exec`). Host reads are denied except enumerated\n * system/runtime paths, declared inputs, external reads, writes and private\n * tmp. Host credential locations are re-closed under broad grants. Writes\n * and network access are denied except what the request opens.\n * - Docker: `docker run` with a read-only root, declared reads mounted\n * read-only at their host paths, declared writes read-write, `--network\n * none` unless the request opens it. The image supplies the toolchain. It is\n * the path on Windows and on any host without the native mechanism.\n *\n * Every declared confinement is enforced or the caller fails closed. Nothing\n * here logs \"unenforced\" and carries on; the one caller that may run\n * unconfined when no mechanism exists, `ProcessConfinement`, says so in its\n * own options and never on this module's behalf.\n *\n * @since 1.0.0-rc.1\n */\n\nimport { execFileSync } from \"node:child_process\"\nimport { randomUUID } from \"node:crypto\"\nimport * as NodeFs from \"node:fs\"\nimport * as NodeOs from \"node:os\"\nimport * as NodePath from \"node:path\"\n\n/**\n * The network posture a run is confined to. `\"none\"` closes IP networking.\n * Build tools retain host Unix sockets by default. Native agent confinement\n * additionally closes host Unix sockets regardless of the IP posture, except\n * macOS's system DNS resolver endpoint when IP networking is open, and\n * refuses all Linux socket creation when IP networking is closed;\n * socketpair remains available for communication inside the process family.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport type Network = \"none\" | \"loopback\" | \"open\"\n\n/**\n * Which mechanism a request asks for. Absent means the platform's native\n * mechanism: bubblewrap on Linux, seatbelt on macOS.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport type Selection =\n | { readonly _tag: \"bubblewrap\" }\n | { readonly _tag: \"docker\"; readonly image: string }\n\n/**\n * What a caller asks for: the network posture, the mechanism choice, and the\n * read and write sets. Every path is workspace-relative and posix-shaped;\n * a read may name a file or a directory, a write names a directory. A\n * `readOnly` entry re-closes a subtree under a declared write.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Request {\n readonly network: Network\n /** Whether host Unix sockets are permitted; defaults to true for build tools. */\n readonly unixSockets?: boolean | undefined\n /** Closed-by-default process and IPC policy for untrusted native commands. */\n readonly strict?: boolean | undefined\n readonly mechanism?: Selection | undefined\n readonly reads: ReadonlyArray<string>\n readonly writes: ReadonlyArray<string>\n /**\n * Declared outputs whose leaf is a file rather than a directory. A file\n * cannot be bound writable on its own and does not exist before the tool\n * writes it, so its parent directory becomes the writable bind. Kept apart\n * from `writes` because only the caller knows which of the two a\n * declaration meant: inferring it from the path would hand a not-yet-created\n * `.cargo-home` or `dist.new` its parent, and a parent that is the\n * workspace root opens the whole workspace for the run.\n */\n readonly writeFiles?: ReadonlyArray<string> | undefined\n readonly readOnly?: ReadonlyArray<string> | undefined\n /**\n * Absolute host paths outside the workspace the tool reads: a git\n * submodule's local source repository, for one. Bound read-only where the\n * mechanism would otherwise hide them; a path that does not exist, or that\n * sits inside the workspace, is dropped.\n */\n readonly externalReads?: ReadonlyArray<string> | undefined\n}\n\n/**\n * The host facts mechanism selection reads. Injectable so every platform's\n * argv can be tested on every other platform.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Host {\n readonly platform: NodeJS.Platform\n /** Selected Xcode developer tree on a macOS runner. */\n readonly developerDirectory?: string | undefined\n /** The real host home, used to mask credentials and locate runtime caches. */\n readonly home?: string | undefined\n /** Resolves an executable name on `PATH` to an absolute path, or nothing. */\n readonly executable: (name: string) => string | undefined\n /** Whether a host path exists. */\n readonly exists: (path: string) => boolean\n /** Whether a host path is a directory. */\n readonly isDirectory: (path: string) => boolean\n /** Tests the entry itself, including dangling links; defaults to the real host. */\n readonly isSymbolicLink?: ((path: string) => boolean) | undefined\n /**\n * The names of a directory's direct children, or undefined when the host\n * cannot list it. Optional: a host without it keeps every declared file as\n * its own entry, which is correct and merely long.\n */\n readonly entries?: ((directory: string) => ReadonlyArray<string> | undefined) | undefined\n /**\n * Where a path's bytes actually live once every symbolic link on it is\n * followed, or `undefined` when the host cannot say. Write validation uses\n * the real host when this probe is omitted and refuses unresolved existing\n * paths. Read aliases are only collected when this probe is supplied.\n */\n readonly realpath?: ((path: string) => string | undefined) | undefined\n /**\n * The program a package-manager shim script runs: the absolute path on the\n * `# cmd-shim-target=` line pnpm's cmd-shim writes, or `undefined` for any\n * other file. Optional: a host without it treats a shim as the program.\n */\n readonly shimTarget?: ((path: string) => string | undefined) | undefined\n readonly uid: number | undefined\n readonly gid: number | undefined\n}\n\n/**\n * One selected enforcement mechanism.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport type Mechanism =\n | { readonly _tag: \"bubblewrap\"; readonly executable: string }\n | { readonly _tag: \"seatbelt\"; readonly executable: string }\n | { readonly _tag: \"docker\"; readonly executable: string; readonly image: string }\n | { readonly _tag: \"none\" }\n\n/**\n * The refusal a host that cannot enforce a declared confinement produces.\n * `mechanism` names what was selected, `missing` what the host lacks.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Unenforceable {\n readonly _tag: \"@smthrs/platform-node/SandboxUnenforceable\"\n readonly platform: NodeJS.Platform\n readonly mechanism: string\n readonly missing: string\n readonly message: string\n}\n\n/**\n * Checks for an {@link Unenforceable} refusal.\n *\n * @category guards\n * @since 1.0.0-rc.1\n */\nexport const isUnenforceable = (value: unknown): value is Unenforceable =>\n typeof value === \"object\" && value !== null &&\n (value as { readonly _tag?: unknown })._tag === \"@smthrs/platform-node/SandboxUnenforceable\"\n\n/**\n * Builds an {@link Unenforceable} refusal.\n *\n * @category constructors\n * @since 1.0.0-rc.1\n */\nexport const unenforceable = (\n platform: NodeJS.Platform,\n mechanism: string,\n missing: string,\n detail: string\n): Unenforceable => ({\n _tag: \"@smthrs/platform-node/SandboxUnenforceable\",\n platform,\n mechanism,\n missing,\n message: `sandbox: the declared confinement cannot be enforced on this ${platform} host: ${detail}.`\n})\n\n/**\n * The resolved confinement one spawn runs under. Every path is absolute and\n * canonical.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport interface Plan {\n readonly mechanism: Exclude<Mechanism, { readonly _tag: \"none\" }>\n readonly network: Network\n /** Whether host Unix sockets are permitted; defaults to true for build tools. */\n readonly unixSockets?: boolean | undefined\n /** Closed-by-default process and IPC policy for untrusted native commands. */\n readonly strict?: boolean | undefined\n readonly workspaceRoot: string\n readonly cwd: string\n readonly reads: ReadonlyArray<string>\n /**\n * Paths inside a granted directory that the declaration did not cover,\n * re-closed after the grant. Only a mechanism with deny rules (seatbelt)\n * folds; the others keep every declared file as its own entry.\n */\n readonly readDenies: ReadonlyArray<string>\n readonly writes: ReadonlyArray<string>\n readonly readOnly: ReadonlyArray<string>\n /**\n * Where a declared read's bytes actually live when the path reaches them\n * through a symbolic link that leaves the workspace.\n *\n * Bubblewrap needs them because it replaces `/tmp` with a private tmpfs, so\n * a link into the host's `/tmp` (the scratch tree's `node_modules`, which\n * points back at the real workspace) resolves to nothing inside the sandbox.\n * Binding the real location read-only makes the link resolve to the same\n * bytes the host sees, which is what declaring the read asked for. Docker\n * mounts them for the same reason: the image supplies everything outside\n * the workspace, and these paths are on the host. Seatbelt leaves the host\n * filesystem in place but must grant the resolved spelling too. The\n * request's own `externalReads` land here too.\n */\n readonly externalReads: ReadonlyArray<string>\n /** A host directory the run may scribble in; created by the caller, removed after. */\n readonly tmp: string\n readonly uid: number | undefined\n readonly gid: number | undefined\n}\n\nconst pathSeparator = (platform: NodeJS.Platform): string => platform === \"win32\" ? \";\" : \":\"\n\n/** Ambient host environment for runtime lookup and cache locations. */\nconst ambientEnvironment = (): Readonly<Record<string, string | undefined>> => process.env\n\n/**\n * The real host: platform, `PATH` lookup, and filesystem probes.\n *\n * @category resolution\n * @since 0.1.0\n */\nexport const host = (env: Readonly<Record<string, string | undefined>> = ambientEnvironment()): Host => {\n const platform = process.platform\n let developerDirectory = env[\"DEVELOPER_DIR\"]\n if (platform === \"darwin\" && developerDirectory === undefined) {\n try {\n developerDirectory = execFileSync(\"/usr/bin/xcode-select\", [\"-p\"], { encoding: \"utf8\", timeout: 2_000 }).trim()\n } catch {\n // A host without Xcode needs no developer-tree grant.\n }\n }\n const entries = (env[\"PATH\"] ?? \"\").split(pathSeparator(platform)).filter((entry) => entry !== \"\")\n const extensions = platform === \"win32\" ? (env[\"PATHEXT\"] ?? \".EXE;.CMD;.BAT\").split(\";\") : [\"\"]\n return {\n platform,\n developerDirectory,\n home: NodeOs.homedir(),\n executable: (name) => {\n if (NodePath.isAbsolute(name)) return isExecutable(name) ? name : undefined\n for (const entry of entries) {\n for (const extension of extensions) {\n const candidate = NodePath.join(entry, name + extension)\n if (isExecutable(candidate)) return candidate\n }\n }\n return undefined\n },\n exists: (path) => NodeFs.existsSync(path),\n isSymbolicLink,\n isDirectory: (path) => {\n try {\n return NodeFs.statSync(path).isDirectory()\n } catch {\n return false\n }\n },\n entries: (directory) => {\n try {\n return NodeFs.readdirSync(directory)\n } catch {\n return undefined\n }\n },\n realpath: (path) => {\n try {\n return NodeFs.realpathSync(path)\n } catch {\n return undefined\n }\n },\n shimTarget,\n uid: typeof process.getuid === \"function\" ? process.getuid() : undefined,\n gid: typeof process.getgid === \"function\" ? process.getgid() : undefined\n }\n}\n\n/** A cmd-shim is a page of shell; a larger file is some other program. */\nconst shimLimit = 64 * 1024\n\nconst shimTarget = (path: string): string | undefined => {\n try {\n if (NodeFs.statSync(path).size > shimLimit) return undefined\n const text = NodeFs.readFileSync(path, \"utf8\")\n if (!text.startsWith(\"#!\")) return undefined\n const target = /^# cmd-shim-target=(.+)$/m.exec(text)?.[1]\n return target !== undefined && NodePath.isAbsolute(target) ? target : undefined\n } catch {\n return undefined\n }\n}\n\nconst isExecutable = (path: string): boolean => {\n try {\n NodeFs.accessSync(path, NodeFs.constants.X_OK)\n return NodeFs.statSync(path).isFile()\n } catch {\n return false\n }\n}\n\n/**\n * Selects the mechanism a request runs under on a host.\n *\n * A docker selection is honored when `docker` is on `PATH` and refused\n * otherwise; a bubblewrap selection is refused off Linux. With no selection\n * the platform decides: bubblewrap on Linux, seatbelt on macOS, and a refusal\n * elsewhere, because Windows has no user-level process sandbox and Docker\n * needs an image only the caller can name.\n *\n * @category resolution\n * @since 1.0.0-rc.1\n */\nexport const select = (\n request: Pick<Request, \"network\" | \"mechanism\">,\n hostFacts: Host\n): Exclude<Mechanism, { readonly _tag: \"none\" }> | Unenforceable => {\n const declared = request.mechanism\n const platform = hostFacts.platform\n if (declared !== undefined && declared._tag === \"docker\") {\n const executable = hostFacts.executable(\"docker\")\n if (executable === undefined) {\n return unenforceable(platform, \"docker\", \"docker\", \"a docker mechanism is selected but docker is not on PATH\")\n }\n return { _tag: \"docker\", executable, image: declared.image }\n }\n if (declared !== undefined && platform !== \"linux\") {\n return unenforceable(\n platform,\n \"bubblewrap\",\n \"linux\",\n \"bubblewrap is selected but runs only on Linux; on macOS the seatbelt mechanism is selected when no mechanism is named\"\n )\n }\n if (platform === \"linux\") {\n const executable = hostFacts.executable(\"bwrap\")\n if (executable === undefined) {\n return unenforceable(platform, \"bubblewrap\", \"bwrap\", \"bwrap is not on PATH (install the bubblewrap package)\")\n }\n if (request.network === \"loopback\") {\n return unenforceable(\n platform,\n \"bubblewrap\",\n \"network: true\",\n \"bubblewrap cannot expose only the host loopback interface: sharing that interface also grants full host networking; \" +\n \"open the whole network to opt into that access\"\n )\n }\n return { _tag: \"bubblewrap\", executable }\n }\n if (platform === \"darwin\") {\n const executable = hostFacts.executable(\"/usr/bin/sandbox-exec\")\n if (executable === undefined) {\n return unenforceable(platform, \"seatbelt\", \"/usr/bin/sandbox-exec\", \"/usr/bin/sandbox-exec is missing\")\n }\n return { _tag: \"seatbelt\", executable }\n }\n return unenforceable(\n platform,\n \"docker\",\n \"docker image\",\n `${platform} has no process sandbox of its own; select a docker mechanism with an image`\n )\n}\n\nconst toPosix = (path: string): string => path.split(NodePath.sep).join(\"/\")\n\n/**\n * The path with the symlinks of its existing ancestors resolved. A path that\n * does not exist yet keeps its unresolved tail below the deepest ancestor\n * that does; a path with no existing ancestor is returned as given.\n */\nconst realized = (path: string): string => {\n let head = path\n const tail: Array<string> = []\n while (head !== NodePath.dirname(head)) {\n try {\n const real = NodeFs.realpathSync(head)\n return tail.length === 0 ? real : NodePath.join(real, ...tail)\n } catch {\n tail.unshift(NodePath.basename(head))\n head = NodePath.dirname(head)\n }\n }\n return path\n}\n\nconst insideRoot = (root: string, candidate: string): boolean => {\n const relative = NodePath.relative(root, candidate)\n return relative === \"\" ||\n (relative !== \"..\" && !relative.startsWith(`..${NodePath.sep}`) && !NodePath.isAbsolute(relative))\n}\n\n/** Unlike exists/stat, lstat sees dangling links and does not follow them. */\nconst isSymbolicLink = (path: string): boolean => {\n try {\n return NodeFs.lstatSync(path).isSymbolicLink()\n } catch (cause) {\n if ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return false\n throw cause\n }\n}\n\n/** Check each component, including existing ancestors of a missing output. */\nconst safeWrite = (root: string, path: string, hostFacts: Host): boolean => {\n if (!insideRoot(root, path)) return false\n try {\n const realpath = hostFacts.realpath ?? NodeFs.realpathSync\n const link = hostFacts.isSymbolicLink ?? isSymbolicLink\n if (realpath(root) !== root) return false\n let current = root\n for (const component of NodePath.relative(root, path).split(NodePath.sep).filter(Boolean)) {\n current = NodePath.join(current, component)\n if (link(current)) return false\n if (hostFacts.exists(current) && realpath(current) !== current) return false\n }\n return true\n } catch {\n return false\n }\n}\n\nconst unsafeWrite = (platform: NodeJS.Platform, mechanism: string, path: string): Unenforceable =>\n unenforceable(\n platform,\n mechanism,\n \"canonical workspace write path\",\n `write path ${path} has a symbolic link, an unresolved ancestor, or leaves the canonical workspace`\n )\n\n/**\n * Rechecks write grants before directory creation and before rendering. Throws\n * an {@link Unenforceable} refusal if a path changed since planning. Callers\n * must keep the workspace stable until the operating system consumes mounts.\n *\n * @category resolution\n * @since 0.1.0\n */\nexport const validateWrites = (confinement: Plan, hostFacts: Host = host()): void => {\n for (const write of confinement.writes) {\n if (!safeWrite(confinement.workspaceRoot, write, hostFacts)) {\n throw unsafeWrite(hostFacts.platform, confinement.mechanism._tag, write)\n }\n }\n}\n\n/**\n * Folds a declared file set into directories plus the entries to re-close.\n *\n * The seatbelt compiler caps one rule's data at 64 KiB, so a workspace with a\n * few thousand declared sources cannot be granted file by file. Deepest\n * first, a directory in which at least as many entries are covered as not is\n * granted whole, and its uncovered entries are listed for a deny rule that\n * follows the grant. The result admits exactly the declared set plus the\n * granted directories' future entries. A directory whose uncovered entry\n * still holds a covered path deeper down is never folded over that entry,\n * the workspace root is never folded, and a host that cannot list a\n * directory leaves its files as they were. Covered means a declared read, a\n * declared write, or the private tmp.\n */\nconst fold = (\n root: string,\n declared: ReadonlyArray<string>,\n alsoCovered: ReadonlyArray<string>,\n hostFacts: Host\n): { readonly reads: ReadonlyArray<string>; readonly denies: ReadonlyArray<string> } => {\n const entries = hostFacts.entries\n if (entries === undefined) return { reads: declared, denies: [] }\n const covered = new Set([...declared, ...alsoCovered])\n const candidates = new Set<string>()\n for (const path of declared) {\n let current = NodePath.dirname(path)\n while (insideRoot(root, current) && current !== root && !candidates.has(current)) {\n candidates.add(current)\n current = NodePath.dirname(current)\n }\n }\n // An undeclared directory on the way down to a covered write or read-only\n // path is not a candidate, so nothing else would stop its parent being\n // folded over it. The deny that follows the grant names the directory, and\n // because later rules win in SBPL it closes reads on the very subtree the\n // plan grants a write to.\n const holdsCovered = new Set<string>()\n for (const path of alsoCovered) {\n let current = NodePath.dirname(path)\n while (insideRoot(root, current) && current !== root && !holdsCovered.has(current)) {\n holdsCovered.add(current)\n current = NodePath.dirname(current)\n }\n }\n const deepestFirst = [...candidates].sort((left, right) => right.length - left.length || (left < right ? -1 : 1))\n const promoted: Array<string> = []\n const denies: Array<string> = []\n for (const directory of deepestFirst) {\n if (covered.has(directory)) continue\n const children = entries(directory)\n if (children === undefined || children.length === 0) continue\n const uncovered: Array<string> = []\n let coveredCount = 0\n let partial = false\n for (const child of children) {\n const path = NodePath.join(directory, child)\n if (covered.has(path)) coveredCount += 1\n else if (candidates.has(path) || holdsCovered.has(path)) partial = true\n else uncovered.push(path)\n }\n if (partial || coveredCount === 0 || coveredCount < uncovered.length) continue\n covered.add(directory)\n promoted.push(directory)\n denies.push(...uncovered)\n }\n return { reads: [...declared, ...promoted], denies }\n}\n\n/**\n * Orders paths so that every subtree is contiguous and its root comes first.\n *\n * Comparing on the path plus its separator is what makes that hold: `a/b` and\n * `a/b/c` sort around `a/b.ts`, because `.` precedes `/`, and a plain\n * comparison would then put a sibling between an ancestor and its child.\n */\nconst bySubtree = (left: string, right: string): number => {\n const leftKey = left + NodePath.sep\n const rightKey = right + NodePath.sep\n return leftKey < rightKey ? -1 : 1\n}\n\n/**\n * Drops a path covered by another entry of the same set, keeping the set ordered.\n *\n * One sweep in subtree order carries the shallowest kept ancestor forward,\n * because searching the kept set per path is quadratic in the sibling files a\n * bubblewrap or Docker plan declares one by one.\n */\nconst collapse = (paths: ReadonlyArray<string>): ReadonlyArray<string> => {\n const unique = [...new Set(paths)]\n const kept = new Set<string>()\n let ancestor: string | undefined\n for (const path of [...unique].sort(bySubtree)) {\n if (ancestor !== undefined && path.startsWith(ancestor + NodePath.sep)) continue\n kept.add(path)\n ancestor = path\n }\n return unique.filter((path) => kept.has(path)).sort((left, right) =>\n left.length - right.length || (left < right ? -1 : 1)\n )\n}\n\n/**\n * Resolves a request against a workspace into a {@link Plan}, or refuses.\n *\n * Every relative path is anchored at the canonical workspace root; one that\n * escapes the workspace is dropped, because the sandbox never grants anything\n * outside the tree the key covers. Reads that do not exist are dropped too:\n * bubblewrap cannot bind a missing path, and a declared input that vanished is\n * the executor's measurement failure, not this module's. A write containing\n * any symbolic link below the canonical root is refused, including a missing\n * output below a linked ancestor.\n *\n * @category resolution\n * @since 0.1.0\n */\nexport const plan = (\n request: Request,\n location: { readonly workspaceRoot: string; readonly cwd: string; readonly tmp: string },\n hostFacts: Host\n): Plan | Unenforceable => {\n const selected = select(request, hostFacts)\n if (isUnenforceable(selected)) return selected\n let root: string\n try {\n const canonical = (hostFacts.realpath ?? NodeFs.realpathSync)(location.workspaceRoot)\n if (canonical === undefined) return unsafeWrite(hostFacts.platform, selected._tag, location.workspaceRoot)\n root = canonical\n } catch {\n return unsafeWrite(hostFacts.platform, selected._tag, location.workspaceRoot)\n }\n const anchor = (relative: string): string | undefined => {\n const absolute = NodePath.resolve(root, ...relative.split(\"/\"))\n return insideRoot(root, absolute) ? absolute : undefined\n }\n const declared: Array<string> = []\n // A declared link grants its target as well: native read allowlists and\n // Docker's image filesystem otherwise hide bytes outside the workspace.\n const externalReads: Array<string> = []\n for (const relative of request.reads) {\n const absolute = anchor(relative)\n if (absolute === undefined || !hostFacts.exists(absolute)) continue\n declared.push(absolute)\n const real = hostFacts.realpath?.(absolute)\n if (real !== undefined && real !== absolute && !insideRoot(root, real)) externalReads.push(real)\n }\n for (const path of request.externalReads ?? []) {\n if (!NodePath.isAbsolute(path) || insideRoot(root, path) || !hostFacts.exists(path)) continue\n externalReads.push(hostFacts.realpath?.(path) ?? path)\n }\n // A write names a directory the tool may fill, and is bound as declared\n // whether or not it exists yet: a name is never read as a file, because a\n // dot in `.cargo-home` or `dist.new` would otherwise widen the bind to the\n // parent, and a parent that is the workspace root opens every file in it.\n // A path that is already a file on the host is the one exception, since a\n // file cannot be bound as a writable directory.\n const writes: Array<string> = []\n const add = (absolute: string): Unenforceable | undefined => {\n if (!safeWrite(root, absolute, hostFacts)) return unsafeWrite(hostFacts.platform, selected._tag, absolute)\n writes.push(absolute)\n return undefined\n }\n for (const relative of request.writes) {\n const absolute = anchor(relative)\n if (absolute === undefined) continue\n if (!safeWrite(root, absolute, hostFacts)) return unsafeWrite(hostFacts.platform, selected._tag, absolute)\n const refusal = add(\n hostFacts.exists(absolute) && !hostFacts.isDirectory(absolute) ? NodePath.dirname(absolute) : absolute\n )\n if (refusal !== undefined) return refusal\n }\n // A declared output file opens its parent: a file cannot be bound before it\n // exists, and a tool that writes by rename needs the directory anyway.\n for (const relative of request.writeFiles ?? []) {\n const absolute = anchor(relative)\n if (absolute === undefined) continue\n if (!safeWrite(root, absolute, hostFacts)) return unsafeWrite(hostFacts.platform, selected._tag, absolute)\n const refusal = add(\n hostFacts.exists(absolute) && hostFacts.isDirectory(absolute) ? absolute : NodePath.dirname(absolute)\n )\n if (refusal !== undefined) return refusal\n }\n const readOnly: Array<string> = []\n for (const relative of request.readOnly ?? []) {\n const absolute = anchor(relative)\n if (absolute !== undefined) readOnly.push(absolute)\n }\n const folded = selected._tag === \"seatbelt\"\n ? fold(root, declared, [...writes, ...readOnly, location.tmp], hostFacts)\n : { reads: declared, denies: [] }\n return {\n mechanism: selected,\n network: request.network,\n unixSockets: request.unixSockets,\n strict: request.strict,\n workspaceRoot: root,\n cwd: location.cwd,\n reads: collapse(folded.reads),\n readDenies: collapse(folded.denies),\n writes: collapse(writes),\n readOnly: collapse(readOnly),\n externalReads: collapse(externalReads),\n tmp: location.tmp,\n uid: hostFacts.uid,\n gid: hostFacts.gid\n }\n}\n\n/**\n * Where corepack keeps the package-manager binaries it manages, resolved the\n * way corepack itself resolves it from a host environment.\n *\n * A `pnpm`, `yarn`, or `npm` on `PATH` is often corepack's shim, and the\n * program it execs lives in this directory, not on `PATH`. Redirecting `HOME`\n * and `XDG_CACHE_HOME` to the private tmp moves that directory out from under\n * the shim, which then tries to re-download the package manager from the\n * registry \u2014 and a confined run has no network, so every rule that drives the\n * package manager fails with a connect error that names the registry rather\n * than the missing cache.\n */\nconst corepackHome = (env: Readonly<Record<string, string | undefined>>, home: string): string => {\n const declared = env[\"COREPACK_HOME\"]\n if (declared !== undefined && declared !== \"\") return declared\n const base = env[\"XDG_CACHE_HOME\"] ?? env[\"LOCALAPPDATA\"] ??\n NodePath.join(home, process.platform === \"win32\" ? \"AppData/Local\" : \".cache\")\n return NodePath.join(base, \"node\", \"corepack\")\n}\n\n/**\n * Environment the confined process receives on top of the tool environment:\n * a private temporary directory and home, so nothing a tool caches lands in\n * the real home or the shared temp directory.\n *\n * `COREPACK_HOME` is the one host cache kept: it holds the package-manager\n * program a corepack shim execs, the run may only read it (the mechanisms\n * deny writes outside the workspace), and without it a confined `pnpm` is a\n * shim with nothing to run. A non-default cache location requires an explicit\n * `externalReads` grant; preserving the variable does not grant access.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const environment = (\n confinement: Plan,\n hostEnv: Readonly<Record<string, string | undefined>> = ambientEnvironment(),\n hostHome: string = NodeOs.homedir()\n): Readonly<Record<string, string>> => {\n const tmp = confinement.mechanism._tag === \"seatbelt\" ? confinement.tmp : \"/tmp\"\n const home = NodePath.posix.join(toPosix(tmp), \"home\")\n return {\n COREPACK_HOME: corepackHome(hostEnv, hostHome),\n HOME: home,\n TMPDIR: tmp,\n TMP: tmp,\n TEMP: tmp,\n XDG_CACHE_HOME: NodePath.posix.join(toPosix(tmp), \"cache\"),\n XDG_CONFIG_HOME: NodePath.posix.join(toPosix(tmp), \"config\"),\n XDG_DATA_HOME: NodePath.posix.join(toPosix(tmp), \"data\"),\n XDG_STATE_HOME: NodePath.posix.join(toPosix(tmp), \"state\")\n }\n}\n\n/** The fixed runtime surface, never the host root, home, PATH, or arbitrary env paths. */\nconst runtimeReads = (hostFacts: Host): ReadonlyArray<string> => {\n const paths = [\n \"/usr\",\n \"/bin\",\n \"/sbin\",\n \"/lib\",\n \"/lib64\",\n \"/nix/store\",\n \"/opt/homebrew\",\n // A Smithers machine's toolchain layer: the repository's Node, pnpm, Go,\n // Rust and Python, which the machine's environment puts first on PATH\n // (packages/backend/microsandbox layers.go toolchainRoot). Never all of\n // /opt/smithers, which holds the machine's environment and helpers.\n \"/opt/smithers/toolchain\",\n \"/opt/smithers/rust\",\n \"/System/Library\",\n \"/System/Volumes/Preboot/Cryptexes/OS\",\n \"/private/var/db/dyld\",\n // macOS shell and developer-tool dispatch read these system selectors.\n \"/var/select/sh\",\n \"/private/var/select/sh\",\n \"/var/select/developer_dir\",\n \"/private/var/select/developer_dir\",\n \"/Library/Apple\",\n \"/Library/Developer\",\n \"/Applications/Xcode.app/Contents/Developer\",\n \"/Applications/Xcode.app/Contents/Info.plist\",\n \"/Applications/Xcode.app/Contents/Frameworks\",\n \"/Applications/Xcode.app/Contents/SharedFrameworks\",\n \"/Applications/Xcode.app/Contents/PlugIns\",\n \"/etc/ld.so.cache\",\n \"/etc/ld.so.conf\",\n \"/etc/ld.so.conf.d\",\n \"/etc/localtime\",\n \"/etc/ssl\",\n \"/etc/hosts\",\n \"/etc/resolv.conf\",\n \"/etc/nsswitch.conf\"\n ]\n const developerDirectory = hostFacts.developerDirectory\n if (\n hostFacts.platform === \"darwin\" && developerDirectory !== undefined &&\n /^\\/Applications\\/Xcode[^/]*\\.app\\/Contents\\/Developer$/.test(developerDirectory)\n ) {\n paths.push(developerDirectory)\n }\n if (hostFacts.home !== undefined) paths.push(NodePath.join(hostFacts.home, \".cache/node/corepack\"))\n for (const name of [\"node\", \"bun\"]) {\n const executable = hostFacts.executable(name)\n if (executable !== undefined) {\n paths.push(executable)\n const real = hostFacts.realpath?.(executable) ?? executable\n paths.push(real)\n // Node distributions keep npm/corepack here; never grant the install\n // prefix itself, which can be the real home for a custom installation.\n paths.push(NodePath.resolve(NodePath.dirname(real), \"../lib/node_modules\"))\n }\n }\n const present = paths.filter((path) => hostFacts.exists(path))\n return collapse(present.flatMap((path) => [path, hostFacts.realpath?.(path) ?? path]))\n}\n\n/** Credentials remain closed even when a workspace or runtime grant contains the home. */\nconst credentialPaths = (hostFacts: Host): ReadonlyArray<string> => {\n const home = hostFacts.home\n if (home === undefined) return []\n return collapse([\n \".aws\",\n \".ssh\",\n \".gnupg\",\n \".azure\",\n \".kube\",\n \".docker\",\n \".config/gcloud\",\n \".config/gh\",\n \".npmrc\",\n \".netrc\",\n \".git-credentials\"\n ].flatMap((relative) => {\n const path = NodePath.join(home, relative)\n return [path, hostFacts.realpath?.(path) ?? path]\n }))\n}\n\n/**\n * The program a confined run execs, made reachable inside its empty root.\n *\n * Bubblewrap resolves a bare program name with `execvp` against `PATH` inside\n * the new root, and that root holds only the runtime paths and the declared\n * sets. A package manager installed outside them \u2014 pnpm under the\n * `pnpm/action-setup` prefix on a CI runner, a `.bin` link into a\n * `node_modules` tree \u2014 is on the host `PATH` and absent from the sandbox, so\n * the run died with `bwrap: execvp pnpm` before the tool started (#3140).\n *\n * A bare name is resolved on the tool's `PATH` (the host's when none is\n * given). A program already inside a granted path is left exactly as spelled,\n * so a multi-call binary keeps the name it dispatches on, and so is one inside\n * the workspace, whose reads only the declaration may open. Otherwise the run\n * execs the program's real file, and the nearest package directory holding it\n * (its `package.json`, at most three levels up, never the home or the root)\n * is bound read-only, because a script like `pnpm.cjs` loads its siblings.\n * With no package directory only the real file is bound. A name that resolves\n * nowhere is left for `execvp` to report.\n *\n * A package-manager shim script is followed like a link. `pnpm self-update`\n * under `pnpm/action-setup` installs pnpm as a cmd-shim in `$PNPM_HOME/bin`\n * that runs node on a package in pnpm's global store, a path relative to the\n * script; binding the script alone left that package missing. When the\n * program the shim names is executable, the run execs its real file and binds\n * its package as above.\n *\n * @since 1.0.0\n * @private\n */\nexport const launcher = (\n program: string,\n searchPath: string | undefined,\n granted: ReadonlyArray<string>,\n workspaceRoot: string,\n hostFacts: Host\n): { readonly program: string; readonly reads: ReadonlyArray<string> } => {\n const unchanged = { program, reads: [] }\n if (program.includes(\"/\") && !NodePath.isAbsolute(program)) return unchanged\n const found = NodePath.isAbsolute(program)\n ? hostFacts.executable(program)\n : searchPath === undefined\n ? hostFacts.executable(program)\n : searchPath.split(\":\").filter((entry) => NodePath.isAbsolute(entry))\n .map((entry) => hostFacts.executable(NodePath.join(entry, program)))\n .find((candidate) => candidate !== undefined)\n if (found === undefined || granted.some((parent) => insideRoot(parent, found))) return unchanged\n const resolved = hostFacts.realpath?.(found) ?? found\n const named = hostFacts.shimTarget?.(resolved)\n const real = named === undefined || hostFacts.executable(named) === undefined\n ? resolved\n : hostFacts.realpath?.(named) ?? named\n if ([found, resolved, real].some((path) => insideRoot(workspaceRoot, path))) return unchanged\n if (granted.some((parent) => insideRoot(parent, real))) return { program: real, reads: [] }\n let directory = NodePath.dirname(real)\n for (let level = 0; level < 3; level++) {\n if (directory === NodePath.dirname(directory) || directory === hostFacts.home) break\n if (hostFacts.exists(NodePath.join(directory, \"package.json\"))) return { program: real, reads: [directory] }\n directory = NodePath.dirname(directory)\n }\n return { program: real, reads: [real] }\n}\n\n/**\n * The bubblewrap argv for a plan.\n *\n * Order matters: bubblewrap applies operations in argument order, so the\n * empty root goes first, runtime paths are bound, the workspace tmpfs shadows\n * any broad runtime grant, declared paths are bound on top, and re-closed\n * subtrees are bound read-only over their writable parents. The tmpfs is\n * remounted read-only last so an undeclared write\n * under the workspace fails with `EROFS` instead of vanishing into the tmpfs.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const bubblewrap = (\n confinement: Plan,\n argv: ReadonlyArray<string>,\n hostFacts: Host = host(),\n searchPath?: string | undefined\n): ReadonlyArray<string> => {\n if (confinement.mechanism._tag !== \"bubblewrap\") throw new Error(\"bubblewrap argv needs a bubblewrap plan\")\n validateWrites(confinement, hostFacts)\n const home = \"/tmp/home\"\n const out: Array<string> = [\n confinement.mechanism.executable,\n \"--tmpfs\",\n \"/\"\n ]\n const runtime = runtimeReads(hostFacts)\n for (const read of runtime) out.push(\"--ro-bind\", read, read)\n out.push(\n \"--dev\",\n \"/dev\",\n \"--proc\",\n \"/proc\",\n \"--tmpfs\",\n \"/tmp\",\n \"--dir\",\n home,\n \"--dir\",\n \"/tmp/cache\",\n \"--tmpfs\",\n confinement.workspaceRoot,\n \"--dir\",\n confinement.cwd\n )\n // The target of a link that leaves the workspace is bound first, so the\n // link a declared read then binds resolves to the same bytes the host sees\n // even when `/tmp` above the target has just become a private tmpfs.\n for (const real of confinement.externalReads) out.push(\"--ro-bind\", real, real)\n const [program, ...args] = argv\n const launch = program === undefined ? undefined : launcher(\n program,\n searchPath,\n [...runtime, ...confinement.reads, ...confinement.writes, ...confinement.externalReads],\n confinement.workspaceRoot,\n hostFacts\n )\n for (const read of launch?.reads ?? []) out.push(\"--ro-bind\", read, read)\n for (const read of confinement.reads) out.push(\"--ro-bind\", read, read)\n for (const write of confinement.writes) out.push(\"--bind\", write, write)\n for (const closed of confinement.readOnly) {\n if (confinement.writes.some((write) => closed === write || closed.startsWith(write + NodePath.sep))) {\n out.push(\"--ro-bind-try\", closed, closed)\n }\n }\n const granted = [\n ...runtime,\n ...(launch?.reads ?? []),\n ...confinement.reads,\n ...confinement.writes,\n ...confinement.externalReads\n ]\n for (const path of credentialPaths(hostFacts)) {\n if (!hostFacts.exists(path) || !granted.some((parent) => insideRoot(parent, path))) continue\n // An explicit external read can name a credential or a file inside it.\n // Mask first, then restore only those explicit paths below the mask.\n if (confinement.externalReads.some((parent) => insideRoot(parent, path))) continue\n const directory = hostFacts.isDirectory(path)\n if (directory) out.push(\"--tmpfs\", path)\n else out.push(\"--ro-bind\", \"/dev/null\", path)\n for (const read of confinement.externalReads) {\n if (insideRoot(path, read)) out.push(\"--ro-bind\", read, read)\n }\n if (directory) out.push(\"--remount-ro\", path)\n }\n // `--remount-ro` acts on the mount at that exact path. When the root itself\n // is a declared write (a declared output file at the top level opens its\n // parent), that mount is the writable bind, and remounting it would deny\n // every write the declaration admitted; the tmpfs it replaced needs no\n // re-closing.\n if (!confinement.writes.includes(confinement.workspaceRoot)) out.push(\"--remount-ro\", confinement.workspaceRoot)\n out.push(\"--remount-ro\", \"/\")\n out.push(\"--chdir\", confinement.cwd, \"--unshare-all\", \"--new-session\", \"--die-with-parent\")\n // Bubblewrap cannot expose only the host loopback interface. Planning\n // refuses that posture on Linux, so only an explicit full-network opening\n // reaches `--share-net` here.\n if (confinement.network === \"loopback\") {\n throw new Error(\"bubblewrap cannot render loopback-only networking\")\n }\n if (confinement.network === \"open\") out.push(\"--share-net\")\n out.push(\"--\", ...(launch === undefined ? argv : [launch.program, ...args]))\n return out\n}\n\n/** Escapes one string for a seatbelt profile literal. */\nconst sbpl = (value: string): string => `\"${value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"\\\"\", \"\\\\\\\"\")}\"`\n\n/** Every directory from the workspace root down to the parent of each path. */\nconst ancestors = (root: string, paths: ReadonlyArray<string>): ReadonlyArray<string> => {\n const found = new Set<string>([root])\n for (const path of paths) {\n let current = NodePath.dirname(path)\n while (current.length >= root.length && current !== NodePath.dirname(current)) {\n found.add(current)\n if (current === root) break\n current = NodePath.dirname(current)\n }\n }\n return [...found].sort()\n}\n\n/**\n * The seatbelt profile for a plan.\n *\n * File reads start closed, then enumerated runtime paths and declared sets\n * are opened. The workspace is re-closed before its grants so a workspace\n * under a runtime root does not inherit the broad runtime grant. Credentials\n * are re-closed before explicit external reads, which can opt into them.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const seatbelt = (confinement: Plan, hostFacts: Host = host()): string => {\n if (confinement.mechanism._tag !== \"seatbelt\") throw new Error(\"a seatbelt profile needs a seatbelt plan\")\n validateWrites(confinement, hostFacts)\n const lines: Array<string> = [\"(version 1)\", confinement.strict === true ? \"(deny default)\" : \"(allow default)\"]\n if (confinement.strict === true) {\n // No Mach lookup, Apple Events or LaunchServices grants: work must stay\n // in descendants inheriting this sandbox, never delegated to a host daemon.\n lines.push(\n \"(allow process-exec)\",\n \"(allow process-fork)\",\n \"(allow signal (target same-sandbox))\",\n \"(allow process-info* (target same-sandbox))\",\n \"(allow sysctl-read (sysctl-name-prefix \\\"hw.\\\") (sysctl-name-prefix \\\"machdep.cpu.\\\") (sysctl-name \\\"kern.argmax\\\") (sysctl-name \\\"kern.hostname\\\") (sysctl-name \\\"kern.maxfilesperproc\\\") (sysctl-name \\\"kern.maxproc\\\") (sysctl-name \\\"kern.osproductversion\\\") (sysctl-name \\\"kern.osrelease\\\") (sysctl-name \\\"kern.ostype\\\") (sysctl-name \\\"kern.osversion\\\") (sysctl-name \\\"kern.secure_kernel\\\") (sysctl-name \\\"kern.usrstack64\\\") (sysctl-name \\\"kern.version\\\") (sysctl-name \\\"sysctl.proc_cputype\\\") (sysctl-name \\\"vm.loadavg\\\"))\",\n \"(allow mach-host*)\"\n )\n if (confinement.network === \"open\") lines.push(\"(allow network*)\")\n }\n if (confinement.network !== \"open\") {\n lines.push(\"(deny network*)\")\n if (confinement.unixSockets !== false) lines.push(\"(allow network* (local unix-socket))\")\n if (confinement.network === \"loopback\") {\n lines.push(\n \"(allow network-bind (local ip \\\"localhost:*\\\"))\",\n \"(allow network-inbound (local ip \\\"localhost:*\\\"))\",\n \"(allow network-outbound (remote ip \\\"localhost:*\\\"))\"\n )\n }\n }\n if (confinement.network === \"open\" && confinement.unixSockets === false) {\n lines.push(\"(deny network* (local unix-socket))\")\n if (confinement.strict === true) {\n // Darwin resolves DNS through this trusted endpoint. Full IP authority\n // permits DNS only; other host daemons remain closed, including Mach IPC.\n lines.push(\"(allow network-outbound (literal \\\"/private/var/run/mDNSResponder\\\"))\")\n }\n }\n const writable = [...confinement.writes, confinement.tmp]\n lines.push(\n \"(deny file-write*)\",\n `(allow file-write* ${confinement.strict === true ? \"(literal \\\"/dev/null\\\")\" : \"(subpath \\\"/dev\\\")\"} ${\n writable.map((path) => `(subpath ${sbpl(path)})`).join(\" \")\n })`\n )\n if (confinement.readOnly.length > 0) {\n lines.push(`(deny file-write* ${confinement.readOnly.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n }\n lines.push(\n \"(deny file-read*)\",\n confinement.strict === true\n ? \"(allow file-read* (literal \\\"/\\\") (literal \\\"/dev/null\\\") (literal \\\"/dev/zero\\\") (literal \\\"/dev/random\\\") (literal \\\"/dev/urandom\\\"))\"\n : \"(allow file-read* (literal \\\"/\\\") (subpath \\\"/dev\\\"))\",\n // macOS's system aliases need readlink access before a shell can execute\n // an admitted file through them. Literal grants expose the links only;\n // their target directories still cannot be listed or read wholesale.\n \"(allow file-read* (literal \\\"/tmp\\\") (literal \\\"/var\\\") (literal \\\"/etc\\\"))\"\n )\n const runtime = runtimeReads(hostFacts)\n if (runtime.length > 0) lines.push(`(allow file-read* ${runtime.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n const readable = [...confinement.reads, ...confinement.writes, confinement.tmp]\n // Node realpath walks every ancestor, including parents outside the\n // workspace. Grant only their metadata, without listing or reading bytes.\n const traversable = ancestors(\"/\", [...runtime, ...readable, ...confinement.externalReads])\n .filter((path) => !insideRoot(confinement.workspaceRoot, path))\n lines.push(`(allow file-read-metadata ${traversable.map((path) => `(literal ${sbpl(path)})`).join(\" \")})`)\n const listable = ancestors(\n confinement.workspaceRoot,\n readable.filter((path) => insideRoot(confinement.workspaceRoot, path))\n )\n lines.push(\n `(deny file-read* (subpath ${sbpl(confinement.workspaceRoot)}))`\n )\n if (confinement.strict === true) {\n const metadata = [\n ...new Set([\n ...listable,\n ...ancestors(confinement.workspaceRoot, [confinement.cwd]),\n confinement.cwd\n ])\n ].sort()\n lines.push(`(allow file-read-metadata ${metadata.map((path) => `(literal ${sbpl(path)})`).join(\" \")})`)\n } else {\n lines.push(`(allow file-read-metadata (subpath ${sbpl(confinement.workspaceRoot)}))`)\n }\n lines.push(\n `(allow file-read* ${\n [\n ...(confinement.strict === true ? [] : listable.map((path) => `(literal ${sbpl(path)})`)),\n ...readable.map((path) => `(subpath ${sbpl(path)})`)\n ].join(\" \")\n })`\n )\n if (confinement.readDenies.length > 0) {\n lines.push(`(deny file-read* ${confinement.readDenies.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n }\n const credentials = credentialPaths(hostFacts)\n if (credentials.length > 0) {\n lines.push(`(deny file-read* ${credentials.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n if (confinement.strict === true) {\n lines.push(`(deny file-write* ${credentials.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n }\n }\n if (confinement.externalReads.length > 0) {\n lines.push(`(allow file-read* ${confinement.externalReads.map((path) => `(subpath ${sbpl(path)})`).join(\" \")})`)\n }\n return lines.join(\"\")\n}\n\n/**\n * One `key=value` field of a `docker run --mount` value. Docker parses the\n * value as one CSV record, so a field containing a comma, a quote, or a line\n * break is wrapped in quotes with inner quotes doubled; any other field keeps\n * its plain spelling.\n */\nconst mountField = (key: string, value: string): string => {\n const field = `${key}=${value}`\n return /[\",\\r\\n]/.test(field) ? `\"${field.replaceAll(\"\\\"\", \"\\\"\\\"\")}\"` : field\n}\n\nconst bindMount = (path: string, readonly: boolean): string =>\n [\"type=bind\", mountField(\"src\", path), mountField(\"dst\", toPosix(path)), ...(readonly ? [\"readonly\"] : [])].join(\",\")\n\n/**\n * The `docker run` argv for a plan. Paths keep their host spelling inside the\n * container so an argv that names workspace paths works unchanged; the image\n * supplies everything outside the workspace.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const docker = (\n confinement: Plan,\n argv: ReadonlyArray<string>,\n env: Readonly<Record<string, string>>,\n hostFacts: Host = host(),\n containerName: string = `smithers-${randomUUID()}`\n): ReadonlyArray<string> => {\n if (confinement.mechanism._tag !== \"docker\") throw new Error(\"docker argv needs a docker plan\")\n validateWrites(confinement, hostFacts)\n const out: Array<string> = [\n confinement.mechanism.executable,\n \"run\",\n \"--rm\",\n \"--init\",\n \"--name\",\n containerName,\n \"--read-only\",\n \"--tmpfs\",\n \"/tmp:rw,exec\",\n \"--network\",\n confinement.network === \"open\" ? \"bridge\" : \"none\",\n \"--workdir\",\n toPosix(confinement.cwd)\n ]\n if (confinement.uid !== undefined && confinement.gid !== undefined) {\n out.push(\"--user\", `${confinement.uid}:${confinement.gid}`)\n }\n for (const real of confinement.externalReads) out.push(\"--mount\", bindMount(real, true))\n for (const read of confinement.reads) out.push(\"--mount\", bindMount(read, true))\n for (const write of confinement.writes) out.push(\"--mount\", bindMount(write, false))\n for (const closed of confinement.readOnly) {\n if (confinement.writes.some((write) => closed === write || closed.startsWith(write + NodePath.sep))) {\n out.push(\"--mount\", bindMount(closed, true))\n }\n }\n for (const [name, value] of Object.entries(env).sort(([left], [right]) => (left < right ? -1 : 1))) {\n if (name === \"PATH\" || name === \"HOME\") continue\n out.push(\"--env\", `${name}=${value}`)\n }\n out.push(\"--env\", \"HOME=/tmp/home\", confinement.mechanism.image, ...argv)\n return out\n}\n\n/**\n * Wraps a tool argv in the plan's mechanism and reports the environment the\n * wrapper needs the child to carry. Docker runs also return the unique\n * container name the caller must forcibly remove when the client scope closes.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const wrap = (\n confinement: Plan,\n argv: ReadonlyArray<string>,\n env: Readonly<Record<string, string>>,\n hostFacts: Host = host()\n): {\n readonly argv: ReadonlyArray<string>\n readonly env: Readonly<Record<string, string>>\n readonly containerName?: string\n} => {\n const extra = environment(confinement)\n switch (confinement.mechanism._tag) {\n case \"bubblewrap\":\n return { argv: bubblewrap(confinement, argv, hostFacts, env[\"PATH\"]), env: extra }\n case \"seatbelt\": {\n const profile = seatbelt(confinement, hostFacts)\n return { argv: [confinement.mechanism.executable, \"-p\", profile, ...argv], env: extra }\n }\n case \"docker\": {\n const containerName = `smithers-${randomUUID()}`\n return { argv: docker(confinement, argv, { ...env, ...extra }, hostFacts, containerName), env: {}, containerName }\n }\n }\n}\n\n/**\n * Each pattern names the path a tool complained about and whether the text\n * proves the denied operation was a write. A confined process never sees an\n * undeclared path: seatbelt answers EPERM, bubblewrap answers ENOENT because\n * the path was never mounted, and dash spells that \"Directory nonexistent\".\n */\nconst denialPatterns: ReadonlyArray<{ readonly pattern: RegExp; readonly write: boolean }> = [\n { pattern: /\\b(?:EACCES|EPERM|ENOENT)\\b[^'\"\\n]*['\"]([^'\"\\n]+)['\"]/g, write: false },\n { pattern: /\\bEROFS\\b[^'\"\\n]*['\"]([^'\"\\n]+)['\"]/g, write: true },\n {\n pattern:\n /(?:^|[\\s:])((?:\\/|\\.{0,2}\\/?)[^\\s:'\"]+): (?:Operation not permitted|Permission denied|No such file or directory|Directory nonexistent|Not a directory)/gm,\n write: false\n },\n { pattern: /(?:^|[\\s:])((?:\\/|\\.{0,2}\\/?)[^\\s:'\"]+): Read-only file system/gm, write: true },\n {\n pattern:\n /(?:Operation not permitted|Permission denied|No such file or directory|Directory nonexistent)[^\\n]*?[:\\s]['\"]?((?:\\/|[A-Za-z]:\\\\)[^'\"\\s:]+)/g,\n write: false\n },\n { pattern: /Read-only file system[^\\n]*?[:\\s]['\"]?((?:\\/|[A-Za-z]:\\\\)[^'\"\\s:]+)/g, write: true },\n {\n pattern: /cannot (?:create|write|touch|mkdir|remove) (?:directory )?['\"]?((?:\\/|[A-Za-z]:\\\\|\\.{0,2}\\/?)[^'\":\\s]+)/g,\n write: true\n },\n { pattern: /cannot (?:open|access|stat|read) ['\"]?((?:\\/|[A-Za-z]:\\\\|\\.{0,2}\\/?)[^'\":\\s]+)/g, write: false }\n]\n\n/**\n * Names, from a failed tool's own output, the paths it touched outside the\n * declared set. The sandbox denies at the kernel, so the only witness is the\n * tool's error text; this reads it back and says which side of the boundary\n * each path fell on.\n *\n * @category diagnostics\n * @since 0.1.0\n */\nexport const diagnose = (confinement: Plan, text: string): string | undefined => {\n // Path \u2192 whether the text proved the denied operation was a write.\n const named = new Map<string, boolean>()\n for (const { pattern, write } of denialPatterns) {\n for (const match of text.matchAll(pattern)) {\n const raw = match[1]!\n const absolute = NodePath.isAbsolute(raw) ? raw : NodePath.resolve(confinement.cwd, raw)\n if (insideRoot(confinement.workspaceRoot, absolute)) named.set(absolute, (named.get(absolute) ?? false) || write)\n if (named.size >= 5) break\n }\n if (named.size >= 5) break\n }\n if (named.size === 0) return undefined\n const covered = (path: string, set: ReadonlyArray<string>): boolean =>\n set.some((entry) => entry === path || path.startsWith(entry + NodePath.sep))\n const lines = [...named.entries()].sort(([left], [right]) => (left < right ? -1 : 1)).map(([path, write]) => {\n const relative = toPosix(NodePath.relative(confinement.workspaceRoot, path))\n // A path that lexically sits in the workspace can still leave it through\n // a symlink in an existing ancestor; the declaration covers the tree, not\n // wherever a link points.\n const real = realized(path)\n const escapes = !insideRoot(confinement.workspaceRoot, real)\n const readable = !escapes && (covered(path, confinement.reads) || covered(path, confinement.writes))\n const writable = !escapes && covered(path, confinement.writes) && !covered(path, confinement.readOnly)\n if (escapes) {\n return `sandbox: ${relative} resolves to ${real}, outside the declared ${write ? \"write\" : \"read\"} set`\n }\n if (write) {\n return writable\n ? `sandbox: ${relative} was denied inside the declared set`\n : `sandbox: ${relative} is outside the declared write set`\n }\n if (!readable) return `sandbox: ${relative} is outside the declared read set`\n if (!writable) return `sandbox: ${relative} is outside the declared write set`\n return `sandbox: ${relative} was denied inside the declared set`\n })\n return `${lines.join(\"\\n\")}\\nsandbox: ${confinement.mechanism._tag}, network ${confinement.network}, ` +\n `${confinement.reads.length} read path(s), ${confinement.writes.length} write path(s)`\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -33,7 +33,8 @@ export interface Handle extends ChildProcessHandle {
|
|
|
33
33
|
/**
|
|
34
34
|
* Starts a pipe-based command and contains its lifetime inside the caller's
|
|
35
35
|
* scope. The returned pid names the live supervisor; exitCode reports
|
|
36
|
-
* the actual target. The private parent connection stops the tree on host loss
|
|
36
|
+
* the actual target. The private parent connection stops the tree on host loss,
|
|
37
|
+
* and the supervisor's group guardian stops the group on supervisor loss.
|
|
37
38
|
* Transient commands have no durable ledger; durable hosts use NodeHost instead.
|
|
38
39
|
*
|
|
39
40
|
* Unconsumed output stays in bounded native buffers until it is read or the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScopedProcess.d.ts","sourceRoot":"","sources":["../../src/ScopedProcess.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,KAAK,aAAa,MAAM,sBAAsB,CAAA;AAC1D,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAA;AAKrF;;;;GAIG;AACH,MAAM,WAAW,OAAQ,SAAQ,YAAY,CAAC,WAAW;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,SAAS,CAAA;IACjD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAA;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAA;IAC9C,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvD,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC3C;AAED;;;;GAIG;AACH,MAAM,WAAW,MAAO,SAAQ,kBAAkB;IAChD,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED
|
|
1
|
+
{"version":3,"file":"ScopedProcess.d.ts","sourceRoot":"","sources":["../../src/ScopedProcess.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,KAAK,aAAa,MAAM,sBAAsB,CAAA;AAC1D,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAA;AAKrF;;;;GAIG;AACH,MAAM,WAAW,OAAQ,SAAQ,YAAY,CAAC,WAAW;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,SAAS,CAAA;IACjD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAA;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAA;IAC9C,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACvD,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC3C;AAED;;;;GAIG;AACH,MAAM,WAAW,MAAO,SAAQ,kBAAkB;IAChD,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,KAAK,GAChB,SAAS,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,EAAE,KAAK,CAAC,KAAK,CAqC9D,CAAA;AAEH;;;;GAIG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,MAAM,GAAG,IAAI,CAAA;CAC5C;AAED;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,QAAQ,kBAAkB,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAUlG,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ScopedProcess.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Scoped transient commands with the host's process-tree cleanup policy.\n * @since 1.0.0\n */\n\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport type * as PlatformError from \"effect/PlatformError\"\nimport * as Scope from \"effect/Scope\"\nimport * as ChildProcess from \"effect/unstable/process/ChildProcess\"\nimport type { ChildProcessHandle } from \"effect/unstable/process/ChildProcessSpawner\"\nimport * as PipedProcess from \"./internal/PipedProcess.ts\"\nimport { targetPidOf } from \"./internal/ProcessSupervisor.ts\"\nimport * as ProcessReaper from \"./ProcessReaper.ts\"\n\n/**\n * One command's literal argv, environment, pipes and termination policy.\n * @category models\n * @since 1.0.0\n */\nexport interface Options extends ChildProcess.KillOptions {\n readonly command: string\n readonly args?: ReadonlyArray<string> | undefined\n readonly cwd?: string | undefined\n readonly env?: Readonly<Record<string, string | undefined>> | undefined\n readonly stdin?: \"ignore\" | \"pipe\" | undefined\n readonly windowsVerbatimArguments?: boolean | undefined\n readonly windowsHide?: boolean | undefined\n}\n\n/**\n * The managed owner identity and the actual target identity are distinct.\n * @category models\n * @since 1.0.0\n */\nexport interface Handle extends ChildProcessHandle {\n /** The native target's pid, for diagnostics and service liveness. */\n readonly targetPid: number\n}\n\n/**\n * Starts a pipe-based command and contains its lifetime inside the caller's\n * scope. The returned pid names the live supervisor; exitCode reports\n * the actual target. The private parent connection stops the tree on host loss.\n * Transient commands have no durable ledger; durable hosts use NodeHost instead.\n *\n * Unconsumed output stays in bounded native buffers until it is read or the\n * scope closes. Consume stdout and stderr alongside exitCode: a command can\n * block when an unread pipe is full.\n *\n * Failed startup closes its child scope before returning an error, even when\n * the caller catches that error and retains the surrounding scope.\n * @category constructors\n * @since 1.0.0\n */\nexport const spawn = (\n options: Options\n): Effect.Effect<Handle, PlatformError.PlatformError, Scope.Scope> =>\n Effect.uninterruptibleMask((restore) =>\n Effect.gen(function*() {\n const scope = yield* Scope.fork(yield* Scope.Scope)\n const command = ChildProcess.make(options.command, [...options.args ?? []], {\n cwd: options.cwd,\n env: options.env,\n extendEnv: false,\n stdin: options.stdin ?? \"ignore\",\n stdout: \"pipe\",\n stderr: \"pipe\",\n detached: process.platform !== \"win32\",\n killSignal: options.killSignal ?? \"SIGTERM\",\n forceKillAfter: options.forceKillAfter ?? 2000,\n windowsHide: options.windowsHide ?? true,\n // Preserve the target's native option through the private protocol.\n ...{ windowsVerbatimArguments: options.windowsVerbatimArguments }\n })\n return yield* restore(\n Effect.gen(function*() {\n const prepared = yield* ProcessReaper.processLifecycle(\n command,\n (command) => PipedProcess.spawn(command, false, true)\n )\n yield* prepared.activate\n const targetPid = targetPidOf(prepared.handle)\n if (targetPid === undefined) {\n return yield* Effect.fail(\n PipedProcess.failure(\"spawn\", new Error(\"The supervisor did not identify its target\"))\n )\n }\n return Object.defineProperty(prepared.handle, \"targetPid\", { value: targetPid, enumerable: true }) as Handle\n }).pipe(Scope.provide(scope))\n ).pipe(\n Effect.onError((cause) => Scope.close(scope, Exit.failCause(cause)))\n )\n })\n )\n\n/**\n * The native command's result, preserving a signal instead of inventing a code.\n * @category models\n * @since 1.0.0\n */\nexport interface Status {\n readonly code: number | null\n readonly signal: ChildProcess.Signal | null\n}\n\n/**\n * Reads the target's result. Failures without an actual target signal remain\n * platform failures, including loss of the supervisor before a result arrives.\n * @category execution\n * @since 1.0.0\n */\nexport const status = (handle: ChildProcessHandle): Effect.Effect<Status, PlatformError.PlatformError> =>\n handle.exitCode.pipe(\n Effect.map((code): Status => ({ code, signal: null })),\n Effect.catch((error) => {\n const cause = \"cause\" in error.reason ? error.reason.cause : undefined\n const signal = typeof cause === \"object\" && cause !== null && \"signal\" in cause ? cause.signal : undefined\n return typeof signal === \"string\" && /^SIG[A-Z0-9]+$/.test(signal)\n ? Effect.succeed({ code: null, signal: signal as ChildProcess.Signal })\n : Effect.fail(error)\n })\n )\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,aAAwB;AACxB,WAAsB;AAEtB,YAAuB;AACvB,mBAA8B;AAE9B,mBAA8B;AAC9B,+BAA4B;AAC5B,oBAA+B;
|
|
4
|
+
"sourcesContent": ["/**\n * Scoped transient commands with the host's process-tree cleanup policy.\n * @since 1.0.0\n */\n\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport type * as PlatformError from \"effect/PlatformError\"\nimport * as Scope from \"effect/Scope\"\nimport * as ChildProcess from \"effect/unstable/process/ChildProcess\"\nimport type { ChildProcessHandle } from \"effect/unstable/process/ChildProcessSpawner\"\nimport * as PipedProcess from \"./internal/PipedProcess.ts\"\nimport { targetPidOf } from \"./internal/ProcessSupervisor.ts\"\nimport * as ProcessReaper from \"./ProcessReaper.ts\"\n\n/**\n * One command's literal argv, environment, pipes and termination policy.\n * @category models\n * @since 1.0.0\n */\nexport interface Options extends ChildProcess.KillOptions {\n readonly command: string\n readonly args?: ReadonlyArray<string> | undefined\n readonly cwd?: string | undefined\n readonly env?: Readonly<Record<string, string | undefined>> | undefined\n readonly stdin?: \"ignore\" | \"pipe\" | undefined\n readonly windowsVerbatimArguments?: boolean | undefined\n readonly windowsHide?: boolean | undefined\n}\n\n/**\n * The managed owner identity and the actual target identity are distinct.\n * @category models\n * @since 1.0.0\n */\nexport interface Handle extends ChildProcessHandle {\n /** The native target's pid, for diagnostics and service liveness. */\n readonly targetPid: number\n}\n\n/**\n * Starts a pipe-based command and contains its lifetime inside the caller's\n * scope. The returned pid names the live supervisor; exitCode reports\n * the actual target. The private parent connection stops the tree on host loss,\n * and the supervisor's group guardian stops the group on supervisor loss.\n * Transient commands have no durable ledger; durable hosts use NodeHost instead.\n *\n * Unconsumed output stays in bounded native buffers until it is read or the\n * scope closes. Consume stdout and stderr alongside exitCode: a command can\n * block when an unread pipe is full.\n *\n * Failed startup closes its child scope before returning an error, even when\n * the caller catches that error and retains the surrounding scope.\n * @category constructors\n * @since 1.0.0\n */\nexport const spawn = (\n options: Options\n): Effect.Effect<Handle, PlatformError.PlatformError, Scope.Scope> =>\n Effect.uninterruptibleMask((restore) =>\n Effect.gen(function*() {\n const scope = yield* Scope.fork(yield* Scope.Scope)\n const command = ChildProcess.make(options.command, [...options.args ?? []], {\n cwd: options.cwd,\n env: options.env,\n extendEnv: false,\n stdin: options.stdin ?? \"ignore\",\n stdout: \"pipe\",\n stderr: \"pipe\",\n detached: process.platform !== \"win32\",\n killSignal: options.killSignal ?? \"SIGTERM\",\n forceKillAfter: options.forceKillAfter ?? 2000,\n windowsHide: options.windowsHide ?? true,\n // Preserve the target's native option through the private protocol.\n ...{ windowsVerbatimArguments: options.windowsVerbatimArguments }\n })\n return yield* restore(\n Effect.gen(function*() {\n const prepared = yield* ProcessReaper.processLifecycle(\n command,\n (command) => PipedProcess.spawn(command, false, true)\n )\n yield* prepared.activate\n const targetPid = targetPidOf(prepared.handle)\n if (targetPid === undefined) {\n return yield* Effect.fail(\n PipedProcess.failure(\"spawn\", new Error(\"The supervisor did not identify its target\"))\n )\n }\n return Object.defineProperty(prepared.handle, \"targetPid\", { value: targetPid, enumerable: true }) as Handle\n }).pipe(Scope.provide(scope))\n ).pipe(\n Effect.onError((cause) => Scope.close(scope, Exit.failCause(cause)))\n )\n })\n )\n\n/**\n * The native command's result, preserving a signal instead of inventing a code.\n * @category models\n * @since 1.0.0\n */\nexport interface Status {\n readonly code: number | null\n readonly signal: ChildProcess.Signal | null\n}\n\n/**\n * Reads the target's result. Failures without an actual target signal remain\n * platform failures, including loss of the supervisor before a result arrives.\n * @category execution\n * @since 1.0.0\n */\nexport const status = (handle: ChildProcessHandle): Effect.Effect<Status, PlatformError.PlatformError> =>\n handle.exitCode.pipe(\n Effect.map((code): Status => ({ code, signal: null })),\n Effect.catch((error) => {\n const cause = \"cause\" in error.reason ? error.reason.cause : undefined\n const signal = typeof cause === \"object\" && cause !== null && \"signal\" in cause ? cause.signal : undefined\n return typeof signal === \"string\" && /^SIG[A-Z0-9]+$/.test(signal)\n ? Effect.succeed({ code: null, signal: signal as ChildProcess.Signal })\n : Effect.fail(error)\n })\n )\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,aAAwB;AACxB,WAAsB;AAEtB,YAAuB;AACvB,mBAA8B;AAE9B,mBAA8B;AAC9B,+BAA4B;AAC5B,oBAA+B;AA2CxB,MAAM,QAAQ,CACnB,YAEA,OAAO;AAAA,EAAoB,CAAC,YAC1B,OAAO,IAAI,aAAY;AACrB,UAAM,QAAQ,OAAO,MAAM,KAAK,OAAO,MAAM,KAAK;AAClD,UAAM,UAAU,aAAa,KAAK,QAAQ,SAAS,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC,GAAG;AAAA,MAC1E,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,WAAW;AAAA,MACX,OAAO,QAAQ,SAAS;AAAA,MACxB,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU,QAAQ,aAAa;AAAA,MAC/B,YAAY,QAAQ,cAAc;AAAA,MAClC,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,aAAa,QAAQ,eAAe;AAAA;AAAA,MAEpC,GAAG,EAAE,0BAA0B,QAAQ,yBAAyB;AAAA,IAClE,CAAC;AACD,WAAO,OAAO;AAAA,MACZ,OAAO,IAAI,aAAY;AACrB,cAAM,WAAW,OAAO,cAAc;AAAA,UACpC;AAAA,UACA,CAACA,aAAY,aAAa,MAAMA,UAAS,OAAO,IAAI;AAAA,QACtD;AACA,eAAO,SAAS;AAChB,cAAM,gBAAY,sCAAY,SAAS,MAAM;AAC7C,YAAI,cAAc,QAAW;AAC3B,iBAAO,OAAO,OAAO;AAAA,YACnB,aAAa,QAAQ,SAAS,IAAI,MAAM,4CAA4C,CAAC;AAAA,UACvF;AAAA,QACF;AACA,eAAO,OAAO,eAAe,SAAS,QAAQ,aAAa,EAAE,OAAO,WAAW,YAAY,KAAK,CAAC;AAAA,MACnG,CAAC,EAAE,KAAK,MAAM,QAAQ,KAAK,CAAC;AAAA,IAC9B,EAAE;AAAA,MACA,OAAO,QAAQ,CAAC,UAAU,MAAM,MAAM,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC;AAAA,IACrE;AAAA,EACF,CAAC;AACH;AAkBK,MAAM,SAAS,CAAC,WACrB,OAAO,SAAS;AAAA,EACd,OAAO,IAAI,CAAC,UAAkB,EAAE,MAAM,QAAQ,KAAK,EAAE;AAAA,EACrD,OAAO,MAAM,CAAC,UAAU;AACtB,UAAM,QAAQ,WAAW,MAAM,SAAS,MAAM,OAAO,QAAQ;AAC7D,UAAM,SAAS,OAAO,UAAU,YAAY,UAAU,QAAQ,YAAY,QAAQ,MAAM,SAAS;AACjG,WAAO,OAAO,WAAW,YAAY,iBAAiB,KAAK,MAAM,IAC7D,OAAO,QAAQ,EAAE,MAAM,MAAM,OAAsC,CAAC,IACpE,OAAO,KAAK,KAAK;AAAA,EACvB,CAAC;AACH;",
|
|
6
6
|
"names": ["command"]
|
|
7
7
|
}
|
|
@@ -102,6 +102,8 @@ export declare class Control {
|
|
|
102
102
|
targetDone: boolean;
|
|
103
103
|
ownerCreation: unknown;
|
|
104
104
|
targetPid: number | undefined;
|
|
105
|
+
/** The owner's group guardian: a member that is neither target nor owner. */
|
|
106
|
+
guardianPid: number | undefined;
|
|
105
107
|
ownerDone: boolean;
|
|
106
108
|
spawnFailed: boolean;
|
|
107
109
|
cleanupFailed: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProcessSupervisor.d.ts","sourceRoot":"","sources":["../../../src/internal/ProcessSupervisor.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ProcessSupervisor.d.ts","sourceRoot":"","sources":["../../../src/internal/ProcessSupervisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -168,6 +168,8 @@ class Control {
|
|
|
168
168
|
targetDone = false;
|
|
169
169
|
ownerCreation;
|
|
170
170
|
targetPid;
|
|
171
|
+
/** The owner's group guardian: a member that is neither target nor owner. */
|
|
172
|
+
guardianPid;
|
|
171
173
|
ownerDone = false;
|
|
172
174
|
spawnFailed = false;
|
|
173
175
|
cleanupFailed = false;
|
|
@@ -353,11 +355,12 @@ class Control {
|
|
|
353
355
|
this.ready.resolve(Number(message.pid));
|
|
354
356
|
return;
|
|
355
357
|
case "spawned":
|
|
356
|
-
if (!this.activationSent || this.receivedStarted || !Number.isSafeInteger(message.pid) || Number(message.pid) <= 1) {
|
|
358
|
+
if (!this.activationSent || this.receivedStarted || !Number.isSafeInteger(message.pid) || Number(message.pid) <= 1 || message.guardian !== void 0 && (!Number.isSafeInteger(message.guardian) || Number(message.guardian) <= 1)) {
|
|
357
359
|
throw (0, import_ProcessFault.processFault)("status_invalid", "Invalid target startup");
|
|
358
360
|
}
|
|
359
361
|
this.receivedStarted = true;
|
|
360
362
|
this.targetPid = Number(message.pid);
|
|
363
|
+
if (message.guardian !== void 0) this.guardianPid = Number(message.guardian);
|
|
361
364
|
this.started.resolve();
|
|
362
365
|
return;
|
|
363
366
|
case "spawn_error":
|
|
@@ -458,7 +461,7 @@ const prepare = (system, policy, transport = "native") => (command, spawn) => Ef
|
|
|
458
461
|
if (!grouped) return control.targetDone;
|
|
459
462
|
const observed = snapshot();
|
|
460
463
|
if (observed === void 0 || observed.ownGroup === raw.pid) return false;
|
|
461
|
-
const running = observed.members.filter((member) => !member.zombie);
|
|
464
|
+
const running = observed.members.filter((member) => !member.zombie && member.pid !== control.guardianPid);
|
|
462
465
|
return running.length === 1 && running[0].pid === raw.pid;
|
|
463
466
|
};
|
|
464
467
|
control.onTargetExit = () => {
|