@descryy/runtime-controller 0.0.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.
Files changed (61) hide show
  1. package/dist/capability-registry.d.ts +24 -0
  2. package/dist/capability-registry.d.ts.map +1 -0
  3. package/dist/capability-registry.js +42 -0
  4. package/dist/capability-registry.js.map +1 -0
  5. package/dist/collector-version.d.ts +10 -0
  6. package/dist/collector-version.d.ts.map +1 -0
  7. package/dist/collector-version.js +12 -0
  8. package/dist/collector-version.js.map +1 -0
  9. package/dist/container-sandbox.d.ts +188 -0
  10. package/dist/container-sandbox.d.ts.map +1 -0
  11. package/dist/container-sandbox.js +233 -0
  12. package/dist/container-sandbox.js.map +1 -0
  13. package/dist/controller.d.ts +162 -0
  14. package/dist/controller.d.ts.map +1 -0
  15. package/dist/controller.js +433 -0
  16. package/dist/controller.js.map +1 -0
  17. package/dist/dependency-version-check.d.ts +90 -0
  18. package/dist/dependency-version-check.d.ts.map +1 -0
  19. package/dist/dependency-version-check.js +121 -0
  20. package/dist/dependency-version-check.js.map +1 -0
  21. package/dist/env.d.ts +16 -0
  22. package/dist/env.d.ts.map +1 -0
  23. package/dist/env.js +18 -0
  24. package/dist/env.js.map +1 -0
  25. package/dist/environment-metadata.d.ts +23 -0
  26. package/dist/environment-metadata.d.ts.map +1 -0
  27. package/dist/environment-metadata.js +80 -0
  28. package/dist/environment-metadata.js.map +1 -0
  29. package/dist/environment-version-check.d.ts +83 -0
  30. package/dist/environment-version-check.d.ts.map +1 -0
  31. package/dist/environment-version-check.js +218 -0
  32. package/dist/environment-version-check.js.map +1 -0
  33. package/dist/execution-safety.d.ts +154 -0
  34. package/dist/execution-safety.d.ts.map +1 -0
  35. package/dist/execution-safety.js +194 -0
  36. package/dist/execution-safety.js.map +1 -0
  37. package/dist/index.d.ts +35 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +14 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/orchestration.d.ts +70 -0
  42. package/dist/orchestration.d.ts.map +1 -0
  43. package/dist/orchestration.js +255 -0
  44. package/dist/orchestration.js.map +1 -0
  45. package/dist/process-collector.d.ts +34 -0
  46. package/dist/process-collector.d.ts.map +1 -0
  47. package/dist/process-collector.js +148 -0
  48. package/dist/process-collector.js.map +1 -0
  49. package/dist/process-manager.d.ts +133 -0
  50. package/dist/process-manager.d.ts.map +1 -0
  51. package/dist/process-manager.js +333 -0
  52. package/dist/process-manager.js.map +1 -0
  53. package/dist/readiness.d.ts +120 -0
  54. package/dist/readiness.d.ts.map +1 -0
  55. package/dist/readiness.js +179 -0
  56. package/dist/readiness.js.map +1 -0
  57. package/dist/sandbox.d.ts +139 -0
  58. package/dist/sandbox.d.ts.map +1 -0
  59. package/dist/sandbox.js +271 -0
  60. package/dist/sandbox.js.map +1 -0
  61. package/package.json +29 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Readiness (plan §6): "Readiness must not equal 'process exists.'" —
