@warlock.js/process 0.0.0-stage → 5.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +59 -2
- package/cjs/index.cjs +1023 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/guardian.mjs +134 -0
- package/esm/guardian.mjs.map +1 -0
- package/esm/identity.d.mts +7 -0
- package/esm/identity.d.mts.map +1 -0
- package/esm/identity.mjs +86 -0
- package/esm/identity.mjs.map +1 -0
- package/esm/index.d.mts +5 -0
- package/esm/index.mjs +5 -0
- package/esm/platform.d.mts +7 -0
- package/esm/platform.d.mts.map +1 -0
- package/esm/platform.mjs +156 -0
- package/esm/platform.mjs.map +1 -0
- package/esm/registry.mjs +118 -0
- package/esm/registry.mjs.map +1 -0
- package/esm/resources.mjs +85 -0
- package/esm/resources.mjs.map +1 -0
- package/esm/supervisor.d.mts +5 -0
- package/esm/supervisor.d.mts.map +1 -0
- package/esm/supervisor.mjs +443 -0
- package/esm/supervisor.mjs.map +1 -0
- package/esm/types.d.mts +84 -0
- package/esm/types.d.mts.map +1 -0
- package/llms-full.txt +157 -0
- package/llms.txt +9 -0
- package/package.json +49 -4
- package/skills/supervise-processes/SKILL.md +147 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["path","readFile","mkdir","writeFile","rmdir","randomBytes","createHmac","connect","promisify","execFile","readFile","mkdir","path","readFile","randomBytes","rename","rm","writeFile","path","delay","PassThrough","randomBytes","Writable","spawnChild"],"sources":["../../../../../../../packages/process/src/resources.ts","../../../../../../../packages/process/src/guardian.ts","../../../../../../../packages/process/src/platform.ts","../../../../../../../packages/process/src/identity.ts","../../../../../../../packages/process/src/registry.ts","../../../../../../../packages/process/src/supervisor.ts"],"sourcesContent":["import { mkdir, readFile, rmdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { ProcessLimits } from \"./types\";\n\nfunction mountedCgroup(membership: string): string | undefined {\n const mount = \"/sys/fs/cgroup\";\n const directory = path.resolve(mount, `.${membership}`);\n // A cgroup namespace can report ancestors using /../ components. Never\n // normalize those into writes outside the standard cgroup mount.\n return directory === mount || directory.startsWith(`${mount}/`) ? directory : undefined;\n}\n\nexport function validateLimits(limits: ProcessLimits, platform: string = process.platform): void {\n for (const [key, value] of Object.entries(limits)) {\n if (\n ![\"timeoutMs\", \"memoryBytes\", \"cpuSeconds\", \"virtualMemoryBytes\"].includes(key) ||\n !Number.isSafeInteger(value) ||\n value <= 0\n )\n throw new Error(`Invalid process limit: ${key}`);\n }\n if (platform !== \"linux\" && limits.virtualMemoryBytes !== undefined)\n throw new Error(\"virtualMemoryBytes is supported only on Linux\");\n if (platform === \"win32\" && limits.cpuSeconds !== undefined)\n throw new Error(\"cpuSeconds is supported only on POSIX platforms\");\n}\n\n/**\n * POSIX shell applies inherited limits. Linux stops here; macOS execs a gated guardian.\n * Flags/units: Apple's bash-3.2/builtins/ulimit.def and installed Bash manual.\n * https://github.com/apple-oss-distributions/bash/blob/main/bash-3.2/builtins/ulimit.def\n */\nexport function launcher(limits: ProcessLimits, platform: string = process.platform): string {\n const commands = [\"set -e\"];\n if (limits.cpuSeconds) commands.push(`ulimit -t ${limits.cpuSeconds}`);\n if (limits.virtualMemoryBytes)\n commands.push(`ulimit -v ${Math.ceil(limits.virtualMemoryBytes / 1024)}`);\n // Linux ignores RLIMIT_RSS; polling remains the fallback resident-memory cap.\n // Darwin's RLIMIT_DATA restricts heap growth, not total resident memory.\n if (limits.memoryBytes)\n commands.push(\n `ulimit -${platform === \"darwin\" ? \"d\" : \"m\"} ${Math.ceil(limits.memoryBytes / 1024)}`,\n );\n if (platform === \"linux\") commands.push('kill -STOP \"$$\"');\n commands.push('exec \"$@\"');\n return commands.join(\"\\n\");\n}\n\n/** https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html */\nexport async function attachCgroup(\n pid: number,\n token: string,\n limits: ProcessLimits,\n root?: string,\n): Promise<string | undefined> {\n if (process.platform !== \"linux\") return undefined;\n let directory: string | undefined;\n try {\n const own = (await readFile(\"/proc/self/cgroup\", \"utf8\"))\n .split(\"\\n\")\n .find((line) => line.startsWith(\"0::\"))\n ?.slice(3);\n if (!root && own === undefined) return undefined;\n const parent = root ?? mountedCgroup(own ?? \"/\");\n if (!parent) return undefined;\n // Do not enable controllers or move the app: delegation belongs to the service manager.\n await readFile(path.join(parent, \"cgroup.controllers\"), \"utf8\");\n directory = path.join(parent, `warlock-${token}`);\n await mkdir(directory);\n if (limits.memoryBytes) {\n await writeFile(path.join(directory, \"memory.max\"), String(limits.memoryBytes));\n await writeFile(path.join(directory, \"memory.oom.group\"), \"1\");\n }\n await writeFile(path.join(directory, \"cgroup.procs\"), String(pid));\n return directory;\n } catch {\n if (directory) await rmdir(directory).catch(() => undefined);\n return undefined;\n }\n}\n\nexport async function cgroupMemory(directory: string): Promise<{ peak: number; oom: boolean }> {\n const [peak, events] = await Promise.all([\n readFile(path.join(directory, \"memory.peak\"), \"utf8\").catch(() => \"0\"),\n readFile(path.join(directory, \"memory.events\"), \"utf8\").catch(() => \"\"),\n ]);\n return { peak: Number(peak) || 0, oom: Number(/^oom_kill (\\d+)/m.exec(events)?.[1] ?? 0) > 0 };\n}\n\n/** Only reattach the token-specific cgroup confirmed by the kernel membership file. */\nexport async function recoveredCgroup(pid: number, token: string): Promise<string | undefined> {\n if (process.platform !== \"linux\") return undefined;\n let source: string;\n try {\n source = await readFile(`/proc/${pid}/cgroup`, \"utf8\");\n } catch (error) {\n if ([\"ENOENT\", \"ESRCH\"].includes(String((error as NodeJS.ErrnoException).code)))\n return undefined;\n throw error;\n }\n const membership = source\n .split(\"\\n\")\n .find((line) => line.startsWith(\"0::\"))\n ?.slice(3);\n if (!membership || path.basename(membership) !== `warlock-${token}`) return undefined;\n return mountedCgroup(membership);\n}\n\nexport async function cleanCgroup(directory: string): Promise<void> {\n // This unique cgroup was created for this token, or recovered after identity proof.\n // cgroup.kill also covers descendants that created their own POSIX session.\n await writeFile(path.join(directory, \"cgroup.kill\"), \"1\").catch(() => undefined);\n await rmdir(directory).catch(() => undefined);\n}\n","import { createHmac, randomBytes } from \"node:crypto\";\nimport { connect } from \"node:net\";\nimport type { Command, OwnershipProof } from \"./platform\";\nimport { launcher } from \"./resources\";\nimport type { ProcessLimits } from \"./types\";\n\nexport function guardianAddress(platform: string, pid: number, uid: number): string {\n return platform === \"win32\"\n ? `\\\\\\\\.\\\\pipe\\\\warlock-process-${pid}`\n : `/tmp/warlock-process-${uid}-${pid}.sock`;\n}\n\n/** A live guardian reads its own environment, never argv, to prove ownership.\n * Node IPC is native-free: https://nodejs.org/api/net.html#ipc-support\n * The stable root PID stays alive for the workload and survives supervisor restarts.\n */\nexport const guardianScript = String.raw`\nconst { createServer } = require(\"node:net\");\nconst { createHmac } = require(\"node:crypto\");\nconst { spawn } = require(\"node:child_process\");\nconst { chmodSync, unlinkSync } = require(\"node:fs\");\nconst token = process.env.WARLOCK_PROCESS_TOKEN;\nconst address = process.platform === \"win32\"\n ? \"\\\\\\\\.\\\\pipe\\\\warlock-process-\" + process.pid\n : \"/tmp/warlock-process-\" + process.getuid() + \"-\" + process.pid + \".sock\";\nlet gated = true;\nconst finish = (code) => { server.close(); process.exit(code); };\nconst start = () => {\n if (!gated) return;\n gated = false;\n const [command, ...args] = JSON.parse(process.env.WARLOCK_PROCESS_LAUNCH);\n const env = { ...process.env };\n for (const key of Object.keys(env)) {\n const name = process.platform === \"win32\" ? key.toUpperCase() : key;\n if (name === \"WARLOCK_PROCESS_TOKEN\" || name === \"WARLOCK_PROCESS_LAUNCH\") delete env[key];\n }\n const child = spawn(command, args, { env, stdio: \"inherit\", windowsHide: true });\n child.on(\"error\", (error) => { console.error(error.message); finish(127); });\n child.on(\"exit\", (code, signal) => {\n if (signal && process.platform !== \"win32\") process.kill(process.pid, signal);\n else finish(code ?? 1);\n });\n};\nconst server = createServer((socket) => {\n socket.setTimeout(2000, () => socket.destroy());\n socket.on(\"error\", () => {});\n let input = \"\";\n socket.on(\"data\", (chunk) => {\n input += chunk;\n if (input.length > 1024) { socket.destroy(); return; }\n if (!input.endsWith(\"\\n\")) return;\n try {\n const request = JSON.parse(input);\n if (!/^[a-f0-9]{64}$/.test(request.nonce)) { socket.destroy(); return; }\n const sign = (value) => createHmac(\"sha256\", token).update(value).digest(\"hex\");\n if (request.resume === sign(request.nonce + \":resume\")) start();\n socket.end(JSON.stringify({ pid: process.pid, digest: sign(request.nonce + \":\" + process.pid + \":\" + gated), gated }) + \"\\n\");\n } catch { socket.destroy(); }\n });\n});\nserver.on(\"error\", (error) => { console.error(error.message); process.exit(126); });\nif (process.platform !== \"win32\") {\n try { unlinkSync(address); } catch (error) { if (error.code !== \"ENOENT\") throw error; }\n process.on(\"exit\", () => { try { unlinkSync(address); } catch {} });\n}\nserver.listen(address, () => { if (process.platform !== \"win32\") chmodSync(address, 0o600); });\n`;\n\nexport async function guardianProof(\n pid: number,\n resume?: string,\n expectedToken: string | undefined = resume,\n): Promise<OwnershipProof | undefined> {\n if (!expectedToken) return undefined;\n const token = expectedToken;\n const nonce = randomBytes(32).toString(\"hex\");\n const sign = (value: string) => createHmac(\"sha256\", token).update(value).digest(\"hex\");\n const address = guardianAddress(process.platform, pid, process.getuid?.() ?? 0);\n return new Promise((resolve, reject) => {\n const socket = connect(address);\n let data = \"\";\n const fail = (error: Error) => {\n socket.destroy();\n reject(error);\n };\n socket.setTimeout(2000, () => fail(new Error(\"Process ownership observation timed out\")));\n socket.on(\"connect\", () =>\n socket.write(\n JSON.stringify({ nonce, resume: resume ? sign(nonce + \":resume\") : undefined }) + \"\\n\",\n ),\n );\n socket.on(\"data\", (chunk: Buffer) => {\n data += chunk.toString();\n if (data.length > 1024) fail(new Error(\"Invalid process ownership response\"));\n });\n socket.on(\"error\", (error: NodeJS.ErrnoException) => {\n if ([\"ENOENT\", \"ECONNREFUSED\"].includes(error.code ?? \"\")) resolve(undefined);\n else reject(error);\n });\n socket.on(\"end\", () => {\n try {\n resolve(parseGuardianProof(data, pid, nonce, token));\n } catch (error) {\n reject(error);\n }\n });\n });\n}\n\n/** Malformed observation is an error; an authenticated identity mismatch is unowned. */\nexport function parseGuardianProof(\n source: string,\n pid: number,\n nonce: string,\n token: string,\n): OwnershipProof | undefined {\n const proof: unknown = JSON.parse(source);\n if (\n !proof ||\n typeof proof !== \"object\" ||\n !(\"pid\" in proof) ||\n typeof proof.pid !== \"number\" ||\n !(\"gated\" in proof) ||\n typeof proof.gated !== \"boolean\" ||\n !(\"digest\" in proof) ||\n typeof proof.digest !== \"string\"\n )\n throw new Error(\"Invalid process ownership response\");\n const expected = createHmac(\"sha256\", token)\n .update(nonce + \":\" + pid + \":\" + proof.gated)\n .digest(\"hex\");\n if (proof.pid !== pid || proof.digest !== expected) return undefined;\n return { pid, token, gated: proof.gated };\n}\n\n/** Pure launch construction, including case-insensitive Windows environment keys. */\nexport function launchCommand(\n platform: string,\n node: string,\n command: string,\n args: string[],\n limits: ProcessLimits,\n environment: NodeJS.ProcessEnv,\n token: string,\n): Omit<Command, \"env\"> & { env: NodeJS.ProcessEnv } {\n const portable = platform !== \"linux\";\n const env: NodeJS.ProcessEnv = Object.fromEntries(\n Object.entries(environment).filter(\n ([key]) =>\n ![\"WARLOCK_PROCESS_TOKEN\", \"WARLOCK_PROCESS_LAUNCH\"].includes(\n platform === \"win32\" ? key.toUpperCase() : key,\n ),\n ),\n );\n env.WARLOCK_PROCESS_TOKEN = token;\n if (portable) env.WARLOCK_PROCESS_LAUNCH = JSON.stringify([command, ...args]);\n const executable = portable ? node : command;\n const arguments_ = portable ? [\"-e\", guardianScript] : args;\n return {\n file: platform === \"win32\" ? executable : \"/bin/sh\",\n args:\n platform === \"win32\"\n ? arguments_\n : [\"-c\", launcher(limits, platform), \"warlock-process\", executable, ...arguments_],\n env,\n };\n}\n","import type { Identity } from \"./identity\";\n\nexport type PortablePlatform = \"darwin\" | \"win32\";\nexport type Command = { file: string; args: string[]; env?: Record<string, string> };\nexport type Execute = (command: Command) => Promise<string>;\nexport type OwnershipProof = { pid: number; token: string; gated: boolean };\nexport type ReadProof = (pid: number) => Promise<OwnershipProof | undefined>;\ntype Row = {\n pid: number;\n parent: number;\n start: string;\n rss: number;\n group: number;\n state: string;\n};\n\nexport class UnsupportedProcessPlatformError extends Error {\n constructor(public readonly platform: string) {\n super(`Process supervision is unsupported on ${platform}`);\n this.name = \"UnsupportedProcessPlatformError\";\n }\n}\nexport function supported(platform: string): void {\n if (![\"linux\", \"darwin\", \"win32\"].includes(platform))\n throw new UnsupportedProcessPlatformError(platform);\n}\nfunction validPid(pid: number): void {\n if (!Number.isSafeInteger(pid) || pid <= 1) throw new Error(\"Invalid process PID\");\n}\n\n/** Apple ps(1): lstart uses strftime %c. LC_ALL=C gives %a %b %e %H:%M:%S %Y.\n * https://github.com/apple-oss-distributions/adv_cmds/blob/main/ps/ps.1\n * https://github.com/apple-oss-distributions/adv_cmds/blob/main/ps/print.c (lstarted)\n * https://github.com/apple-oss-distributions/Libc/blob/main/stdtime/FreeBSD/timelocal.c (_C_time_locale)\n */\nexport function parseDarwinStart(source: string): string {\n const match =\n /^(Sun|Mon|Tue|Wed|Thu|Fri|Sat) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s+(\\d{1,2}) (\\d{2}):(\\d{2}):(\\d{2}) (\\d{4})$/.exec(\n source.trim(),\n );\n if (!match) throw new Error(\"Invalid macOS process start time\");\n const [, weekday, month, day, hour, minute, second, year] = match;\n const monthIndex = \"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\"\n .split(\" \")\n .indexOf(month ?? \"\");\n const date = new Date(\n Date.UTC(Number(year), monthIndex, Number(day), Number(hour), Number(minute), Number(second)),\n );\n if (\n date.getUTCFullYear() !== Number(year) ||\n date.getUTCMonth() !== monthIndex ||\n date.getUTCDate() !== Number(day) ||\n date.getUTCHours() !== Number(hour) ||\n date.getUTCMinutes() !== Number(minute) ||\n date.getUTCSeconds() !== Number(second) ||\n \"Sun Mon Tue Wed Thu Fri Sat\".split(\" \")[date.getUTCDay()] !== weekday\n )\n throw new Error(\"Invalid macOS process start time\");\n return String(date.getTime() / 1000);\n}\nexport function darwinCommand(pid: number, tree = false): Command {\n validPid(pid);\n return {\n file: \"/bin/ps\",\n args: [...(tree ? [\"-ax\"] : [\"-p\", String(pid)]), \"-o\", \"pid=,ppid=,pgid=,state=,rss=,lstart=\"],\n env: { LC_ALL: \"C\", TZ: \"UTC\" },\n };\n}\nexport function parseDarwinRows(source: string): Row[] {\n return source.trim()\n ? source\n .trim()\n .split(\"\\n\")\n .map((line) => {\n const match = /^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\S+)\\s+(\\d+)\\s+(.+?)\\s*$/.exec(line);\n if (!match) throw new Error(\"Invalid macOS process observation\");\n return {\n pid: Number(match[1]),\n parent: Number(match[2]),\n group: Number(match[3]),\n state: match[4] ?? \"\",\n rss: Number(match[5]) * 1024,\n start: parseDarwinStart(match[6] ?? \"\"),\n };\n })\n : [];\n}\n\n/** CIM CreationDate is a DateTime; serialize UTC ticks as text, never a locale date\n * or JSON number (100ns ticks exceed Number's precision). WorkingSetSize is bytes.\n * https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process\n */\nexport function windowsCommand(pid: number, tree = false): Command {\n validPid(pid);\n const query = `Get-CimInstance Win32_Process${tree ? \"\" : ` -Filter 'ProcessId=${pid}'`}`;\n const script = [\n \"$ErrorActionPreference = 'Stop'\",\n `$rows = @(${query} | ForEach-Object {`,\n `if ([int]$_.ProcessId -eq ${pid} -and $null -eq $_.CreationDate) { throw 'Cannot read process creation time' }`,\n \"if ($null -ne $_.CreationDate) {\",\n \"[pscustomobject]@{ pid = [int]$_.ProcessId; parent = [int]$_.ParentProcessId; start = $_.CreationDate.ToUniversalTime().Ticks.ToString([Globalization.CultureInfo]::InvariantCulture); rss = [double]$_.WorkingSetSize }\",\n \"}\",\n \"})\",\n \"ConvertTo-Json -InputObject $rows -Compress\",\n ].join(\"\\n\");\n return {\n file: \"powershell.exe\",\n args: [\"-NoLogo\", \"-NoProfile\", \"-NonInteractive\", \"-Command\", script],\n };\n}\nexport function parseWindowsRows(source: string): Row[] {\n const value: unknown = JSON.parse(source.replace(/^\\uFEFF/, \"\"));\n if (!Array.isArray(value)) throw new Error(\"Invalid Windows process observation\");\n return value.map((row: unknown) => {\n if (\n !row ||\n typeof row !== \"object\" ||\n !(\"pid\" in row) ||\n typeof row.pid !== \"number\" ||\n !Number.isSafeInteger(row.pid) ||\n row.pid < 0 ||\n !(\"parent\" in row) ||\n typeof row.parent !== \"number\" ||\n !Number.isSafeInteger(row.parent) ||\n row.parent < 0 ||\n !(\"start\" in row) ||\n typeof row.start !== \"string\" ||\n !/^\\d{18}$/.test(row.start) ||\n !(\"rss\" in row) ||\n typeof row.rss !== \"number\" ||\n !Number.isFinite(row.rss) ||\n row.rss < 0\n )\n throw new Error(\"Invalid Windows process observation\");\n return {\n pid: row.pid,\n parent: row.parent,\n start: row.start,\n rss: row.rss,\n group: row.pid,\n state: \"R\",\n };\n });\n}\nexport function taskkillCommand(pid: number): Command {\n validPid(pid);\n // Never accept an image name, filter, wildcard or shell interpolation.\n return { file: \"taskkill.exe\", args: [\"/PID\", String(pid), \"/T\", \"/F\"] };\n}\nexport function treeMemory(rows: Row[], pid: number): number {\n const root = rows.find((row) => row.pid === pid);\n if (!root) return 0;\n const included = new Map<number, Row>([[pid, root]]);\n let changed = true;\n while (changed) {\n changed = false;\n for (const row of rows) {\n const parent = included.get(row.parent);\n // Reject a reused parent PID whose creation time is later than the child.\n if (!included.has(row.pid) && parent && BigInt(row.start) >= BigInt(parent.start)) {\n included.set(row.pid, row);\n changed = true;\n }\n }\n }\n return [...included.values()].reduce((total, row) => total + row.rss, 0);\n}\n\n/** All OS effects are injected; foreign-platform specs never execute real commands. */\nexport async function portableIdentity(\n platform: PortablePlatform,\n pid: number,\n token: boolean,\n execute: Execute,\n readProof: ReadProof,\n): Promise<Identity | undefined> {\n validPid(pid);\n const command = platform === \"darwin\" ? darwinCommand : windowsCommand;\n const parse = platform === \"darwin\" ? parseDarwinRows : parseWindowsRows;\n const rows = parse(await execute(command(pid, token)));\n const before = rows.find((row) => row.pid === pid);\n if (!before || /^[ZX]/.test(before.state)) return undefined;\n if (!token) return before;\n const proof = await readProof(pid);\n const after = parse(await execute(command(pid))).find((row) => row.pid === pid);\n if (\n !after ||\n after.start !== before.start ||\n after.group !== before.group ||\n /^[ZX]/.test(after.state)\n )\n return undefined;\n return {\n start: after.start,\n group: after.group,\n state: proof?.pid === pid && proof.gated ? \"T\" : after.state,\n rss: treeMemory(rows, pid),\n token: proof?.pid === pid ? proof.token : undefined,\n };\n}\n","import { execFile } from \"node:child_process\";\nimport { readFile } from \"node:fs/promises\";\nimport { promisify } from \"node:util\";\n\nimport { guardianProof } from \"./guardian\";\nimport { portableIdentity, type Command } from \"./platform\";\n\nconst execute = promisify(execFile);\nexport async function executeCommand(command: Command): Promise<string> {\n const { stdout } = await execute(command.file, command.args, {\n env: { ...process.env, ...command.env },\n timeout: 5000,\n maxBuffer: 4 * 1024 * 1024,\n windowsHide: true,\n });\n return stdout;\n}\nexport const TOKEN_ENV = \"WARLOCK_PROCESS_TOKEN\";\nexport type Identity = { start: string; group: number; state: string; rss: number; token?: string };\n\nfunction statFields(stat: string): string[] {\n return stat\n .slice(stat.lastIndexOf(\")\") + 2)\n .trim()\n .split(/\\s+/);\n}\n\n/**\n * A task that is exiting has already released its memory map (stat field 23,\n * vsize, reads 0) before it turns into a zombie. Linux then reports its /proc\n * files as root-owned, so it is never a live child to verify or signal.\n */\nfunction exiting(fields: string[]): boolean {\n return [\"Z\", \"X\"].includes(fields[0] ?? \"\") || fields[20] === \"0\";\n}\n\n/** /proc fields: https://man7.org/linux/man-pages/man5/proc_pid_stat.5.html */\nexport async function identity(\n pid: number,\n token = false,\n expectedToken?: string,\n): Promise<Identity | undefined> {\n if (!Number.isSafeInteger(pid) || pid <= 1) return undefined;\n try {\n if (process.platform === \"linux\") {\n const stat = await readFile(`/proc/${pid}/stat`, \"utf8\");\n const fields = statFields(stat);\n const state = fields[0];\n const start = fields[19];\n if (!state || !start || exiting(fields)) return undefined;\n const boot = (await readFile(\"/proc/sys/kernel/random/boot_id\", \"utf8\")).trim();\n const status = await readFile(`/proc/${pid}/status`, \"utf8\");\n let environment = \"\";\n if (token) {\n try {\n environment = await readFile(`/proc/${pid}/environ`, \"utf8\");\n } catch (error) {\n // Without root, the environment of a task that started exiting after\n // the first stat read is unreadable. Only that case counts as gone;\n // any other unreadable environment still fails closed.\n if ((error as { code?: unknown }).code !== \"EACCES\") throw error;\n const now = statFields(await readFile(`/proc/${pid}/stat`, \"utf8\"));\n if (now[19] !== start || exiting(now)) return undefined;\n throw error;\n }\n }\n // /proc reads are separate operations. Reject PID reuse or a group change\n // during observation before combining a token with a start identity.\n const after = statFields(await readFile(`/proc/${pid}/stat`, \"utf8\"));\n if (after[19] !== start || after[2] !== fields[2] || exiting(after)) return undefined;\n return {\n start: `${boot}:${start}`,\n group: Number(fields[2]),\n state: after[0] ?? state,\n rss: Number(/^VmRSS:\\s+(\\d+)/m.exec(status)?.[1] ?? 0) * 1024,\n token: environment\n .split(\"\\0\")\n .find((entry) => entry.startsWith(`${TOKEN_ENV}=`))\n ?.slice(TOKEN_ENV.length + 1),\n };\n }\n if (process.platform === \"darwin\" || process.platform === \"win32\")\n return await portableIdentity(process.platform, pid, token, executeCommand, (selected) =>\n guardianProof(selected, undefined, expectedToken),\n );\n return undefined;\n } catch (error) {\n const code = (error as { code?: unknown }).code;\n if (\n (process.platform === \"linux\" && [\"ENOENT\", \"ESRCH\"].includes(String(code))) ||\n (process.platform === \"darwin\" &&\n code === 1 &&\n !(error as { stdout?: string }).stdout?.trim())\n )\n return undefined;\n throw error;\n }\n}\n\nexport class ProcessNotOwnedError extends Error {\n constructor(public readonly pid: number) {\n super(`Process ${pid} does not have a verified supervisor identity`);\n this.name = \"ProcessNotOwnedError\";\n }\n}\n\nexport async function owns(entry: { pid: number; start: string; token: string }): Promise<boolean> {\n const current = await identity(entry.pid, true, entry.token);\n return (\n current?.start === entry.start && current.token === entry.token && current.group === entry.pid\n );\n}\n","import { randomBytes } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { identity } from \"./identity\";\nimport type { ProcessInfo } from \"./types\";\n\nexport type RegistryEntry = ProcessInfo & {\n token: string;\n start: string;\n cgroup?: string;\n /** Persisted launch gate: recovery may resume an adopted, interrupted launch. */\n gated?: boolean;\n};\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction strings(value: unknown): value is Record<string, string> {\n return record(value) && Object.values(value).every((item) => typeof item === \"string\");\n}\nexport function parseRegistry(source: string, owner: string): RegistryEntry[] {\n const value: unknown = JSON.parse(source);\n if (\n !record(value) ||\n value.version !== 1 ||\n value.owner !== owner ||\n !Array.isArray(value.entries)\n )\n throw new Error(\"Invalid process registry or different owner\");\n const entries: RegistryEntry[] = [];\n for (const entry of value.entries) {\n if (\n !record(entry) ||\n entry.owner !== owner ||\n typeof entry.pid !== \"number\" ||\n !Number.isSafeInteger(entry.pid) ||\n Number(entry.pid) <= 1 ||\n typeof entry.token !== \"string\" ||\n !/^[a-f0-9]{64}$/.test(entry.token) ||\n typeof entry.start !== \"string\" ||\n typeof entry.type !== \"string\" ||\n typeof entry.command !== \"string\" ||\n !Array.isArray(entry.args) ||\n !entry.args.every((arg) => typeof arg === \"string\") ||\n !strings(entry.labels) ||\n typeof entry.startedAt !== \"number\" ||\n !Number.isFinite(entry.startedAt) ||\n entry.startedAt < 0 ||\n !record(entry.limits) ||\n !Object.entries(entry.limits).every(\n ([key, limit]) =>\n [\"timeoutMs\", \"memoryBytes\", \"cpuSeconds\", \"virtualMemoryBytes\"].includes(key) &&\n typeof limit === \"number\" &&\n Number.isSafeInteger(limit) &&\n limit > 0,\n ) ||\n ![\"cgroup-v2\", \"rlimit-polling\", \"rss-polling\"].includes(String(entry.enforcement)) ||\n typeof entry.peakMemoryBytes !== \"number\" ||\n !Number.isFinite(entry.peakMemoryBytes) ||\n entry.peakMemoryBytes < 0 ||\n (entry.cgroup !== undefined && typeof entry.cgroup !== \"string\") ||\n (entry.gated !== undefined && typeof entry.gated !== \"boolean\")\n )\n throw new Error(\"Invalid process registry entry\");\n entries.push(entry as RegistryEntry);\n }\n if (new Set(entries.map((entry) => entry.pid)).size !== entries.length)\n throw new Error(\"Duplicate registry PID\");\n return entries;\n}\n\n/** One writer per registry. A crash leaves a lease which only a dead identity can release. */\nexport class Registry {\n private queue: Promise<void> = Promise.resolve();\n private locked = false;\n constructor(\n public readonly file: string,\n private readonly owner: string,\n ) {}\n async open(): Promise<RegistryEntry[]> {\n await mkdir(path.dirname(this.file), { recursive: true, mode: 0o700 });\n const self = await identity(process.pid);\n if (!self) throw new Error(\"Cannot verify registry writer identity\");\n const lock = `${this.file}.lock`;\n try {\n await mkdir(lock, { mode: 0o700 });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n const reclaim = `${lock}.reclaim`;\n await mkdir(reclaim, { mode: 0o700 });\n try {\n const lease: unknown = JSON.parse(await readFile(path.join(lock, \"writer.json\"), \"utf8\"));\n if (!record(lease) || typeof lease.pid !== \"number\" || typeof lease.start !== \"string\")\n throw new Error(\"Invalid registry writer lease; inspect it manually\");\n const writer = await identity(lease.pid);\n if (writer?.start === lease.start) throw new Error(\"Process registry is already in use\");\n const stale = `${lock}.${randomBytes(8).toString(\"hex\")}`;\n await rename(lock, stale);\n await rm(stale, { recursive: true });\n await mkdir(lock, { mode: 0o700 });\n } finally {\n await rm(reclaim, { recursive: true });\n }\n }\n this.locked = true;\n try {\n await writeFile(\n path.join(lock, \"writer.json\"),\n JSON.stringify({ pid: process.pid, start: self.start }),\n { mode: 0o600 },\n );\n try {\n return parseRegistry(await readFile(this.file, \"utf8\"), this.owner);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw error;\n }\n } catch (error) {\n await this.close();\n throw error;\n }\n }\n save(entries: RegistryEntry[]): Promise<void> {\n const source = JSON.stringify({ version: 1, owner: this.owner, entries }, null, 2) + \"\\n\";\n const next = this.queue.then(async () => {\n const temporary = `${this.file}.${randomBytes(8).toString(\"hex\")}.tmp`;\n try {\n await writeFile(temporary, source, { mode: 0o600, flag: \"wx\" });\n await rename(temporary, this.file);\n } finally {\n await rm(temporary, { force: true });\n }\n });\n this.queue = next.catch(() => undefined);\n return next;\n }\n async close(): Promise<void> {\n await this.queue;\n if (this.locked) {\n await rm(`${this.file}.lock`, { recursive: true });\n this.locked = false;\n }\n }\n}\n","import { spawn as spawnChild } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport path from \"node:path\";\nimport { PassThrough, Writable } from \"node:stream\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { identity, owns, ProcessNotOwnedError, executeCommand } from \"./identity\";\nimport { guardianProof, launchCommand } from \"./guardian\";\nimport { supported, taskkillCommand } from \"./platform\";\nimport { Registry, type RegistryEntry } from \"./registry\";\nimport {\n attachCgroup,\n cgroupMemory,\n cleanCgroup,\n recoveredCgroup,\n validateLimits,\n} from \"./resources\";\nimport type {\n ProcessExit,\n ProcessFilter,\n ProcessHandle,\n ProcessInfo,\n ProcessSupervisor,\n PtyHandle,\n RecoveryResult,\n SpawnOptions,\n SupervisorOptions,\n} from \"./types\";\n\ntype Pty = {\n pid: number;\n onData(listener: (data: string) => void): { dispose(): void };\n onExit(listener: (event: { exitCode: number; signal?: number }) => void): { dispose(): void };\n write(data: string): void;\n resize(columns: number, rows: number): void;\n};\ntype PtyModule = {\n spawn(\n command: string,\n args: string[],\n options: {\n cwd?: string;\n env: Record<string, string>;\n cols: number;\n rows: number;\n name: string;\n },\n ): Pty;\n};\ntype Active = {\n entry: RegistryEntry;\n handle: ProcessHandle;\n reason?: ProcessExit[\"reason\"];\n finish(code: number | null, signal: string | null): Promise<void>;\n stopping?: Promise<void>;\n timer?: ReturnType<typeof setInterval>;\n finished: boolean;\n adopted: boolean;\n fail(error: unknown): void;\n};\n\nfunction info(entry: RegistryEntry): ProcessInfo {\n const { token: _token, start: _start, cgroup: _cgroup, gated: _gated, ...visible } = entry;\n return structuredClone(visible);\n}\nfunction validateFilter(filter: ProcessFilter): void {\n if (\n !filter ||\n typeof filter !== \"object\" ||\n Array.isArray(filter) ||\n Object.keys(filter).some((key) => key !== \"labels\")\n )\n throw new Error(\"Only label filters are supported\");\n if (\n filter.labels !== undefined &&\n (filter.labels === null ||\n typeof filter.labels !== \"object\" ||\n Array.isArray(filter.labels) ||\n !Object.values(filter.labels).every((value) => typeof value === \"string\"))\n )\n throw new Error(\"Labels must be string key/value pairs\");\n}\nfunction matches(entry: RegistryEntry, filter: ProcessFilter): boolean {\n return Object.entries(filter.labels ?? {}).every(([key, value]) => entry.labels[key] === value);\n}\n\nexport function supervisor(options: SupervisorOptions): ProcessSupervisor {\n for (const option of [\"owner\", \"registry\"] as const) {\n if (!options || typeof options[option] !== \"string\" || !options[option].trim()) {\n throw new TypeError(`Invalid supervisor configuration: ${option} must be a nonempty string`);\n }\n }\n validateLimits(options.defaults ?? {});\n // Caller-owned configuration must not change an active supervisor's identity.\n options = { ...options, defaults: { ...options.defaults } };\n const pollMs = options.pollIntervalMs ?? 100;\n const graceMs = options.stopGraceMs ?? 1000;\n if (!Number.isSafeInteger(pollMs) || pollMs < 10 || !Number.isSafeInteger(graceMs) || graceMs < 0)\n throw new Error(\"Invalid polling interval or stop grace period\");\n const registry = new Registry(path.resolve(options.registry), options.owner);\n const entries = new Map<number, RegistryEntry>();\n const active = new Map<number, Active>();\n let ready: Promise<void> | undefined;\n let closing = false;\n let shutdown: Promise<void> | undefined;\n // Serialize public operations, including shutdown, so a gated spawn cannot escape stopAll.\n let queue: Promise<unknown> = Promise.resolve();\n function serial<T>(operation: () => Promise<T>): Promise<T> {\n const next = queue.then(operation);\n queue = next.catch(() => undefined);\n return next;\n }\n async function open(): Promise<void> {\n supported(process.platform);\n if (closing) throw new Error(\"Supervisor has shut down\");\n ready ??= registry.open().then((saved) => {\n for (const entry of saved) {\n delete entry.cgroup;\n entries.set(entry.pid, entry);\n }\n });\n await ready;\n }\n const save = () => registry.save([...entries.values()]);\n\n async function signal(entry: RegistryEntry, value: NodeJS.Signals): Promise<void> {\n if (!(await owns(entry))) throw new ProcessNotOwnedError(entry.pid);\n if (process.platform !== \"linux\" && value === \"SIGCONT\") {\n const proof = await guardianProof(entry.pid, entry.token);\n if (!proof || proof.token !== entry.token || proof.gated)\n throw new ProcessNotOwnedError(entry.pid);\n return;\n }\n if (process.platform === \"win32\") {\n try {\n await executeCommand(taskkillCommand(entry.pid));\n } catch (error) {\n if (await owns(entry)) throw error;\n }\n return;\n }\n // Negative PID targets only the session/group established for this child.\n // Recheck for every signal, including escalation; never match a command line.\n try {\n process.kill(-entry.pid, value);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ESRCH\") throw error;\n }\n }\n async function stopEntry(\n entry: RegistryEntry,\n reason: ProcessExit[\"reason\"] = \"stopped\",\n ): Promise<void> {\n const tracked = active.get(entry.pid);\n let running: Active;\n if (!tracked) {\n if (!(await owns(entry))) throw new ProcessNotOwnedError(entry.pid);\n entry.cgroup = await recoveredCgroup(entry.pid, entry.token);\n running = track(entry);\n } else running = tracked;\n if (running.finished) return running.finish(null, null);\n if (running.stopping) return running.stopping;\n const stopping = (async () => {\n if (!(await owns(entry))) throw new ProcessNotOwnedError(entry.pid);\n running.reason ??= reason;\n await signal(entry, \"SIGTERM\");\n const deadline = Date.now() + graceMs;\n while (await owns(entry)) {\n if (Date.now() >= deadline) {\n await signal(entry, \"SIGKILL\");\n break;\n }\n await delay(20);\n }\n const killedDeadline = Date.now() + 5000;\n while (await owns(entry)) {\n if (Date.now() >= killedDeadline)\n throw new Error(`Process ${entry.pid} did not exit after SIGKILL`);\n await delay(20);\n }\n await running.finish(null, null);\n })();\n running.stopping = stopping;\n try {\n await stopping;\n } catch (error) {\n running.stopping = undefined;\n // The leader may exit between identity verification and a signal. A\n // missing identity is harmless for a tracked handle; a live mismatch is\n // still an ownership failure and must never receive another signal.\n if (error instanceof ProcessNotOwnedError && !(await identity(entry.pid))) {\n await running.finish(null, null);\n return;\n }\n throw error;\n }\n }\n\n function track(\n entry: RegistryEntry,\n streams?: Pick<ProcessHandle, \"stdin\" | \"stdout\" | \"stderr\">,\n ): Active {\n let resolveExit: (exit: ProcessExit) => void = () => undefined;\n let rejectExit: (error: unknown) => void = () => undefined;\n const exited = new Promise<ProcessExit>((resolve, reject) => {\n resolveExit = resolve;\n rejectExit = reject;\n });\n // A rejected persistence operation is still observable through handle.exited.\n void exited.catch(() => undefined);\n const stdout = streams?.stdout ?? new PassThrough();\n const stderr = streams?.stderr ?? new PassThrough();\n const stdin = streams?.stdin ?? new PassThrough();\n let finishing: Promise<void> | undefined;\n const running: Active = {\n entry,\n finished: false,\n adopted: !streams,\n fail: (error) => rejectExit(error),\n handle: {\n pid: entry.pid,\n stdout,\n stderr,\n stdin,\n exited,\n stop: () =>\n serial(async () => {\n if (!running.finished) await stopEntry(entry);\n }),\n },\n finish(code, sig) {\n finishing ??= (async () => {\n running.finished = true;\n clearInterval(running.timer);\n if (entry.cgroup) {\n const memory = await cgroupMemory(entry.cgroup);\n entry.peakMemoryBytes = Math.max(entry.peakMemoryBytes, memory.peak);\n if (memory.oom) running.reason = \"memory-cap\";\n await cleanCgroup(entry.cgroup);\n }\n active.delete(entry.pid);\n entries.delete(entry.pid);\n try {\n await save();\n resolveExit({\n reason: running.reason ?? \"exit\",\n code,\n signal: sig,\n peakMemoryBytes: entry.peakMemoryBytes,\n });\n } catch (error) {\n rejectExit(error);\n throw error;\n }\n if (!streams) {\n (stdout as PassThrough).end();\n (stderr as PassThrough).end();\n stdin.end();\n }\n })();\n return finishing;\n },\n };\n active.set(entry.pid, running);\n return running;\n }\n\n function monitor(running: Active): void {\n if (running.finished) return;\n let sampling = false;\n const sample = async () => {\n if (sampling || running.finished) return;\n sampling = true;\n try {\n const entry = running.entry;\n const current = await identity(entry.pid, true, entry.token);\n if (running.finished) return;\n if (\n current?.start !== entry.start ||\n current.token !== entry.token ||\n current.group !== entry.pid\n ) {\n if (!current) {\n if (running.adopted) await running.finish(null, null);\n } else {\n // A live child that changes its environment or group can no longer\n // be supervised safely. Preserve the entry and surface the failure.\n throw new ProcessNotOwnedError(entry.pid);\n }\n return;\n }\n entry.peakMemoryBytes = Math.max(entry.peakMemoryBytes, current.rss);\n if (entry.cgroup) {\n const memory = await cgroupMemory(entry.cgroup);\n if (running.finished) return;\n entry.peakMemoryBytes = Math.max(entry.peakMemoryBytes, memory.peak);\n if (memory.oom) running.reason = \"memory-cap\";\n }\n if (entry.limits.memoryBytes && entry.peakMemoryBytes > entry.limits.memoryBytes)\n await stopEntry(entry, \"memory-cap\");\n else if (entry.limits.timeoutMs && Date.now() - entry.startedAt >= entry.limits.timeoutMs)\n await stopEntry(entry, \"timeout\");\n } finally {\n sampling = false;\n }\n };\n running.timer = setInterval(() => {\n void sample().catch((error: unknown) => {\n // Never signal on an observation error. Surface it to the caller and keep the registry.\n clearInterval(running.timer);\n if (!running.finished) running.fail(error);\n });\n }, pollMs);\n running.timer.unref();\n }\n\n async function launch(\n input: SpawnOptions & { columns?: number; rows?: number },\n terminal: boolean,\n ): Promise<ProcessHandle | PtyHandle> {\n await open();\n if ([...entries.keys()].some((pid) => !active.has(pid)))\n throw new Error(\"Call recover() before spawning with an existing registry\");\n if (\n typeof input.command !== \"string\" ||\n !input.command ||\n typeof input.kind !== \"string\" ||\n !input.kind\n )\n throw new Error(\"kind and command are required\");\n validateFilter({ labels: input.labels });\n if (\n terminal &&\n [input.columns ?? 80, input.rows ?? 24].some(\n (size) => !Number.isSafeInteger(size) || size <= 0,\n )\n )\n throw new Error(\"Terminal dimensions must be positive safe integers\");\n const limits = { ...options.defaults, ...input.limits };\n validateLimits(limits);\n const token = randomBytes(32).toString(\"hex\");\n const {\n file: executable,\n args,\n env,\n } = launchCommand(\n process.platform,\n process.execPath,\n input.command,\n input.args ?? [],\n limits,\n { ...process.env, ...input.env },\n token,\n );\n let pid: number;\n let streams: Pick<ProcessHandle, \"stdin\" | \"stdout\" | \"stderr\">;\n let terminalProcess: Pty | undefined;\n let exit: { code: number | null; signal: string | null } | undefined;\n let spawnError: Error | undefined;\n let running: Active | undefined;\n const onExit = (code: number | null, sig: string | null) => {\n exit = { code, signal: sig };\n if (running) void running.finish(code, sig).catch(() => undefined);\n };\n if (terminal) {\n // Optional peer loaded only here. Structural types follow node-pty's official typings.\n const peer = \"node-pty\";\n const pty = (await import(peer)) as PtyModule;\n terminalProcess = pty.spawn(executable, args, {\n cwd: input.cwd,\n env: Object.fromEntries(\n Object.entries(env).filter((pair): pair is [string, string] => pair[1] !== undefined),\n ),\n cols: input.columns ?? 80,\n rows: input.rows ?? 24,\n name: \"xterm-256color\",\n });\n pid = terminalProcess.pid;\n const output = new PassThrough();\n const errors = new PassThrough();\n const instance = terminalProcess;\n const dataSubscription = instance.onData((data) => output.write(data));\n const inputStream = new Writable({\n write(chunk: Buffer, _encoding, callback) {\n instance.write(chunk.toString());\n callback();\n },\n });\n const exitSubscription = instance.onExit((event) => {\n dataSubscription.dispose();\n exitSubscription.dispose();\n output.end();\n errors.end();\n inputStream.end();\n onExit(event.exitCode, event.signal ? String(event.signal) : null);\n });\n streams = { stdout: output, stderr: errors, stdin: inputStream };\n } else {\n const child = spawnChild(executable, args, {\n cwd: input.cwd,\n env,\n detached: process.platform !== \"win32\",\n windowsHide: true,\n stdio: \"pipe\",\n });\n child.on(\"error\", (error) => {\n spawnError = error;\n });\n // Finish on leader exit so cgroup cleanup can stop remaining members;\n // waiting for close could deadlock on a descendant holding a pipe open.\n child.on(\"exit\", onExit);\n if (!child.pid) throw new Error(\"Could not start process\");\n pid = child.pid;\n streams = { stdout: child.stdout, stderr: child.stderr, stdin: child.stdin };\n }\n let entry: RegistryEntry | undefined;\n try {\n const deadline = Date.now() + 5000;\n let current = await identity(pid, true, token);\n while (!current?.state.startsWith(\"T\") || current.token !== token) {\n if (spawnError) throw spawnError;\n if (exit || Date.now() >= deadline)\n throw new Error(\"Child failed to reach the resource setup gate\");\n await delay(10);\n current = await identity(pid, true, token);\n }\n if (current.group !== pid) throw new Error(\"Child did not create an isolated process group\");\n entry = {\n pid,\n owner: options.owner,\n type: input.kind,\n command: input.command,\n args: [...(input.args ?? [])],\n labels: { ...input.labels },\n startedAt: Date.now(),\n limits,\n enforcement: process.platform === \"win32\" ? \"rss-polling\" : \"rlimit-polling\",\n peakMemoryBytes: current.rss,\n token,\n start: current.start,\n gated: true,\n };\n entry.cgroup = await attachCgroup(pid, token, limits, options.cgroupRoot);\n if (entry.cgroup) entry.enforcement = \"cgroup-v2\";\n entries.set(pid, entry);\n await save();\n running = track(entry, streams);\n await signal(entry, \"SIGCONT\");\n delete entry.gated;\n await save();\n monitor(running);\n if (terminalProcess) {\n const instance = terminalProcess;\n return {\n ...running.handle,\n write: (data: string) => instance.write(data),\n resize: (columns: number, rows: number) => instance.resize(columns, rows),\n };\n }\n return running.handle;\n } catch (error) {\n // This PID was created by this call. Kill only after its random token is verified.\n const current = await identity(pid, true, token);\n if (current?.token === token) {\n const failed = { pid, token, start: current.start };\n if (await owns(failed)) {\n try {\n if (process.platform === \"win32\") await executeCommand(taskkillCommand(pid));\n else process.kill(-pid, \"SIGKILL\");\n } catch {\n /* Already exited. */\n }\n }\n }\n if (entry && !running) {\n entries.delete(pid);\n await save();\n }\n if (entry?.cgroup) await cleanCgroup(entry.cgroup);\n throw error;\n }\n }\n\n const api: ProcessSupervisor = {\n spawn: (input) => serial(() => launch(input, false)),\n spawnPty: (input) => serial(async () => (await launch(input, true)) as PtyHandle),\n list: (filter = {}) =>\n serial(async () => {\n validateFilter(filter);\n await open();\n return [...entries.values()].filter((entry) => matches(entry, filter)).map(info);\n }),\n stop: (filter = {}) =>\n serial(async () => {\n validateFilter(filter);\n await open();\n for (const entry of [...entries.values()].filter((entry) => matches(entry, filter)))\n await stopEntry(entry);\n }),\n stopPid: (pid) =>\n serial(async () => {\n await open();\n const entry = entries.get(pid);\n if (!entry) throw new ProcessNotOwnedError(pid);\n await stopEntry(entry);\n }),\n recover: (recovery = {}) =>\n serial(async () => {\n await open();\n const result: RecoveryResult = { adopted: [], stopped: [], cleared: [] };\n for (const entry of [...entries.values()]) {\n if (active.has(entry.pid)) continue;\n if (!(await owns(entry))) {\n entries.delete(entry.pid);\n result.cleared.push(entry.pid);\n continue;\n }\n // Never trust a persisted filesystem path for cgroup I/O after a restart.\n entry.cgroup = await recoveredCgroup(entry.pid, entry.token);\n entry.enforcement = entry.cgroup\n ? \"cgroup-v2\"\n : process.platform === \"win32\"\n ? \"rss-polling\"\n : \"rlimit-polling\";\n const keep = await recovery.keep?.(info(entry));\n if (keep) {\n const running = track(entry);\n monitor(running);\n if (entry.gated) {\n await signal(entry, \"SIGCONT\");\n delete entry.gated;\n }\n result.adopted.push(entry.pid);\n } else {\n await stopEntry(entry);\n result.stopped.push(entry.pid);\n }\n }\n await save();\n return result;\n }),\n stopAll() {\n shutdown ??= serial(async () => {\n await open();\n closing = true;\n try {\n const errors: unknown[] = [];\n for (const entry of [...entries.values()]) {\n try {\n if (await owns(entry)) await stopEntry(entry);\n else {\n const running = active.get(entry.pid);\n if (running) await running.finish(null, null);\n else entries.delete(entry.pid);\n }\n } catch (error) {\n errors.push(error);\n }\n }\n await save();\n if (errors.length)\n throw new AggregateError(errors, \"Some owned processes could not be stopped\");\n } finally {\n await registry.close();\n }\n });\n return shutdown;\n },\n };\n return api;\n}\n"],"mappings":"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"}
|
package/esm/guardian.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { launcher } from "./resources.mjs";
|
|
2
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
3
|
+
import { connect } from "node:net";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/process/src/guardian.ts
|
|
6
|
+
function guardianAddress(platform, pid, uid) {
|
|
7
|
+
return platform === "win32" ? `\\\\.\\pipe\\warlock-process-${pid}` : `/tmp/warlock-process-${uid}-${pid}.sock`;
|
|
8
|
+
}
|
|
9
|
+
/** A live guardian reads its own environment, never argv, to prove ownership.
|
|
10
|
+
* Node IPC is native-free: https://nodejs.org/api/net.html#ipc-support
|
|
11
|
+
* The stable root PID stays alive for the workload and survives supervisor restarts.
|
|
12
|
+
*/
|
|
13
|
+
const guardianScript = String.raw`
|
|
14
|
+
const { createServer } = require("node:net");
|
|
15
|
+
const { createHmac } = require("node:crypto");
|
|
16
|
+
const { spawn } = require("node:child_process");
|
|
17
|
+
const { chmodSync, unlinkSync } = require("node:fs");
|
|
18
|
+
const token = process.env.WARLOCK_PROCESS_TOKEN;
|
|
19
|
+
const address = process.platform === "win32"
|
|
20
|
+
? "\\\\.\\pipe\\warlock-process-" + process.pid
|
|
21
|
+
: "/tmp/warlock-process-" + process.getuid() + "-" + process.pid + ".sock";
|
|
22
|
+
let gated = true;
|
|
23
|
+
const finish = (code) => { server.close(); process.exit(code); };
|
|
24
|
+
const start = () => {
|
|
25
|
+
if (!gated) return;
|
|
26
|
+
gated = false;
|
|
27
|
+
const [command, ...args] = JSON.parse(process.env.WARLOCK_PROCESS_LAUNCH);
|
|
28
|
+
const env = { ...process.env };
|
|
29
|
+
for (const key of Object.keys(env)) {
|
|
30
|
+
const name = process.platform === "win32" ? key.toUpperCase() : key;
|
|
31
|
+
if (name === "WARLOCK_PROCESS_TOKEN" || name === "WARLOCK_PROCESS_LAUNCH") delete env[key];
|
|
32
|
+
}
|
|
33
|
+
const child = spawn(command, args, { env, stdio: "inherit", windowsHide: true });
|
|
34
|
+
child.on("error", (error) => { console.error(error.message); finish(127); });
|
|
35
|
+
child.on("exit", (code, signal) => {
|
|
36
|
+
if (signal && process.platform !== "win32") process.kill(process.pid, signal);
|
|
37
|
+
else finish(code ?? 1);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const server = createServer((socket) => {
|
|
41
|
+
socket.setTimeout(2000, () => socket.destroy());
|
|
42
|
+
socket.on("error", () => {});
|
|
43
|
+
let input = "";
|
|
44
|
+
socket.on("data", (chunk) => {
|
|
45
|
+
input += chunk;
|
|
46
|
+
if (input.length > 1024) { socket.destroy(); return; }
|
|
47
|
+
if (!input.endsWith("\n")) return;
|
|
48
|
+
try {
|
|
49
|
+
const request = JSON.parse(input);
|
|
50
|
+
if (!/^[a-f0-9]{64}$/.test(request.nonce)) { socket.destroy(); return; }
|
|
51
|
+
const sign = (value) => createHmac("sha256", token).update(value).digest("hex");
|
|
52
|
+
if (request.resume === sign(request.nonce + ":resume")) start();
|
|
53
|
+
socket.end(JSON.stringify({ pid: process.pid, digest: sign(request.nonce + ":" + process.pid + ":" + gated), gated }) + "\n");
|
|
54
|
+
} catch { socket.destroy(); }
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
server.on("error", (error) => { console.error(error.message); process.exit(126); });
|
|
58
|
+
if (process.platform !== "win32") {
|
|
59
|
+
try { unlinkSync(address); } catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
60
|
+
process.on("exit", () => { try { unlinkSync(address); } catch {} });
|
|
61
|
+
}
|
|
62
|
+
server.listen(address, () => { if (process.platform !== "win32") chmodSync(address, 0o600); });
|
|
63
|
+
`;
|
|
64
|
+
async function guardianProof(pid, resume, expectedToken = resume) {
|
|
65
|
+
if (!expectedToken) return void 0;
|
|
66
|
+
const token = expectedToken;
|
|
67
|
+
const nonce = randomBytes(32).toString("hex");
|
|
68
|
+
const sign = (value) => createHmac("sha256", token).update(value).digest("hex");
|
|
69
|
+
const address = guardianAddress(process.platform, pid, process.getuid?.() ?? 0);
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const socket = connect(address);
|
|
72
|
+
let data = "";
|
|
73
|
+
const fail = (error) => {
|
|
74
|
+
socket.destroy();
|
|
75
|
+
reject(error);
|
|
76
|
+
};
|
|
77
|
+
socket.setTimeout(2e3, () => fail(/* @__PURE__ */ new Error("Process ownership observation timed out")));
|
|
78
|
+
socket.on("connect", () => socket.write(JSON.stringify({
|
|
79
|
+
nonce,
|
|
80
|
+
resume: resume ? sign(nonce + ":resume") : void 0
|
|
81
|
+
}) + "\n"));
|
|
82
|
+
socket.on("data", (chunk) => {
|
|
83
|
+
data += chunk.toString();
|
|
84
|
+
if (data.length > 1024) fail(/* @__PURE__ */ new Error("Invalid process ownership response"));
|
|
85
|
+
});
|
|
86
|
+
socket.on("error", (error) => {
|
|
87
|
+
if (["ENOENT", "ECONNREFUSED"].includes(error.code ?? "")) resolve(void 0);
|
|
88
|
+
else reject(error);
|
|
89
|
+
});
|
|
90
|
+
socket.on("end", () => {
|
|
91
|
+
try {
|
|
92
|
+
resolve(parseGuardianProof(data, pid, nonce, token));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
reject(error);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** Malformed observation is an error; an authenticated identity mismatch is unowned. */
|
|
100
|
+
function parseGuardianProof(source, pid, nonce, token) {
|
|
101
|
+
const proof = JSON.parse(source);
|
|
102
|
+
if (!proof || typeof proof !== "object" || !("pid" in proof) || typeof proof.pid !== "number" || !("gated" in proof) || typeof proof.gated !== "boolean" || !("digest" in proof) || typeof proof.digest !== "string") throw new Error("Invalid process ownership response");
|
|
103
|
+
const expected = createHmac("sha256", token).update(nonce + ":" + pid + ":" + proof.gated).digest("hex");
|
|
104
|
+
if (proof.pid !== pid || proof.digest !== expected) return void 0;
|
|
105
|
+
return {
|
|
106
|
+
pid,
|
|
107
|
+
token,
|
|
108
|
+
gated: proof.gated
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Pure launch construction, including case-insensitive Windows environment keys. */
|
|
112
|
+
function launchCommand(platform, node, command, args, limits, environment, token) {
|
|
113
|
+
const portable = platform !== "linux";
|
|
114
|
+
const env = Object.fromEntries(Object.entries(environment).filter(([key]) => !["WARLOCK_PROCESS_TOKEN", "WARLOCK_PROCESS_LAUNCH"].includes(platform === "win32" ? key.toUpperCase() : key)));
|
|
115
|
+
env.WARLOCK_PROCESS_TOKEN = token;
|
|
116
|
+
if (portable) env.WARLOCK_PROCESS_LAUNCH = JSON.stringify([command, ...args]);
|
|
117
|
+
const executable = portable ? node : command;
|
|
118
|
+
const arguments_ = portable ? ["-e", guardianScript] : args;
|
|
119
|
+
return {
|
|
120
|
+
file: platform === "win32" ? executable : "/bin/sh",
|
|
121
|
+
args: platform === "win32" ? arguments_ : [
|
|
122
|
+
"-c",
|
|
123
|
+
launcher(limits, platform),
|
|
124
|
+
"warlock-process",
|
|
125
|
+
executable,
|
|
126
|
+
...arguments_
|
|
127
|
+
],
|
|
128
|
+
env
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
//#endregion
|
|
133
|
+
export { guardianAddress, guardianProof, guardianScript, launchCommand, parseGuardianProof };
|
|
134
|
+
//# sourceMappingURL=guardian.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardian.mjs","names":[],"sources":["../../../../../../../packages/process/src/guardian.ts"],"sourcesContent":["import { createHmac, randomBytes } from \"node:crypto\";\nimport { connect } from \"node:net\";\nimport type { Command, OwnershipProof } from \"./platform\";\nimport { launcher } from \"./resources\";\nimport type { ProcessLimits } from \"./types\";\n\nexport function guardianAddress(platform: string, pid: number, uid: number): string {\n return platform === \"win32\"\n ? `\\\\\\\\.\\\\pipe\\\\warlock-process-${pid}`\n : `/tmp/warlock-process-${uid}-${pid}.sock`;\n}\n\n/** A live guardian reads its own environment, never argv, to prove ownership.\n * Node IPC is native-free: https://nodejs.org/api/net.html#ipc-support\n * The stable root PID stays alive for the workload and survives supervisor restarts.\n */\nexport const guardianScript = String.raw`\nconst { createServer } = require(\"node:net\");\nconst { createHmac } = require(\"node:crypto\");\nconst { spawn } = require(\"node:child_process\");\nconst { chmodSync, unlinkSync } = require(\"node:fs\");\nconst token = process.env.WARLOCK_PROCESS_TOKEN;\nconst address = process.platform === \"win32\"\n ? \"\\\\\\\\.\\\\pipe\\\\warlock-process-\" + process.pid\n : \"/tmp/warlock-process-\" + process.getuid() + \"-\" + process.pid + \".sock\";\nlet gated = true;\nconst finish = (code) => { server.close(); process.exit(code); };\nconst start = () => {\n if (!gated) return;\n gated = false;\n const [command, ...args] = JSON.parse(process.env.WARLOCK_PROCESS_LAUNCH);\n const env = { ...process.env };\n for (const key of Object.keys(env)) {\n const name = process.platform === \"win32\" ? key.toUpperCase() : key;\n if (name === \"WARLOCK_PROCESS_TOKEN\" || name === \"WARLOCK_PROCESS_LAUNCH\") delete env[key];\n }\n const child = spawn(command, args, { env, stdio: \"inherit\", windowsHide: true });\n child.on(\"error\", (error) => { console.error(error.message); finish(127); });\n child.on(\"exit\", (code, signal) => {\n if (signal && process.platform !== \"win32\") process.kill(process.pid, signal);\n else finish(code ?? 1);\n });\n};\nconst server = createServer((socket) => {\n socket.setTimeout(2000, () => socket.destroy());\n socket.on(\"error\", () => {});\n let input = \"\";\n socket.on(\"data\", (chunk) => {\n input += chunk;\n if (input.length > 1024) { socket.destroy(); return; }\n if (!input.endsWith(\"\\n\")) return;\n try {\n const request = JSON.parse(input);\n if (!/^[a-f0-9]{64}$/.test(request.nonce)) { socket.destroy(); return; }\n const sign = (value) => createHmac(\"sha256\", token).update(value).digest(\"hex\");\n if (request.resume === sign(request.nonce + \":resume\")) start();\n socket.end(JSON.stringify({ pid: process.pid, digest: sign(request.nonce + \":\" + process.pid + \":\" + gated), gated }) + \"\\n\");\n } catch { socket.destroy(); }\n });\n});\nserver.on(\"error\", (error) => { console.error(error.message); process.exit(126); });\nif (process.platform !== \"win32\") {\n try { unlinkSync(address); } catch (error) { if (error.code !== \"ENOENT\") throw error; }\n process.on(\"exit\", () => { try { unlinkSync(address); } catch {} });\n}\nserver.listen(address, () => { if (process.platform !== \"win32\") chmodSync(address, 0o600); });\n`;\n\nexport async function guardianProof(\n pid: number,\n resume?: string,\n expectedToken: string | undefined = resume,\n): Promise<OwnershipProof | undefined> {\n if (!expectedToken) return undefined;\n const token = expectedToken;\n const nonce = randomBytes(32).toString(\"hex\");\n const sign = (value: string) => createHmac(\"sha256\", token).update(value).digest(\"hex\");\n const address = guardianAddress(process.platform, pid, process.getuid?.() ?? 0);\n return new Promise((resolve, reject) => {\n const socket = connect(address);\n let data = \"\";\n const fail = (error: Error) => {\n socket.destroy();\n reject(error);\n };\n socket.setTimeout(2000, () => fail(new Error(\"Process ownership observation timed out\")));\n socket.on(\"connect\", () =>\n socket.write(\n JSON.stringify({ nonce, resume: resume ? sign(nonce + \":resume\") : undefined }) + \"\\n\",\n ),\n );\n socket.on(\"data\", (chunk: Buffer) => {\n data += chunk.toString();\n if (data.length > 1024) fail(new Error(\"Invalid process ownership response\"));\n });\n socket.on(\"error\", (error: NodeJS.ErrnoException) => {\n if ([\"ENOENT\", \"ECONNREFUSED\"].includes(error.code ?? \"\")) resolve(undefined);\n else reject(error);\n });\n socket.on(\"end\", () => {\n try {\n resolve(parseGuardianProof(data, pid, nonce, token));\n } catch (error) {\n reject(error);\n }\n });\n });\n}\n\n/** Malformed observation is an error; an authenticated identity mismatch is unowned. */\nexport function parseGuardianProof(\n source: string,\n pid: number,\n nonce: string,\n token: string,\n): OwnershipProof | undefined {\n const proof: unknown = JSON.parse(source);\n if (\n !proof ||\n typeof proof !== \"object\" ||\n !(\"pid\" in proof) ||\n typeof proof.pid !== \"number\" ||\n !(\"gated\" in proof) ||\n typeof proof.gated !== \"boolean\" ||\n !(\"digest\" in proof) ||\n typeof proof.digest !== \"string\"\n )\n throw new Error(\"Invalid process ownership response\");\n const expected = createHmac(\"sha256\", token)\n .update(nonce + \":\" + pid + \":\" + proof.gated)\n .digest(\"hex\");\n if (proof.pid !== pid || proof.digest !== expected) return undefined;\n return { pid, token, gated: proof.gated };\n}\n\n/** Pure launch construction, including case-insensitive Windows environment keys. */\nexport function launchCommand(\n platform: string,\n node: string,\n command: string,\n args: string[],\n limits: ProcessLimits,\n environment: NodeJS.ProcessEnv,\n token: string,\n): Omit<Command, \"env\"> & { env: NodeJS.ProcessEnv } {\n const portable = platform !== \"linux\";\n const env: NodeJS.ProcessEnv = Object.fromEntries(\n Object.entries(environment).filter(\n ([key]) =>\n ![\"WARLOCK_PROCESS_TOKEN\", \"WARLOCK_PROCESS_LAUNCH\"].includes(\n platform === \"win32\" ? key.toUpperCase() : key,\n ),\n ),\n );\n env.WARLOCK_PROCESS_TOKEN = token;\n if (portable) env.WARLOCK_PROCESS_LAUNCH = JSON.stringify([command, ...args]);\n const executable = portable ? node : command;\n const arguments_ = portable ? [\"-e\", guardianScript] : args;\n return {\n file: platform === \"win32\" ? executable : \"/bin/sh\",\n args:\n platform === \"win32\"\n ? arguments_\n : [\"-c\", launcher(limits, platform), \"warlock-process\", executable, ...arguments_],\n env,\n };\n}\n"],"mappings":";;;;;AAMA,SAAgB,gBAAgB,UAAkB,KAAa,KAAqB;CAClF,OAAO,aAAa,UAChB,gCAAgC,QAChC,wBAAwB,IAAI,GAAG,IAAI;AACzC;;;;;AAMA,MAAa,iBAAiB,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDxC,eAAsB,cACpB,KACA,QACA,gBAAoC,QACC;CACrC,IAAI,CAAC,eAAe,OAAO;CAC3B,MAAM,QAAQ;CACd,MAAM,QAAQ,YAAY,EAAE,CAAC,CAAC,SAAS,KAAK;CAC5C,MAAM,QAAQ,UAAkB,WAAW,UAAU,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;CACtF,MAAM,UAAU,gBAAgB,QAAQ,UAAU,KAAK,QAAQ,SAAS,KAAK,CAAC;CAC9E,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,SAAS,QAAQ,OAAO;EAC9B,IAAI,OAAO;EACX,MAAM,QAAQ,UAAiB;GAC7B,OAAO,QAAQ;GACf,OAAO,KAAK;EACd;EACA,OAAO,WAAW,WAAY,qBAAK,IAAI,MAAM,yCAAyC,CAAC,CAAC;EACxF,OAAO,GAAG,iBACR,OAAO,MACL,KAAK,UAAU;GAAE;GAAO,QAAQ,SAAS,KAAK,QAAQ,SAAS,IAAI;EAAU,CAAC,IAAI,IACpF,CACF;EACA,OAAO,GAAG,SAAS,UAAkB;GACnC,QAAQ,MAAM,SAAS;GACvB,IAAI,KAAK,SAAS,MAAM,qBAAK,IAAI,MAAM,oCAAoC,CAAC;EAC9E,CAAC;EACD,OAAO,GAAG,UAAU,UAAiC;GACnD,IAAI,CAAC,UAAU,cAAc,CAAC,CAAC,SAAS,MAAM,QAAQ,EAAE,GAAG,QAAQ,MAAS;QACvE,OAAO,KAAK;EACnB,CAAC;EACD,OAAO,GAAG,aAAa;GACrB,IAAI;IACF,QAAQ,mBAAmB,MAAM,KAAK,OAAO,KAAK,CAAC;GACrD,SAAS,OAAO;IACd,OAAO,KAAK;GACd;EACF,CAAC;CACH,CAAC;AACH;;AAGA,SAAgB,mBACd,QACA,KACA,OACA,OAC4B;CAC5B,MAAM,QAAiB,KAAK,MAAM,MAAM;CACxC,IACE,CAAC,SACD,OAAO,UAAU,YACjB,EAAE,SAAS,UACX,OAAO,MAAM,QAAQ,YACrB,EAAE,WAAW,UACb,OAAO,MAAM,UAAU,aACvB,EAAE,YAAY,UACd,OAAO,MAAM,WAAW,UAExB,MAAM,IAAI,MAAM,oCAAoC;CACtD,MAAM,WAAW,WAAW,UAAU,KAAK,CAAC,CACzC,OAAO,QAAQ,MAAM,MAAM,MAAM,MAAM,KAAK,CAAC,CAC7C,OAAO,KAAK;CACf,IAAI,MAAM,QAAQ,OAAO,MAAM,WAAW,UAAU,OAAO;CAC3D,OAAO;EAAE;EAAK;EAAO,OAAO,MAAM;CAAM;AAC1C;;AAGA,SAAgB,cACd,UACA,MACA,SACA,MACA,QACA,aACA,OACmD;CACnD,MAAM,WAAW,aAAa;CAC9B,MAAM,MAAyB,OAAO,YACpC,OAAO,QAAQ,WAAW,CAAC,CAAC,QACzB,CAAC,SACA,CAAC,CAAC,yBAAyB,wBAAwB,CAAC,CAAC,SACnD,aAAa,UAAU,IAAI,YAAY,IAAI,GAC7C,CACJ,CACF;CACA,IAAI,wBAAwB;CAC5B,IAAI,UAAU,IAAI,yBAAyB,KAAK,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;CAC5E,MAAM,aAAa,WAAW,OAAO;CACrC,MAAM,aAAa,WAAW,CAAC,MAAM,cAAc,IAAI;CACvD,OAAO;EACL,MAAM,aAAa,UAAU,aAAa;EAC1C,MACE,aAAa,UACT,aACA;GAAC;GAAM,SAAS,QAAQ,QAAQ;GAAG;GAAmB;GAAY,GAAG;EAAU;EACrF;CACF;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.mts","names":[],"sources":["../../../../../../../packages/process/src/identity.ts"],"mappings":";qBAmGa,6BAA6B;WACZ;EAA5B,YAA4B"}
|
package/esm/identity.mjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { guardianProof } from "./guardian.mjs";
|
|
2
|
+
import { portableIdentity } from "./platform.mjs";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
//#region ../../packages/process/src/identity.ts
|
|
8
|
+
const execute = promisify(execFile);
|
|
9
|
+
async function executeCommand(command) {
|
|
10
|
+
const { stdout } = await execute(command.file, command.args, {
|
|
11
|
+
env: {
|
|
12
|
+
...process.env,
|
|
13
|
+
...command.env
|
|
14
|
+
},
|
|
15
|
+
timeout: 5e3,
|
|
16
|
+
maxBuffer: 4194304,
|
|
17
|
+
windowsHide: true
|
|
18
|
+
});
|
|
19
|
+
return stdout;
|
|
20
|
+
}
|
|
21
|
+
const TOKEN_ENV = "WARLOCK_PROCESS_TOKEN";
|
|
22
|
+
function statFields(stat) {
|
|
23
|
+
return stat.slice(stat.lastIndexOf(")") + 2).trim().split(/\s+/);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A task that is exiting has already released its memory map (stat field 23,
|
|
27
|
+
* vsize, reads 0) before it turns into a zombie. Linux then reports its /proc
|
|
28
|
+
* files as root-owned, so it is never a live child to verify or signal.
|
|
29
|
+
*/
|
|
30
|
+
function exiting(fields) {
|
|
31
|
+
return ["Z", "X"].includes(fields[0] ?? "") || fields[20] === "0";
|
|
32
|
+
}
|
|
33
|
+
/** /proc fields: https://man7.org/linux/man-pages/man5/proc_pid_stat.5.html */
|
|
34
|
+
async function identity(pid, token = false, expectedToken) {
|
|
35
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) return void 0;
|
|
36
|
+
try {
|
|
37
|
+
if (process.platform === "linux") {
|
|
38
|
+
const fields = statFields(await readFile(`/proc/${pid}/stat`, "utf8"));
|
|
39
|
+
const state = fields[0];
|
|
40
|
+
const start = fields[19];
|
|
41
|
+
if (!state || !start || exiting(fields)) return void 0;
|
|
42
|
+
const boot = (await readFile("/proc/sys/kernel/random/boot_id", "utf8")).trim();
|
|
43
|
+
const status = await readFile(`/proc/${pid}/status`, "utf8");
|
|
44
|
+
let environment = "";
|
|
45
|
+
if (token) try {
|
|
46
|
+
environment = await readFile(`/proc/${pid}/environ`, "utf8");
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error.code !== "EACCES") throw error;
|
|
49
|
+
const now = statFields(await readFile(`/proc/${pid}/stat`, "utf8"));
|
|
50
|
+
if (now[19] !== start || exiting(now)) return void 0;
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
const after = statFields(await readFile(`/proc/${pid}/stat`, "utf8"));
|
|
54
|
+
if (after[19] !== start || after[2] !== fields[2] || exiting(after)) return void 0;
|
|
55
|
+
return {
|
|
56
|
+
start: `${boot}:${start}`,
|
|
57
|
+
group: Number(fields[2]),
|
|
58
|
+
state: after[0] ?? state,
|
|
59
|
+
rss: Number(/^VmRSS:\s+(\d+)/m.exec(status)?.[1] ?? 0) * 1024,
|
|
60
|
+
token: environment.split("\0").find((entry) => entry.startsWith(`${TOKEN_ENV}=`))?.slice(22)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (process.platform === "darwin" || process.platform === "win32") return await portableIdentity(process.platform, pid, token, executeCommand, (selected) => guardianProof(selected, void 0, expectedToken));
|
|
64
|
+
return;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const code = error.code;
|
|
67
|
+
if (process.platform === "linux" && ["ENOENT", "ESRCH"].includes(String(code)) || process.platform === "darwin" && code === 1 && !error.stdout?.trim()) return void 0;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
var ProcessNotOwnedError = class extends Error {
|
|
72
|
+
pid;
|
|
73
|
+
constructor(pid) {
|
|
74
|
+
super(`Process ${pid} does not have a verified supervisor identity`);
|
|
75
|
+
this.pid = pid;
|
|
76
|
+
this.name = "ProcessNotOwnedError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
async function owns(entry) {
|
|
80
|
+
const current = await identity(entry.pid, true, entry.token);
|
|
81
|
+
return current?.start === entry.start && current.token === entry.token && current.group === entry.pid;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
export { ProcessNotOwnedError, TOKEN_ENV, executeCommand, identity, owns };
|
|
86
|
+
//# sourceMappingURL=identity.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.mjs","names":[],"sources":["../../../../../../../packages/process/src/identity.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { readFile } from \"node:fs/promises\";\nimport { promisify } from \"node:util\";\n\nimport { guardianProof } from \"./guardian\";\nimport { portableIdentity, type Command } from \"./platform\";\n\nconst execute = promisify(execFile);\nexport async function executeCommand(command: Command): Promise<string> {\n const { stdout } = await execute(command.file, command.args, {\n env: { ...process.env, ...command.env },\n timeout: 5000,\n maxBuffer: 4 * 1024 * 1024,\n windowsHide: true,\n });\n return stdout;\n}\nexport const TOKEN_ENV = \"WARLOCK_PROCESS_TOKEN\";\nexport type Identity = { start: string; group: number; state: string; rss: number; token?: string };\n\nfunction statFields(stat: string): string[] {\n return stat\n .slice(stat.lastIndexOf(\")\") + 2)\n .trim()\n .split(/\\s+/);\n}\n\n/**\n * A task that is exiting has already released its memory map (stat field 23,\n * vsize, reads 0) before it turns into a zombie. Linux then reports its /proc\n * files as root-owned, so it is never a live child to verify or signal.\n */\nfunction exiting(fields: string[]): boolean {\n return [\"Z\", \"X\"].includes(fields[0] ?? \"\") || fields[20] === \"0\";\n}\n\n/** /proc fields: https://man7.org/linux/man-pages/man5/proc_pid_stat.5.html */\nexport async function identity(\n pid: number,\n token = false,\n expectedToken?: string,\n): Promise<Identity | undefined> {\n if (!Number.isSafeInteger(pid) || pid <= 1) return undefined;\n try {\n if (process.platform === \"linux\") {\n const stat = await readFile(`/proc/${pid}/stat`, \"utf8\");\n const fields = statFields(stat);\n const state = fields[0];\n const start = fields[19];\n if (!state || !start || exiting(fields)) return undefined;\n const boot = (await readFile(\"/proc/sys/kernel/random/boot_id\", \"utf8\")).trim();\n const status = await readFile(`/proc/${pid}/status`, \"utf8\");\n let environment = \"\";\n if (token) {\n try {\n environment = await readFile(`/proc/${pid}/environ`, \"utf8\");\n } catch (error) {\n // Without root, the environment of a task that started exiting after\n // the first stat read is unreadable. Only that case counts as gone;\n // any other unreadable environment still fails closed.\n if ((error as { code?: unknown }).code !== \"EACCES\") throw error;\n const now = statFields(await readFile(`/proc/${pid}/stat`, \"utf8\"));\n if (now[19] !== start || exiting(now)) return undefined;\n throw error;\n }\n }\n // /proc reads are separate operations. Reject PID reuse or a group change\n // during observation before combining a token with a start identity.\n const after = statFields(await readFile(`/proc/${pid}/stat`, \"utf8\"));\n if (after[19] !== start || after[2] !== fields[2] || exiting(after)) return undefined;\n return {\n start: `${boot}:${start}`,\n group: Number(fields[2]),\n state: after[0] ?? state,\n rss: Number(/^VmRSS:\\s+(\\d+)/m.exec(status)?.[1] ?? 0) * 1024,\n token: environment\n .split(\"\\0\")\n .find((entry) => entry.startsWith(`${TOKEN_ENV}=`))\n ?.slice(TOKEN_ENV.length + 1),\n };\n }\n if (process.platform === \"darwin\" || process.platform === \"win32\")\n return await portableIdentity(process.platform, pid, token, executeCommand, (selected) =>\n guardianProof(selected, undefined, expectedToken),\n );\n return undefined;\n } catch (error) {\n const code = (error as { code?: unknown }).code;\n if (\n (process.platform === \"linux\" && [\"ENOENT\", \"ESRCH\"].includes(String(code))) ||\n (process.platform === \"darwin\" &&\n code === 1 &&\n !(error as { stdout?: string }).stdout?.trim())\n )\n return undefined;\n throw error;\n }\n}\n\nexport class ProcessNotOwnedError extends Error {\n constructor(public readonly pid: number) {\n super(`Process ${pid} does not have a verified supervisor identity`);\n this.name = \"ProcessNotOwnedError\";\n }\n}\n\nexport async function owns(entry: { pid: number; start: string; token: string }): Promise<boolean> {\n const current = await identity(entry.pid, true, entry.token);\n return (\n current?.start === entry.start && current.token === entry.token && current.group === entry.pid\n );\n}\n"],"mappings":";;;;;;;AAOA,MAAM,UAAU,UAAU,QAAQ;AAClC,eAAsB,eAAe,SAAmC;CACtE,MAAM,EAAE,WAAW,MAAM,QAAQ,QAAQ,MAAM,QAAQ,MAAM;EAC3D,KAAK;GAAE,GAAG,QAAQ;GAAK,GAAG,QAAQ;EAAI;EACtC,SAAS;EACT,WAAW;EACX,aAAa;CACf,CAAC;CACD,OAAO;AACT;AACA,MAAa,YAAY;AAGzB,SAAS,WAAW,MAAwB;CAC1C,OAAO,KACJ,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC,CAChC,KAAK,CAAC,CACN,MAAM,KAAK;AAChB;;;;;;AAOA,SAAS,QAAQ,QAA2B;CAC1C,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,SAAS,OAAO,MAAM,EAAE,KAAK,OAAO,QAAQ;AAChE;;AAGA,eAAsB,SACpB,KACA,QAAQ,OACR,eAC+B;CAC/B,IAAI,CAAC,OAAO,cAAc,GAAG,KAAK,OAAO,GAAG,OAAO;CACnD,IAAI;EACF,IAAI,QAAQ,aAAa,SAAS;GAEhC,MAAM,SAAS,WAAW,MADP,SAAS,SAAS,IAAI,QAAQ,MAAM,CACzB;GAC9B,MAAM,QAAQ,OAAO;GACrB,MAAM,QAAQ,OAAO;GACrB,IAAI,CAAC,SAAS,CAAC,SAAS,QAAQ,MAAM,GAAG,OAAO;GAChD,MAAM,QAAQ,MAAM,SAAS,mCAAmC,MAAM,EAAC,CAAE,KAAK;GAC9E,MAAM,SAAS,MAAM,SAAS,SAAS,IAAI,UAAU,MAAM;GAC3D,IAAI,cAAc;GAClB,IAAI,OACF,IAAI;IACF,cAAc,MAAM,SAAS,SAAS,IAAI,WAAW,MAAM;GAC7D,SAAS,OAAO;IAId,IAAK,MAA6B,SAAS,UAAU,MAAM;IAC3D,MAAM,MAAM,WAAW,MAAM,SAAS,SAAS,IAAI,QAAQ,MAAM,CAAC;IAClE,IAAI,IAAI,QAAQ,SAAS,QAAQ,GAAG,GAAG,OAAO;IAC9C,MAAM;GACR;GAIF,MAAM,QAAQ,WAAW,MAAM,SAAS,SAAS,IAAI,QAAQ,MAAM,CAAC;GACpE,IAAI,MAAM,QAAQ,SAAS,MAAM,OAAO,OAAO,MAAM,QAAQ,KAAK,GAAG,OAAO;GAC5E,OAAO;IACL,OAAO,GAAG,KAAK,GAAG;IAClB,OAAO,OAAO,OAAO,EAAE;IACvB,OAAO,MAAM,MAAM;IACnB,KAAK,OAAO,mBAAmB,KAAK,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI;IACzD,OAAO,YACJ,MAAM,IAAI,CAAC,CACX,MAAM,UAAU,MAAM,WAAW,GAAG,UAAU,EAAE,CAAC,CAAC,EACjD,MAAM,EAAoB;GAChC;EACF;EACA,IAAI,QAAQ,aAAa,YAAY,QAAQ,aAAa,SACxD,OAAO,MAAM,iBAAiB,QAAQ,UAAU,KAAK,OAAO,iBAAiB,aAC3E,cAAc,UAAU,QAAW,aAAa,CAClD;EACF;CACF,SAAS,OAAO;EACd,MAAM,OAAQ,MAA6B;EAC3C,IACG,QAAQ,aAAa,WAAW,CAAC,UAAU,OAAO,CAAC,CAAC,SAAS,OAAO,IAAI,CAAC,KACzE,QAAQ,aAAa,YACpB,SAAS,KACT,CAAE,MAA8B,QAAQ,KAAK,GAE/C,OAAO;EACT,MAAM;CACR;AACF;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAClB;CAA5B,YAAY,AAAgB,KAAa;EACvC,MAAM,WAAW,IAAI,8CAA8C;EADzC;EAE1B,KAAK,OAAO;CACd;AACF;AAEA,eAAsB,KAAK,OAAwE;CACjG,MAAM,UAAU,MAAM,SAAS,MAAM,KAAK,MAAM,MAAM,KAAK;CAC3D,OACE,SAAS,UAAU,MAAM,SAAS,QAAQ,UAAU,MAAM,SAAS,QAAQ,UAAU,MAAM;AAE/F"}
|
package/esm/index.d.mts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ProcessExit, ProcessFilter, ProcessHandle, ProcessInfo, ProcessLimits, ProcessSupervisor, PtyHandle, RecoveryResult, SpawnOptions, SupervisorOptions } from "./types.mjs";
|
|
2
|
+
import { supervisor } from "./supervisor.mjs";
|
|
3
|
+
import { UnsupportedProcessPlatformError } from "./platform.mjs";
|
|
4
|
+
import { ProcessNotOwnedError } from "./identity.mjs";
|
|
5
|
+
export { type ProcessExit, type ProcessFilter, type ProcessHandle, type ProcessInfo, type ProcessLimits, ProcessNotOwnedError, type ProcessSupervisor, type PtyHandle, type RecoveryResult, type SpawnOptions, type SupervisorOptions, UnsupportedProcessPlatformError, supervisor };
|
package/esm/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.d.mts","names":[],"sources":["../../../../../../../packages/process/src/platform.ts"],"mappings":";qBAgBa,wCAAwC;WACvB;EAA5B,YAA4B"}
|