3
+ * supported strategies are HTTP health check, TCP/port check, a known log
4
+ * pattern, a real subprocess command, or a caller-supplied hook (which
5
+ * covers the plan's "framework readiness hook" and "user-defined readiness"
6
+ * — two names for the same shape, a caller-provided async predicate,
7
+ * consolidated into one mechanism rather than two near-identical ones).
8
+ *
9
+ * **`command` is not `custom-hook` (RT-193).** An earlier version of this
10
+ * comment folded the plan's "command check" into `custom-hook` too, on the
11
+ * reasoning that both are "a caller-provided check." That collapsed a real
12
+ * distinction: `custom-hook` runs a caller's own in-process predicate,
13
+ * while `command` runs an actual subprocess and reads its actual exit
14
+ * code — the same "declared, not inferred" shape `ServiceConfiguration`'s
15
+ * own `command` field already uses, one layer over. A caller with a real
16
+ * CLI health check (`pg_isready`, a framework's own readiness script) had
17
+ * no way to express that without either wrapping it in a hand-written
18
+ * `custom-hook` predicate that shells out itself, or reimplementing the
19
+ * subprocess plumbing per caller — `command` is that plumbing, built once.
20
+ *
21
+ * The plan requires recording which mechanism succeeded — `ReadinessResult`
22
+ * makes that a field, not something inferred after the fact.
23
+ *
24
+ * **What `ready: true` does and does not claim (RT-024).** All four
25
+ * mechanisms answer one question — is something listening / responding
26
+ * where expected — and none answer whether it is the process *this*
27
+ * `spawnProcess` call produced. A stale listener from an unrelated, already-
28
+ * dead run satisfies `http`/`tcp-port` identically to a correct one; nothing
29
+ * here can tell them apart. `log-pattern` only carries the stronger,
30
+ * this-run-specific guarantee when its `read` is wired to *this* execution's
31
+ * own `ManagedProcess.readOutput()` — the type does not enforce that wiring,
32
+ * it is a caller discipline. `custom-hook` inherits whatever the caller's
33
+ * predicate actually checks.
34
+ *
35
+ * Callers that bind a fixed, guessable host:port are exposed to this gap;
36
+ * an ephemeral port closes it by construction (nothing else can already be
37
+ * bound there) rather than by detection, and is the default recommendation
38
+ * over a startup-nonce scheme, which only earns its extra machinery when a
39
+ * fixed port is genuinely unavoidable (a contract with an external tool).
40
+ * See `descry-runtime`'s `DECISIONS.md` RT-024 for how this was found —
41
+ * a real 30-second collector timeout that looked like a Playwright/DOM bug
42
+ * and was actually a correct wait against an orphaned process from a
43
+ * different, already-dead session.
44
+ *
45
+ * **A single probe attempt cannot outlive `timeoutMs` (RT-030).** `http`
46
+ * and `tcp-port` each bind their I/O to the *remaining* time until
47
+ * `deadline`, not a fresh per-attempt budget — a target that accepts a
48
+ * connection and never responds no longer lets one probe attempt run past
49
+ * the caller's own deadline. Measured, not theoretical: an unbound `fetch`
50
+ * here let `awaitReadiness` run past its own `timeoutMs` (>8s observed
51
+ * against a 3s budget). `command` honors the same discipline, the same
52
+ * way: `execFile`'s own `timeout` option is set to the remaining time until
53
+ * `deadline`, so a command that hangs instead of exiting is killed rather
54
+ * than left to run past the caller's own budget — it owns a real OS
55
+ * resource (a child process) exactly like the http/tcp-port sockets do, so
56
+ * it gets the same treatment. `custom-hook` is not wrapped the same way — a
57
+ * caller-supplied predicate owns its own timeout behavior, same as every
58
+ * other `Collector`-shaped callback contract in this repo (`collector.ts`'s
59
+ * own docs put "must resolve, not hang" on the implementer); imposing a
60
+ * surprise external cancellation on arbitrary caller code would be a
61
+ * different, larger decision than fixing the mechanisms that own a real OS
62
+ * resource this module can actually clean up.
63
+ */
64
+ import { createConnection } from "node:net";
65
+ import { execFile } from "node:child_process";
66
+ export const READINESS_MECHANISMS = ["http", "tcp-port", "log-pattern", "command", "custom-hook"];
67
+ const DEFAULT_POLL_INTERVAL_MS = 200;
68
+ export async function awaitReadiness(checks, options) {
69
+ const startedAt = Date.now();
70
+ const deadline = startedAt + options.timeoutMs;
71
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
72
+ if (checks.length === 0) {
73
+ return { ready: false, mechanism: null, elapsedMs: 0, reason: "no readiness checks configured" };
74
+ }
75
+ do {
76
+ for (const check of checks) {
77
+ if (await probe(check, deadline)) {
78
+ return { ready: true, mechanism: check.kind, elapsedMs: Date.now() - startedAt, reason: null };
79
+ }
80
+ }
81
+ await sleep(pollIntervalMs);
82
+ } while (Date.now() < deadline);
83
+ // Names what was actually probed, not just that nothing answered.
84
+ //
85
+ // This is the only cheap thing available against the ephemeral-port race
86
+ // in `allocateEphemeralPort` (bind 0, read, close, spawn): if another
87
+ // process takes the port in the window between release and the child's
88
+ // own bind, the child never comes up and the *only* symptom is this
89
+ // result. "No readiness mechanism succeeded" sends the reader to look at
90
+ // the application; "nothing answered http://127.0.0.1:41237/health"
91
+ // sends them to look at the port, which is where the problem is. The race
92
+ // itself cannot be closed without binding the real service directly to
93
+ // the probe socket, which the child-process model does not support — so
94
+ // making its one symptom legible is the honest remainder.
95
+ return {
96
+ ready: false,
97
+ mechanism: null,
98
+ elapsedMs: Date.now() - startedAt,
99
+ // "timeout" stays in the wording deliberately: the first draft of this
100
+ // message replaced it with "within 1500ms", which reads fine and drops
101
+ // the one word a caller can match on. §41's hung-process test does
102
+ // exactly that, and caught it.
103
+ reason: `no readiness mechanism succeeded within the ${options.timeoutMs}ms timeout — probed ${checks.map(describeCheck).join(", ")}`,
104
+ };
105
+ }
106
+ function describeCheck(check) {
107
+ switch (check.kind) {
108
+ case "http":
109
+ return check.url;
110
+ case "tcp-port":
111
+ return `tcp ${check.host}:${check.port}`;
112
+ case "log-pattern":
113
+ return `log pattern ${String(check.pattern)}`;
114
+ case "command":
115
+ return `command ${[check.command, ...(check.args ?? [])].join(" ")}`;
116
+ case "custom-hook":
117
+ return "custom hook";
118
+ }
119
+ }
120
+ async function probe(check, deadline) {
121
+ switch (check.kind) {
122
+ case "http":
123
+ return probeHttp(check, deadline);
124
+ case "tcp-port":
125
+ return probeTcpPort(check, deadline);
126
+ case "log-pattern":
127
+ return check.pattern.test(check.read());
128
+ case "command":
129
+ return probeCommand(check, deadline);
130
+ case "custom-hook":
131
+ return check.check();
132
+ }
133
+ }
134
+ async function probeHttp(check, deadline) {
135
+ try {
136
+ const response = await fetch(check.url, { signal: AbortSignal.timeout(Math.max(0, deadline - Date.now())) });
137
+ return response.status === (check.expectedStatus ?? 200);
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ }
143
+ function probeTcpPort(check, deadline) {
144
+ return new Promise((resolve) => {
145
+ const socket = createConnection({ host: check.host, port: check.port });
146
+ socket.setTimeout(Math.max(0, deadline - Date.now()));
147
+ const finish = (ok) => {
148
+ socket.destroy();
149
+ resolve(ok);
150
+ };
151
+ socket.once("connect", () => finish(true));
152
+ socket.once("error", () => finish(false));
153
+ socket.once("timeout", () => finish(false));
154
+ });
155
+ }
156
+ /**
157
+ * Ready when `check.command` exits 0 within the remaining time until
158
+ * `deadline` (RT-030 — see this module's own header). A non-zero exit, a
159
+ * spawn failure (e.g. command not found), and a timeout kill are all "not
160
+ * ready yet" here — `awaitReadiness`'s polling loop retries on the next
161
+ * `pollIntervalMs` tick regardless of which one happened, the same as every
162
+ * other mechanism's `false`.
163
+ */
164
+ function probeCommand(check, deadline) {
165
+ return new Promise((resolve) => {
166
+ const remainingMs = Math.max(0, deadline - Date.now());
167
+ if (remainingMs === 0) {
168
+ resolve(false);
169
+ return;
170
+ }
171
+ execFile(check.command, check.args ?? [], { cwd: check.cwd, timeout: remainingMs }, (error) => {
172
+ resolve(error === null);
173
+ });
174
+ });
175
+ }
176
+ function sleep(ms) {
177
+ return new Promise((resolve) => setTimeout(resolve, ms));
178
+ }
179
+ //# sourceMappingURL=readiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.js","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * §21's execution boundary, real isolation half (the sandbox-isolation
3
+ * lane, superseding the "sandbox platform is a separate architectural
4
+ * decision, not built here" deferral). RT-070 and RT-193 gave this runtime
5
+ * a *named* boundary (trusted-local, resource limits, privilege reporting)
6
+ * but not an *isolating* one -- nothing stopped a spawned process from
7
+ * reading any file the invoking user could read, or reaching any host the
8
+ * invoking user's network could reach. This module closes that gap for
9
+ * real, on Linux, for processes this runtime SPAWNS.
10
+ *
11
+ * **The architectural constraint this module does not try to solve
12
+ * around:** isolation can only wrap a process Descry itself launches.
13
+ * Attach-mode (`attach-to-running-process.ts`, `attach-to-running-node-
14
+ * process.ts`, `attach-to-running-jvm-process.ts`) reaches a process that
15
+ * was already running, unconfined, before Descry touched it -- there is no
16
+ * honest way to retroactively contain it (ptrace-based interception was
17
+ * investigated and rejected elsewhere in this repo as "not honestly
18
+ * generalizable"). Spawn-mode gets real isolation via this module.
19
+ * Attach-mode stays permanently and explicitly unsandboxed --
20
+ * `SANDBOX_BOUNDARY_DISCLOSURE` in `execution-safety.ts` says so in-product,
21
+ * and this module does not (and structurally cannot) change that.
22
+ *
23
+ * **Mechanism: bubblewrap (bwrap), not raw `unshare`/`clone`.** bwrap is
24
+ * the unprivileged sandboxing helper behind Flatpak and used by many
25
+ * developer tools directly -- a small (tens of KB), auditable, actively
26
+ * maintained binary, not a bespoke native module this repo would need to
27
+ * build and ship per-platform. It runs setuid-free on any kernel with
28
+ * unprivileged user namespaces enabled (checked below, not assumed) and
29
+ * gives one process a new mount namespace (real filesystem visibility
30
+ * control) and network namespace (real network denial) in one exec,
31
+ * composing cleanly with `applyResourceLimits`'s `prlimit` wrap and with
32
+ * this package's existing process-group kill mechanism (`detached: true` +
33
+ * `process.kill(-pid, …)`) -- verified directly: bwrap does not create a
34
+ * new process group unless asked to, so the group-kill in
35
+ * `process-manager.ts` reaches it and everything it started, unchanged.
36
+ * The one dependency this buys: `bwrap` must be on `PATH`. Reported as a
37
+ * capability, exactly like `prlimit` -- refused, never silently
38
+ * unconstrained, when requested and absent.
39
+ *
40
+ * **What this module does NOT attempt, and where that gap actually closes:**
41
+ * a *native* macOS or Windows sandbox for this mechanism (`sandbox-exec`/
42
+ * Seatbelt -- deprecated public API; the Endpoint Security Framework --
43
+ * needs a special Apple entitlement this project does not have; Windows Job
44
+ * Objects + restricted tokens/AppContainer + WFP network enforcement -- a
45
+ * second and third bespoke native sandbox to build and maintain). That is
46
+ * not "researched but not built here, someday" -- it is an explicit
47
+ * architecture decision NOT to build native sandboxes for those two
48
+ * platforms at all. `../container-sandbox.ts` closes the gap instead, by
49
+ * routing macOS and Windows through a real Docker container that reuses
50
+ * THIS module's own proven Linux mechanism underneath (Docker Desktop's
51
+ * Linux VM on macOS, WSL2/Docker Desktop's Windows backend on Windows) --
52
+ * see that module's own doc comment for exactly what is real-tested there
53
+ * versus unverified on real macOS/Windows hardware. `filesystemIsolationCapability`/
54
+ * `networkIsolationCapability` below still report `unavailable` with a
55
+ * reason on non-Linux platforms -- this module never silently claims to
56
+ * cover them; `container-sandbox.ts` is the module a non-Linux caller
57
+ * actually wants.
58
+ */
59
+ import type { CapabilityStatus, FilesystemPolicy, NetworkPolicy } from "@descryy/runtime-contracts";
60
+ /**
61
+ * Real capability check, matching `resourceLimitCapability`'s own shape and
62
+ * precedent exactly: existence on `PATH` first (fast, matches
63
+ * `prlimit --version`'s pattern), then the one Linux-specific precondition
64
+ * that existence alone doesn't prove -- unprivileged user namespaces
65
+ * actually being usable, not just bwrap being installed.
66
+ */
67
+ export declare function filesystemIsolationCapability(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): CapabilityStatus;
68
+ /**
69
+ * The same underlying mechanism as `filesystemIsolationCapability` --
70
+ * bwrap building one namespace set covers both a restricted mount view and
71
+ * a network namespace in the same exec -- so this delegates rather than
72
+ * duplicating the check. Kept as its own named function because the two
73
+ * policies are declared, refused, and reasoned about independently
74
+ * (`NetworkPolicy` only supports one shape; see `unsupportedNetworkPolicyReason`),
75
+ * even though today they share one mechanism underneath.
76
+ */
77
+ export declare function networkIsolationCapability(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): CapabilityStatus;
78
+ /**
79
+ * `NetworkPolicy`'s general shape (arbitrary allow/deny host lists) is not
80
+ * what this iteration's mechanism enforces -- only full denial. Returns the
81
+ * reason a given policy cannot be applied, or `null` if it is the one
82
+ * shape that can. Checked independently of `networkIsolationCapability`:
83
+ * a policy can be shape-unsupported on a platform where the mechanism
84
+ * itself is otherwise available, and the two refusals are different
85
+ * claims ("nothing here can enforce any network policy" vs. "this specific
86
+ * policy asks for something the real mechanism doesn't do yet").
87
+ */
88
+ export declare function unsupportedNetworkPolicyReason(policy: NetworkPolicy): string | null;
89
+ /**
90
+ * Resolves the real, symlink-followed directory containing the executable
91
+ * `command` would run as -- the one piece of host filesystem a sandboxed
92
+ * process needs beyond the standard OS directories and whatever the caller
93
+ * declared, and the one this function computes generically rather than by
94
+ * naming a language: an interpreter installed outside `/usr` (nvm, rbenv,
95
+ * pyenv, sdkman -- all common, all under a user's home directory) would
96
+ * otherwise be invisible inside the sandbox no matter which language it
97
+ * is, which is exactly the kind of per-language special case §IR-boundary
98
+ * rules out. Returns `null` when `command` cannot be resolved (already
99
+ * absolute-looking and missing, or not found on `PATH`) -- the caller
100
+ * proceeds without an extra bind in that case; the exec itself will fail
101
+ * inside the sandbox the same honest way it would outside one.
102
+ */
103
+ export declare function resolveExecutableDirectory(command: string, cwd: string, env: NodeJS.ProcessEnv): string | null;
104
+ export interface SandboxOptions {
105
+ /** The original, un-resource-limit-wrapped command (e.g. `"node"`) -- used only to resolve which extra directory the interpreter itself needs bound in. */
106
+ readonly interpreterCommand: string;
107
+ /** The command to actually exec inside the sandbox -- already passed through `applyResourceLimits`, so this may be `"prlimit"` with the real command in `args`. */
108
+ readonly command: string;
109
+ readonly args: readonly string[];
110
+ readonly cwd: string;
111
+ readonly filesystemPolicy?: FilesystemPolicy;
112
+ readonly networkPolicy?: NetworkPolicy;
113
+ readonly env?: NodeJS.ProcessEnv;
114
+ readonly platform?: NodeJS.Platform;
115
+ }
116
+ /**
117
+ * Wraps `command`/`args` with `bwrap` so the OS enforces `filesystemPolicy`
118
+ * and/or `networkPolicy`. Neither declared returns `command`/`args`
119
+ * unchanged -- every execution before this lane keeps behaving exactly as
120
+ * before unless it opts in, the same non-regression contract
121
+ * `applyResourceLimits` already established for resource limits.
122
+ *
123
+ * **Throws rather than silently spawning unconstrained** -- same
124
+ * `tokenizeCommand`/`applyResourceLimits` precedent -- when a policy is
125
+ * requested and the real mechanism cannot back it, whether because the
126
+ * platform has no mechanism at all or because the declared shape is one
127
+ * this mechanism doesn't implement (`unsupportedNetworkPolicyReason`).
128
+ *
129
+ * Composes with `applyResourceLimits`: this function wraps *outside* it
130
+ * (`bwrap … -- prlimit … -- realCommand`), because the mount namespace must
131
+ * exist before `prlimit` or the real command run inside it, and `prlimit`
132
+ * itself (in `/usr/bin`) is reachable through the same base OS binding
133
+ * every sandboxed process gets.
134
+ */
135
+ export declare function applySandbox(options: SandboxOptions): {
136
+ readonly command: string;
137
+ readonly args: readonly string[];
138
+ };
139
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AA+BpG;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,gBAAgB,CAoBlJ;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,gBAAgB,CAE/I;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAOnF;AAOD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,IAAI,CAwB9G;AAED,MAAM,WAAW,cAAc;IAC7B,2JAA2J;IAC3J,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,mKAAmK;IACnK,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAmFpH"}
@@ -0,0 +1,271 @@
1
+ /**
2
+ * §21's execution boundary, real isolation half (the sandbox-isolation
3
+ * lane, superseding the "sandbox platform is a separate architectural
4
+ * decision, not built here" deferral). RT-070 and RT-193 gave this runtime
5
+ * a *named* boundary (trusted-local, resource limits, privilege reporting)
6
+ * but not an *isolating* one -- nothing stopped a spawned process from
7
+ * reading any file the invoking user could read, or reaching any host the
8
+ * invoking user's network could reach. This module closes that gap for
9
+ * real, on Linux, for processes this runtime SPAWNS.
10
+ *
11
+ * **The architectural constraint this module does not try to solve
12
+ * around:** isolation can only wrap a process Descry itself launches.
13
+ * Attach-mode (`attach-to-running-process.ts`, `attach-to-running-node-
14
+ * process.ts`, `attach-to-running-jvm-process.ts`) reaches a process that
15
+ * was already running, unconfined, before Descry touched it -- there is no
16
+ * honest way to retroactively contain it (ptrace-based interception was
17
+ * investigated and rejected elsewhere in this repo as "not honestly
18
+ * generalizable"). Spawn-mode gets real isolation via this module.
19
+ * Attach-mode stays permanently and explicitly unsandboxed --
20
+ * `SANDBOX_BOUNDARY_DISCLOSURE` in `execution-safety.ts` says so in-product,
21
+ * and this module does not (and structurally cannot) change that.
22
+ *
23
+ * **Mechanism: bubblewrap (bwrap), not raw `unshare`/`clone`.** bwrap is
24
+ * the unprivileged sandboxing helper behind Flatpak and used by many
25
+ * developer tools directly -- a small (tens of KB), auditable, actively
26
+ * maintained binary, not a bespoke native module this repo would need to
27
+ * build and ship per-platform. It runs setuid-free on any kernel with
28
+ * unprivileged user namespaces enabled (checked below, not assumed) and
29
+ * gives one process a new mount namespace (real filesystem visibility
30
+ * control) and network namespace (real network denial) in one exec,
31
+ * composing cleanly with `applyResourceLimits`'s `prlimit` wrap and with
32
+ * this package's existing process-group kill mechanism (`detached: true` +
33
+ * `process.kill(-pid, …)`) -- verified directly: bwrap does not create a
34
+ * new process group unless asked to, so the group-kill in
35
+ * `process-manager.ts` reaches it and everything it started, unchanged.
36
+ * The one dependency this buys: `bwrap` must be on `PATH`. Reported as a
37
+ * capability, exactly like `prlimit` -- refused, never silently
38
+ * unconstrained, when requested and absent.
39
+ *
40
+ * **What this module does NOT attempt, and where that gap actually closes:**
41
+ * a *native* macOS or Windows sandbox for this mechanism (`sandbox-exec`/
42
+ * Seatbelt -- deprecated public API; the Endpoint Security Framework --
43
+ * needs a special Apple entitlement this project does not have; Windows Job
44
+ * Objects + restricted tokens/AppContainer + WFP network enforcement -- a
45
+ * second and third bespoke native sandbox to build and maintain). That is
46
+ * not "researched but not built here, someday" -- it is an explicit
47
+ * architecture decision NOT to build native sandboxes for those two
48
+ * platforms at all. `../container-sandbox.ts` closes the gap instead, by
49
+ * routing macOS and Windows through a real Docker container that reuses
50
+ * THIS module's own proven Linux mechanism underneath (Docker Desktop's
51
+ * Linux VM on macOS, WSL2/Docker Desktop's Windows backend on Windows) --
52
+ * see that module's own doc comment for exactly what is real-tested there
53
+ * versus unverified on real macOS/Windows hardware. `filesystemIsolationCapability`/
54
+ * `networkIsolationCapability` below still report `unavailable` with a
55
+ * reason on non-Linux platforms -- this module never silently claims to
56
+ * cover them; `container-sandbox.ts` is the module a non-Linux caller
57
+ * actually wants.
58
+ */
59
+ import { execFileSync } from "node:child_process";
60
+ import { accessSync, constants as fsConstants, readFileSync, realpathSync } from "node:fs";
61
+ import { dirname, isAbsolute, join, relative } from "node:path";
62
+ /**
63
+ * Whether unprivileged user namespaces -- the kernel feature bwrap needs to
64
+ * build a mount/network namespace without running setuid-root -- are
65
+ * actually enabled on this host, not merely assumed from "the kernel
66
+ * supports namespaces in general." Two independent sysctls can each
67
+ * disable it; neither file existing at all is itself evidence of a
68
+ * restriction (most non-Debian-derived distros never gate this at all).
69
+ */
70
+ function unprivilegedUserNamespaceStatus() {
71
+ try {
72
+ const clone = readFileSync("/proc/sys/kernel/unprivileged_userns_clone", "utf8").trim();
73
+ if (clone === "0") {
74
+ return { ok: false, reason: "kernel.unprivileged_userns_clone=0 -- unprivileged user namespaces are disabled on this host" };
75
+ }
76
+ }
77
+ catch {
78
+ // Sysctl doesn't exist on this kernel -- not evidence of a restriction,
79
+ // only Debian/Ubuntu-derived kernels gate it through this specific file.
80
+ }
81
+ try {
82
+ const max = readFileSync("/proc/sys/user/max_user_namespaces", "utf8").trim();
83
+ if (max === "0") {
84
+ return { ok: false, reason: "user.max_user_namespaces=0 -- unprivileged user namespaces are disabled on this host" };
85
+ }
86
+ }
87
+ catch {
88
+ // Same reasoning.
89
+ }
90
+ return { ok: true, reason: null };
91
+ }
92
+ /**
93
+ * Real capability check, matching `resourceLimitCapability`'s own shape and
94
+ * precedent exactly: existence on `PATH` first (fast, matches
95
+ * `prlimit --version`'s pattern), then the one Linux-specific precondition
96
+ * that existence alone doesn't prove -- unprivileged user namespaces
97
+ * actually being usable, not just bwrap being installed.
98
+ */
99
+ export function filesystemIsolationCapability(env = process.env, platform = process.platform) {
100
+ if (platform !== "linux") {
101
+ return {
102
+ availability: "unavailable",
103
+ reason: `no filesystem isolation mechanism is wired up on "${platform}" for bwrap -- by design, not by omission (native sandbox-exec/Seatbelt and Job Objects/AppContainer were researched and rejected); see container-sandbox.ts, which routes "${platform}" through a real Docker container instead`,
104
+ };
105
+ }
106
+ try {
107
+ execFileSync("bwrap", ["--version"], { env, stdio: "ignore" });
108
+ }
109
+ catch {
110
+ return {
111
+ availability: "unavailable",
112
+ reason: "bubblewrap (bwrap) is not on PATH -- filesystem isolation cannot be enforced without it",
113
+ };
114
+ }
115
+ const userns = unprivilegedUserNamespaceStatus();
116
+ if (!userns.ok) {
117
+ return { availability: "unavailable", reason: `bubblewrap is installed but unprivileged user namespaces are unavailable: ${userns.reason}` };
118
+ }
119
+ return { availability: "available", reason: null };
120
+ }
121
+ /**
122
+ * The same underlying mechanism as `filesystemIsolationCapability` --
123
+ * bwrap building one namespace set covers both a restricted mount view and
124
+ * a network namespace in the same exec -- so this delegates rather than
125
+ * duplicating the check. Kept as its own named function because the two
126
+ * policies are declared, refused, and reasoned about independently
127
+ * (`NetworkPolicy` only supports one shape; see `unsupportedNetworkPolicyReason`),
128
+ * even though today they share one mechanism underneath.
129
+ */
130
+ export function networkIsolationCapability(env = process.env, platform = process.platform) {
131
+ return filesystemIsolationCapability(env, platform);
132
+ }
133
+ /**
134
+ * `NetworkPolicy`'s general shape (arbitrary allow/deny host lists) is not
135
+ * what this iteration's mechanism enforces -- only full denial. Returns the
136
+ * reason a given policy cannot be applied, or `null` if it is the one
137
+ * shape that can. Checked independently of `networkIsolationCapability`:
138
+ * a policy can be shape-unsupported on a platform where the mechanism
139
+ * itself is otherwise available, and the two refusals are different
140
+ * claims ("nothing here can enforce any network policy" vs. "this specific
141
+ * policy asks for something the real mechanism doesn't do yet").
142
+ */
143
+ export function unsupportedNetworkPolicyReason(policy) {
144
+ if (policy.mode === "allow" && policy.hosts.length === 0)
145
+ return null;
146
+ return ('only full network denial ({ mode: "allow", hosts: [] }) is enforced -- selective allow- or deny-listing of ' +
147
+ "specific hosts needs DNS interception and IP filtering inside the sandbox's network namespace (a veth pair, " +
148
+ "NAT, iptables/nftables rules), not built in this iteration");
149
+ }
150
+ function isWithin(root, target) {
151
+ const rel = relative(root, target);
152
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
153
+ }
154
+ /**
155
+ * Resolves the real, symlink-followed directory containing the executable
156
+ * `command` would run as -- the one piece of host filesystem a sandboxed
157
+ * process needs beyond the standard OS directories and whatever the caller
158
+ * declared, and the one this function computes generically rather than by
159
+ * naming a language: an interpreter installed outside `/usr` (nvm, rbenv,
160
+ * pyenv, sdkman -- all common, all under a user's home directory) would
161
+ * otherwise be invisible inside the sandbox no matter which language it
162
+ * is, which is exactly the kind of per-language special case §IR-boundary
163
+ * rules out. Returns `null` when `command` cannot be resolved (already
164
+ * absolute-looking and missing, or not found on `PATH`) -- the caller
165
+ * proceeds without an extra bind in that case; the exec itself will fail
166
+ * inside the sandbox the same honest way it would outside one.
167
+ */
168
+ export function resolveExecutableDirectory(command, cwd, env) {
169
+ let candidate = null;
170
+ if (command.includes("/")) {
171
+ candidate = isAbsolute(command) ? command : join(cwd, command);
172
+ }
173
+ else {
174
+ const pathVar = env.PATH ?? "";
175
+ for (const dir of pathVar.split(":")) {
176
+ if (dir === "")
177
+ continue;
178
+ const full = join(dir, command);
179
+ try {
180
+ accessSync(full, fsConstants.X_OK);
181
+ candidate = full;
182
+ break;
183
+ }
184
+ catch {
185
+ continue;
186
+ }
187
+ }
188
+ }
189
+ if (candidate === null)
190
+ return null;
191
+ try {
192
+ return dirname(realpathSync(candidate));
193
+ }
194
+ catch {
195
+ return null;
196
+ }
197
+ }
198
+ /**
199
+ * Wraps `command`/`args` with `bwrap` so the OS enforces `filesystemPolicy`
200
+ * and/or `networkPolicy`. Neither declared returns `command`/`args`
201
+ * unchanged -- every execution before this lane keeps behaving exactly as
202
+ * before unless it opts in, the same non-regression contract
203
+ * `applyResourceLimits` already established for resource limits.
204
+ *
205
+ * **Throws rather than silently spawning unconstrained** -- same
206
+ * `tokenizeCommand`/`applyResourceLimits` precedent -- when a policy is
207
+ * requested and the real mechanism cannot back it, whether because the
208
+ * platform has no mechanism at all or because the declared shape is one
209
+ * this mechanism doesn't implement (`unsupportedNetworkPolicyReason`).
210
+ *
211
+ * Composes with `applyResourceLimits`: this function wraps *outside* it
212
+ * (`bwrap … -- prlimit … -- realCommand`), because the mount namespace must
213
+ * exist before `prlimit` or the real command run inside it, and `prlimit`
214
+ * itself (in `/usr/bin`) is reachable through the same base OS binding
215
+ * every sandboxed process gets.
216
+ */
217
+ export function applySandbox(options) {
218
+ const { filesystemPolicy, networkPolicy } = options;
219
+ if (filesystemPolicy === undefined && networkPolicy === undefined) {
220
+ return { command: options.command, args: options.args };
221
+ }
222
+ const env = options.env ?? process.env;
223
+ const platform = options.platform ?? process.platform;
224
+ if (networkPolicy !== undefined) {
225
+ const shapeReason = unsupportedNetworkPolicyReason(networkPolicy);
226
+ if (shapeReason !== null) {
227
+ throw new Error(`networkPolicy was configured but cannot be enforced: ${shapeReason}`);
228
+ }
229
+ const capability = networkIsolationCapability(env, platform);
230
+ if (capability.availability !== "available") {
231
+ throw new Error(`networkPolicy was configured but cannot be enforced on this platform: ${capability.reason}`);
232
+ }
233
+ }
234
+ if (filesystemPolicy !== undefined) {
235
+ const capability = filesystemIsolationCapability(env, platform);
236
+ if (capability.availability !== "available") {
237
+ throw new Error(`filesystemPolicy was configured but cannot be enforced on this platform: ${capability.reason}`);
238
+ }
239
+ }
240
+ const bwrapArgs = [];
241
+ if (filesystemPolicy !== undefined) {
242
+ bwrapArgs.push("--ro-bind", "/usr", "/usr", "--symlink", "/usr/bin", "/bin", "--symlink", "/usr/lib", "/lib", "--symlink", "/usr/lib64", "/lib64", "--proc", "/proc", "--dev", "/dev", "--tmpfs", "/tmp", "--bind", options.cwd, options.cwd);
243
+ const boundRoots = ["/usr", options.cwd, ...filesystemPolicy.allowedRoots];
244
+ for (const root of filesystemPolicy.allowedRoots) {
245
+ bwrapArgs.push("--bind", root, root);
246
+ }
247
+ const interpreterDir = resolveExecutableDirectory(options.interpreterCommand, options.cwd, env);
248
+ if (interpreterDir !== null && !boundRoots.some((root) => isWithin(root, interpreterDir))) {
249
+ bwrapArgs.push("--ro-bind", interpreterDir, interpreterDir);
250
+ }
251
+ }
252
+ else {
253
+ // No filesystem restriction declared -- mirror the host root exactly,
254
+ // matching FilesystemPolicy's own "absent means unconstrained" default.
255
+ // A mount namespace is still created here (bwrap always builds one, and
256
+ // --unshare-net below needs it), so /proc, /dev and /tmp are remounted
257
+ // explicitly rather than inherited: a plain `--bind / /` is not
258
+ // recursive and would otherwise leave them looking empty inside.
259
+ bwrapArgs.push("--bind", "/", "/", "--dev", "/dev", "--proc", "/proc", "--bind", "/tmp", "/tmp");
260
+ }
261
+ bwrapArgs.push("--chdir", options.cwd);
262
+ if (networkPolicy !== undefined) {
263
+ bwrapArgs.push("--unshare-net");
264
+ }
265
+ // Kills the sandboxed tree with the sandbox itself if bwrap's own parent
266
+ // (this Node process) dies unexpectedly -- a second, independent backstop
267
+ // alongside process-manager.ts's own group-kill, not a replacement for it.
268
+ bwrapArgs.push("--die-with-parent", "--", options.command, ...options.args);
269
+ return { command: "bwrap", args: bwrapArgs };
270
+ }
271
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@descryy/runtime-controller",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "description": "Runtime controller: process manager, readiness, timeout/cancellation, execution IDs, cleanup. Deliverable 2 of the runtime phase — depends on @descryy/runtime-contracts, builds no collectors.",
6
+ "license": "UNLICENSED",
7
+ "engines": {
8
+ "node": ">=22.5"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "publishConfig": {
20
+ "registry": "https://registry.npmjs.org",
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -b"
25
+ },
26
+ "dependencies": {
27
+ "@descryy/runtime-contracts": "0.0.0"
28
+ }
29
+ }