@descryy/runtime-controller 0.2.1 → 0.3.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/LICENSE +6 -0
  2. package/dist/capability-registry.d.ts +0 -15
  3. package/dist/capability-registry.d.ts.map +1 -1
  4. package/dist/capability-registry.js +9 -19
  5. package/dist/capability-registry.js.map +1 -1
  6. package/dist/collector-version.d.ts +1 -8
  7. package/dist/collector-version.d.ts.map +1 -1
  8. package/dist/collector-version.js +1 -8
  9. package/dist/collector-version.js.map +1 -1
  10. package/dist/container-sandbox.d.ts +66 -150
  11. package/dist/container-sandbox.d.ts.map +1 -1
  12. package/dist/container-sandbox.js +62 -143
  13. package/dist/container-sandbox.js.map +1 -1
  14. package/dist/controller.d.ts +49 -106
  15. package/dist/controller.d.ts.map +1 -1
  16. package/dist/controller.js +65 -123
  17. package/dist/controller.js.map +1 -1
  18. package/dist/dependency-version-check.d.ts +19 -73
  19. package/dist/dependency-version-check.d.ts.map +1 -1
  20. package/dist/dependency-version-check.js +18 -67
  21. package/dist/dependency-version-check.js.map +1 -1
  22. package/dist/env.d.ts +4 -10
  23. package/dist/env.d.ts.map +1 -1
  24. package/dist/env.js +4 -10
  25. package/dist/env.js.map +1 -1
  26. package/dist/environment-metadata.d.ts +9 -17
  27. package/dist/environment-metadata.d.ts.map +1 -1
  28. package/dist/environment-metadata.js +12 -29
  29. package/dist/environment-metadata.js.map +1 -1
  30. package/dist/environment-version-check.d.ts +22 -65
  31. package/dist/environment-version-check.d.ts.map +1 -1
  32. package/dist/environment-version-check.js +24 -66
  33. package/dist/environment-version-check.js.map +1 -1
  34. package/dist/execution-safety.d.ts +54 -112
  35. package/dist/execution-safety.d.ts.map +1 -1
  36. package/dist/execution-safety.js +48 -102
  37. package/dist/execution-safety.js.map +1 -1
  38. package/dist/index.d.ts +0 -10
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/orchestration.d.ts +24 -39
  42. package/dist/orchestration.d.ts.map +1 -1
  43. package/dist/orchestration.js +39 -75
  44. package/dist/orchestration.js.map +1 -1
  45. package/dist/process-collector.d.ts +8 -22
  46. package/dist/process-collector.d.ts.map +1 -1
  47. package/dist/process-collector.js +23 -55
  48. package/dist/process-collector.js.map +1 -1
  49. package/dist/process-manager.d.ts +45 -80
  50. package/dist/process-manager.d.ts.map +1 -1
  51. package/dist/process-manager.js +51 -101
  52. package/dist/process-manager.js.map +1 -1
  53. package/dist/readiness.d.ts +28 -70
  54. package/dist/readiness.d.ts.map +1 -1
  55. package/dist/readiness.js +73 -95
  56. package/dist/readiness.js.map +1 -1
  57. package/dist/sandbox.d.ts +60 -105
  58. package/dist/sandbox.d.ts.map +1 -1
  59. package/dist/sandbox.js +78 -121
  60. package/dist/sandbox.js.map +1 -1
  61. package/package.json +7 -2
package/LICENSE ADDED
@@ -0,0 +1,6 @@
1
+ Copyright (c) Descry
2
+
3
+ All rights reserved. This software is proprietary and confidential.
4
+ No license, express or implied, to reproduce, distribute, modify, or
5
+ create derivative works is granted except as explicitly agreed in
6
+ writing.
@@ -1,11 +1,3 @@
1
- /**
2
- * Capability registry (plan §16): assembles per-execution capabilities
3
- * from the collectors actually present, so `distributedTrace: unavailable`
4
- * is a first-class answer the AI planner can read rather than a silent
5
- * gap. Does not know how to *produce* a `CollectorCapabilities` value —
6
- * that's each collector's own `capabilities()` (Agents 2-4's concern) —
7
- * only how to assemble and query what's been registered.
8
- */
9
1
  import type { CapabilityStatus, CollectorCapabilities, EnvironmentTier, ExecutionCapabilities, FidelityLevel } from "@descryy/runtime-contracts";
10
2
  export interface RegisteredCollectorCapabilities {
11
3
  readonly collectorId: string;
@@ -13,12 +5,5 @@ export interface RegisteredCollectorCapabilities {
13
5
  }
14
6
  export declare function buildExecutionCapabilities(environmentTier: EnvironmentTier, fidelityLevel: FidelityLevel, collectors: readonly RegisteredCollectorCapabilities[]): ExecutionCapabilities;
15
7
  export type CollectorCapabilityKey = keyof CollectorCapabilities;
16
- /**
17
- * The best (most available) status for one capability across every
18
- * registered collector — "is this observable *anywhere* in this
19
- * execution," not per-collector. An execution with two network collectors
20
- * where one degraded and one is fully available should read as available,
21
- * not degraded.
22
- */
23
8
  export declare function bestCapabilityStatus(capabilities: ExecutionCapabilities, key: CollectorCapabilityKey): CapabilityStatus;
24
9
  //# sourceMappingURL=capability-registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"capability-registry.d.ts","sourceRoot":"","sources":["../src/capability-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAEV,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,aAAa,EACd,MAAM,4BAA4B,CAAC;AAEpC,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC;CAC9C;AAED,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,eAAe,EAChC,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,SAAS,+BAA+B,EAAE,GACrD,qBAAqB,CAEvB;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,qBAAqB,CAAC;AAajE;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,qBAAqB,EACnC,GAAG,EAAE,sBAAsB,GAC1B,gBAAgB,CAalB"}
1
+ {"version":3,"file":"capability-registry.d.ts","sourceRoot":"","sources":["../src/capability-registry.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,aAAa,EACd,MAAM,4BAA4B,CAAC;AAEpC,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC;CAC9C;AAED,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,eAAe,EAChC,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,SAAS,+BAA+B,EAAE,GACrD,qBAAqB,CAEvB;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,qBAAqB,CAAC;AAgBjE,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,qBAAqB,EACnC,GAAG,EAAE,sBAAsB,GAC1B,gBAAgB,CAWlB"}
@@ -1,11 +1,7 @@
1
- /**
2
- * Capability registry (plan §16): assembles per-execution capabilities
3
- * from the collectors actually present, so `distributedTrace: unavailable`
4
- * is a first-class answer the AI planner can read rather than a silent
5
- * gap. Does not know how to *produce* a `CollectorCapabilities` value —
6
- * that's each collector's own `capabilities()` (Agents 2-4's concern) —
7
- * only how to assemble and query what's been registered.
8
- */
1
+ // Capability registry (§16): assembles per-execution capabilities from the collectors
2
+ // actually present, so e.g. `distributedTrace: unavailable` is a first-class answer
3
+ // rather than a silent gap. Assembles and queries only — producing a
4
+ // `CollectorCapabilities` value is each collector's own `capabilities()`.
9
5
  export function buildExecutionCapabilities(environmentTier, fidelityLevel, collectors) {
10
6
  return { environmentTier, fidelityLevel, collectors };
11
7
  }
@@ -18,18 +14,12 @@ const NO_COLLECTOR_STATUS = {
18
14
  availability: "unavailable",
19
15
  reason: "no collector registered for this execution",
20
16
  };
21
- /**
22
- * The best (most available) status for one capability across every
23
- * registered collector — "is this observable *anywhere* in this
24
- * execution," not per-collector. An execution with two network collectors
25
- * where one degraded and one is fully available should read as available,
26
- * not degraded.
27
- */
17
+ // Best (most available) status for one capability across all registered collectors —
18
+ // "observable anywhere in this execution," not per-collector: two network collectors,
19
+ // one degraded one available, should read as available.
28
20
  export function bestCapabilityStatus(capabilities, key) {
29
- // Seeded from the first collector actually seen, not from
30
- // NO_COLLECTOR_STATUS -- seeding from the placeholder would make a
31
- // single collector's genuine "unavailable" tie the placeholder's rank
32
- // and lose its specific reason to the generic one.
21
+ // Seeded from the first collector seen, not NO_COLLECTOR_STATUS — seeding from the
22
+ // placeholder would let a real "unavailable" tie the placeholder and lose its reason.
33
23
  let best = null;
34
24
  for (const collector of capabilities.collectors) {
35
25
  const status = collector.capabilities[key];
@@ -1 +1 @@
1
- {"version":3,"file":"capability-registry.js","sourceRoot":"","sources":["../src/capability-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAgBH,MAAM,UAAU,0BAA0B,CACxC,eAAgC,EAChC,aAA4B,EAC5B,UAAsD;IAEtD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AACxD,CAAC;AAID,MAAM,iBAAiB,GAAqD;IAC1E,WAAW,EAAE,CAAC;IACd,QAAQ,EAAE,CAAC;IACX,SAAS,EAAE,CAAC;CACb,CAAC;AAEF,MAAM,mBAAmB,GAAqB;IAC5C,YAAY,EAAE,aAAa;IAC3B,MAAM,EAAE,4CAA4C;CACrD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,YAAmC,EACnC,GAA2B;IAE3B,0DAA0D;IAC1D,mEAAmE;IACnE,sEAAsE;IACtE,mDAAmD;IACnD,IAAI,IAAI,GAA4B,IAAI,CAAC;IACzC,KAAK,MAAM,SAAS,IAAI,YAAY,CAAC,UAAU,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,IAAI,KAAK,IAAI,IAAI,iBAAiB,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YACnG,IAAI,GAAG,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,IAAI,mBAAmB,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"capability-registry.js","sourceRoot":"","sources":["../src/capability-registry.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,oFAAoF;AACpF,qEAAqE;AACrE,0EAA0E;AAgB1E,MAAM,UAAU,0BAA0B,CACxC,eAAgC,EAChC,aAA4B,EAC5B,UAAsD;IAEtD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AACxD,CAAC;AAID,MAAM,iBAAiB,GAAqD;IAC1E,WAAW,EAAE,CAAC;IACd,QAAQ,EAAE,CAAC;IACX,SAAS,EAAE,CAAC;CACb,CAAC;AAEF,MAAM,mBAAmB,GAAqB;IAC5C,YAAY,EAAE,aAAa;IAC3B,MAAM,EAAE,4CAA4C;CACrD,CAAC;AAEF,qFAAqF;AACrF,sFAAsF;AACtF,wDAAwD;AACxD,MAAM,UAAU,oBAAoB,CAClC,YAAmC,EACnC,GAA2B;IAE3B,mFAAmF;IACnF,sFAAsF;IACtF,IAAI,IAAI,GAA4B,IAAI,CAAC;IACzC,KAAK,MAAM,SAAS,IAAI,YAAY,CAAC,UAAU,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,IAAI,KAAK,IAAI,IAAI,iBAAiB,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YACnG,IAAI,GAAG,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,IAAI,mBAAmB,CAAC;AACrC,CAAC"}
@@ -1,10 +1,3 @@
1
- /**
2
- * `Evidence.collectorVersion`'s source for `ProcessCollector`: this
3
- * package's own `package.json` "version", read from the artifact itself
4
- * rather than hand-duplicated into a second string that can drift from
5
- * what actually shipped. Same idea as `nodeVersion` elsewhere in this repo
6
- * (`process.version`, always known, never probed) applied to a package
7
- * instead of the Node runtime.
8
- */
1
+ /** `Evidence.collectorVersion` for `ProcessCollector`: read from package.json itself so it can't drift from what shipped. Same pattern as `nodeVersion` (`process.version`), applied to a package instead of the runtime. */
9
2
  export declare const COLLECTOR_VERSION: string;
10
3
  //# sourceMappingURL=collector-version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"collector-version.d.ts","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,eAAO,MAAM,iBAAiB,EAAE,MAA6E,CAAC"}
1
+ {"version":3,"file":"collector-version.d.ts","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA,6NAA6N;AAM7N,eAAO,MAAM,iBAAiB,EAAE,MAA6E,CAAC"}
@@ -1,11 +1,4 @@
1
- /**
2
- * `Evidence.collectorVersion`'s source for `ProcessCollector`: this
3
- * package's own `package.json` "version", read from the artifact itself
4
- * rather than hand-duplicated into a second string that can drift from
5
- * what actually shipped. Same idea as `nodeVersion` elsewhere in this repo
6
- * (`process.version`, always known, never probed) applied to a package
7
- * instead of the Node runtime.
8
- */
1
+ /** `Evidence.collectorVersion` for `ProcessCollector`: read from package.json itself so it can't drift from what shipped. Same pattern as `nodeVersion` (`process.version`), applied to a package instead of the runtime. */
9
2
  import { createRequire } from "node:module";
10
3
  const require = createRequire(import.meta.url);
11
4
  export const COLLECTOR_VERSION = require("../package.json").version;
@@ -1 +1 @@
1
- {"version":3,"file":"collector-version.js","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,iBAAiB,GAAY,OAAO,CAAC,iBAAiB,CAAkC,CAAC,OAAO,CAAC"}
1
+ {"version":3,"file":"collector-version.js","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA,6NAA6N;AAE7N,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,iBAAiB,GAAY,OAAO,CAAC,iBAAiB,CAAkC,CAAC,OAAO,CAAC"}
@@ -1,184 +1,100 @@
1
1
  /**
2
- * §21's execution boundary, container backend -- the macOS/Windows half of
3
- * the sandbox-isolation lane. `sandbox.ts` closed this gap for Linux via
4
- * bubblewrap; this module closes it for macOS and Windows, but NOT by
5
- * building a second and third native OS sandbox. That path was researched
6
- * (`sandbox.ts`'s own original module doc named `sandbox-exec`/Seatbelt on
7
- * macOS and Job Objects/AppContainer on Windows as candidates) and
8
- * explicitly rejected as an architecture decision: `sandbox-exec` is
9
- * deprecated (no public API replacement), the Endpoint Security Framework
10
- * needs a special Apple entitlement this project does not have, and
11
- * hand-building Job Objects + AppContainer + WFP network enforcement on
12
- * Windows is a second and third bespoke native sandbox to build and
13
- * maintain for the same problem already solved once.
2
+ * §21's execution boundary, container backend -- the macOS/Windows half of the
3
+ * sandbox-isolation lane (`sandbox.ts` covers Linux via bubblewrap).
14
4
  *
15
- * **Mechanism: route the spawned process through a real container instead.**
16
- * This runtime's actual targets -- JVM/Spring, Node, Python backends -- are
17
- * already containerizable, so this reuses the *Linux* sandbox mechanism
18
- * bwrap already proved, by running the target process inside a `docker run`
19
- * invocation. The container's own rootfs IS the "outside allowedRoots is
20
- * unreachable" boundary; nothing inside the container needs bwrap. On
21
- * macOS, `docker run` targets Docker Desktop's Linux VM; on Windows, WSL2 /
22
- * Docker Desktop's Windows backend. Both ultimately run the same Linux
23
- * container runtime this module already drives directly.
5
+ * **Not a second native sandbox.** `sandbox-exec`/Seatbelt (macOS) and Job
6
+ * Objects/AppContainer/WFP (Windows) were researched and rejected: `sandbox-exec` is
7
+ * deprecated with no public API replacement, Endpoint Security needs an Apple
8
+ * entitlement this project doesn't have, and hand-building Windows equivalents is a
9
+ * second and third bespoke sandbox for a problem already solved once.
24
10
  *
25
- * **What was actually verified, and what was not -- stated precisely,
26
- * matching `sandbox.ts`'s own "researched and disclosed rather than
27
- * guessed" discipline:**
11
+ * **Mechanism: route the spawned process through a real container instead**, via
12
+ * `docker run` (Docker Desktop's Linux VM on macOS, WSL2/Docker Desktop on Windows).
13
+ * The container's own rootfs is the isolation boundary; no bwrap needed inside it.
28
14
  *
29
- * REAL-TESTED, on this Linux development machine, via a real installed
30
- * Docker daemon (server 29.6.1) and real `docker run` invocations -- not
31
- * mocked, not asserted from documentation: a file outside a container's
32
- * bind-mounts is genuinely unreachable (`ENOENT` from inside the running
33
- * container); a network call under `--network none` is genuinely refused
34
- * against a real host listener; both together on one spawn; the same
35
- * boundary through the full `ExecutionController.run()` path, not just this
36
- * module's own function called directly. See `container-sandbox.test.ts`
37
- * and `controller.test.ts`'s container-backend end-to-end test.
15
+ * **Verified for real** (Linux dev machine, real Docker daemon 29.6.1, not mocked):
16
+ * a file outside bind-mounts is unreachable (`ENOENT`); a network call under
17
+ * `--network none` is refused against a real listener; both together, through the
18
+ * full `ExecutionController.run()` path. See `container-sandbox.test.ts`,
19
+ * `controller.test.ts`'s container-backend e2e test.
38
20
  *
39
- * **IMPLEMENTATION-NOT-DONE, not a "gap" and not a coverage caveat: the
40
- * macOS-Docker-Desktop-VM-specific and Windows-WSL2-specific integration
41
- * paths are UNVERIFIED on real macOS/Windows hardware.** No such hardware
42
- * is available in this environment. Concretely unverified: detecting
43
- * whether Docker Desktop is actually running before attempting a spawn;
44
- * path translation across the Docker Desktop VM boundary on macOS; WSL2
45
- * path translation on Windows (a Windows host path and the path Linux
46
- * containers see are not the same string). This module's bind-mount
47
- * strategy assumes the host path and the in-container path are identical
48
- * (`-v hostPath:hostPath`, mirroring `sandbox.ts`'s own `--bind root root`
49
- * convention) -- true on Linux, true for Docker Desktop's Linux VM when the
50
- * path is already inside the VM's shared-drive mapping, but the exact
51
- * translation rules for an arbitrary Windows path are not exercised here at
52
- * all. This is "the container mechanism exists and is tested where it can
53
- * be, but full cross-platform verification needs hardware nobody here has"
54
- * -- a distinct category from a real bug or an intentionally out-of-scope
55
- * capability, and this module does not claim otherwise.
21
+ * **UNVERIFIED on real macOS/Windows hardware** (none available here): Docker
22
+ * Desktop running-detection before spawn, path translation across the Docker
23
+ * Desktop VM boundary (macOS) and WSL2 (Windows) -- this module's bind-mount assumes
24
+ * host path == in-container path (`-v hostPath:hostPath`), true on Linux and for
25
+ * Docker Desktop's Linux VM when already inside its shared-drive mapping, untested
26
+ * for an arbitrary Windows path. Mechanism exists and is tested where it can be;
27
+ * cross-platform verification needs hardware nobody here has -- not a bug, not
28
+ * claimed otherwise.
56
29
  *
57
- * **Explicit scope boundary, same treatment attach-mode already gets in
58
- * this codebase:** a target that cannot run containerized at all --deep
59
- * native OS integration, GUI-dependent processes, OS-specific native
60
- * dependencies that don't exist inside a Linux container image-- is out of
61
- * scope for this approach, on any platform. There is no fallback to a
62
- * native sandbox for such a target; it is simply not isolatable by this
63
- * mechanism, and that is disclosed rather than silently unsupported.
30
+ * **Scope boundary** (same treatment as attach-mode elsewhere): a target that can't
31
+ * run containerized at all (deep native OS integration, GUI, native deps absent
32
+ * from a Linux image) is out of scope on any platform, with no native-sandbox
33
+ * fallback -- disclosed, not silently unsupported.
64
34
  *
65
- * **Real escape testing on the container boundary is still required and
66
- * was done** -- "the container starts successfully" is not treated as
67
- * sufficient evidence here, the same bar `sandbox.ts`'s bwrap path was held
68
- * to. A mutation check on the first version of that testing (temporarily
69
- * removing `--network none`, rerunning) surfaced a genuine behavioral
70
- * difference from bwrap worth stating precisely rather than glossing over:
71
- * **Docker gives every container its own network namespace by default,
72
- * with or without `--network none`** -- unlike bwrap, which shares the
73
- * host's network unless `networkPolicy` is declared. A containerized
74
- * process can therefore never reach the HOST's own loopback either way
75
- * (that alone is not evidence `--network none` specifically did anything),
76
- * but it CAN still reach the public internet via Docker's default bridge
77
- * NAT when no `networkPolicy` is declared -- only `--network none`
78
- * additionally blocks that. `container-sandbox.test.ts`'s dedicated
79
- * mutation-sensitive test isolates exactly this variable (an external
80
- * host, not the host's own loopback) to prove the flag itself is what is
81
- * being tested, not mere containerization.
35
+ * **Measured Docker/bwrap difference** (found via a mutation check removing
36
+ * `--network none` and rerunning): Docker gives every container its own network
37
+ * namespace regardless of `--network none` -- unlike bwrap, which shares the host's
38
+ * network unless `networkPolicy` is declared. So a container can never reach the
39
+ * HOST's loopback either way, but it CAN still reach the public internet via
40
+ * Docker's default bridge NAT unless `--network none` is set. The dedicated
41
+ * mutation-sensitive test in `container-sandbox.test.ts` checks reachability to an
42
+ * external host (not loopback) to isolate exactly this.
82
43
  *
83
- * **What this module does NOT attempt to solve, disclosed rather than
84
- * silently dropped:** composition with `ResourceLimits` (`prlimit`) --
85
- * `sandbox.ts` composes bwrap with `applyResourceLimits` by wrapping
86
- * outside it; this module does not attempt the equivalent composition with
87
- * Docker's own `--memory`/`--cpus`/`--pids-limit` flags in this pass. A
88
- * `resourceLimits`-declared execution routed through the container backend
89
- * runs without OS-enforced resource limits inside the container. Selective
90
- * network allow-/deny-listing (`NetworkPolicy` shapes other than full
91
- * denial) is unimplemented here for the same reason `sandbox.ts` doesn't
92
- * implement it: it needs DNS interception and IP filtering this iteration
93
- * does not build -- `unsupportedContainerNetworkPolicyReason` reports this
94
- * the same way `sandbox.ts`'s own `unsupportedNetworkPolicyReason` does,
95
- * deliberately matching posture rather than silently claiming broader
96
- * support than the underlying mechanism has.
44
+ * **Not solved here, disclosed:** `ResourceLimits` composition -- `sandbox.ts` wraps
45
+ * bwrap with `applyResourceLimits`; this module does not wrap Docker's
46
+ * `--memory`/`--cpus`/`--pids-limit` equivalents, so a `resourceLimits`-declared
47
+ * execution on this backend runs unlimited inside the container. Selective network
48
+ * allow/deny-listing is unimplemented for the same reason as `sandbox.ts` (needs DNS
49
+ * interception + IP filtering) -- `unsupportedContainerNetworkPolicyReason` reports
50
+ * it the same way, matching posture rather than overclaiming.
97
51
  */
98
52
  import type { CapabilityStatus, FilesystemPolicy, NetworkPolicy } from "@descryy/runtime-contracts";
99
- /**
100
- * Real capability check: is a working Docker CLI + reachable daemon
101
- * actually present, not merely "does a `docker` binary exist on PATH."
102
- * `docker version` talks to the daemon; a CLI with no running daemon behind
103
- * it (Docker Desktop not started, dockerd not running) fails this the same
104
- * way a missing binary does -- both are "cannot enforce," and the caller
105
- * does not need to tell them apart to make the right decision (refuse).
106
- */
53
+ /** Real check: working Docker CLI + reachable daemon, not just a `docker` binary on PATH. `docker version` talks to the daemon, so a CLI with no daemon running fails the same way a missing binary does -- both mean "cannot enforce." */
107
54
  export declare function containerRuntimeCapability(env?: NodeJS.ProcessEnv): CapabilityStatus;
108
- /**
109
- * Mirrors `sandbox.ts`'s `filesystemIsolationCapability`/
110
- * `networkIsolationCapability` split: one real mechanism underneath
111
- * (a container boundary), two named capabilities because the two policies
112
- * are declared, refused, and reasoned about independently at the call
113
- * site.
114
- */
115
55
  export declare function containerFilesystemIsolationCapability(env?: NodeJS.ProcessEnv): CapabilityStatus;
116
56
  export declare function containerNetworkIsolationCapability(env?: NodeJS.ProcessEnv): CapabilityStatus;
117
- /**
118
- * Same posture as `sandbox.ts`'s `unsupportedNetworkPolicyReason`, matched
119
- * deliberately rather than reinvented: only full denial
120
- * (`{ mode: "allow", hosts: [] }`, `docker run --network none`) is
121
- * enforced. A shape bwrap already discloses as unsupported is not silently
122
- * claimed as supported here just because the underlying mechanism changed.
123
- */
57
+ /** Same posture as `sandbox.ts`'s `unsupportedNetworkPolicyReason`: only full denial (`--network none`) is enforced. A shape bwrap discloses as unsupported isn't silently claimed here just because the mechanism changed. */
124
58
  export declare function unsupportedContainerNetworkPolicyReason(policy: NetworkPolicy): string | null;
125
59
  export declare function resolveContainerImage(interpreterCommand: string): string | null;
126
60
  export interface ContainerSandboxOptions {
127
- /** Used only to resolve a default base image via `resolveContainerImage` when `containerImage` is not given. */
61
+ /** Resolves a default base image via `resolveContainerImage` when `containerImage` is not given. */
128
62
  readonly interpreterCommand: string;
129
- /** The command to exec *inside* the container -- resolved against the image's own PATH, not the host's (see `processEnv`'s own comment for why `PATH` is never forwarded). */
63
+ /** Command to exec inside the container -- resolved against the image's own PATH, not the host's. */
130
64
  readonly command: string;
131
65
  readonly args: readonly string[];
132
- /** Bind-mounted into the container at the identical path (`-v cwd:cwd`), always -- mirrors `sandbox.ts`'s "cwd always bound" default, and is the one directory the target process is guaranteed to need. */
66
+ /** Always bind-mounted at the identical path (`-v cwd:cwd`) -- mirrors sandbox.ts's "cwd always bound" default. */
133
67
  readonly cwd: string;
134
68
  readonly filesystemPolicy?: FilesystemPolicy;
135
69
  readonly networkPolicy?: NetworkPolicy;
136
70
  /**
137
- * Env vars the CONTAINERIZED PROCESS itself needs (e.g. `PORT`, a
138
- * service's declared `env`) -- forwarded into the container via `-e
139
- * KEY=VALUE`. **`PATH` is deliberately never forwarded**: the container
140
- * must resolve `command` against its own image's filesystem layout
141
- * (`/usr/local/bin/node` in `node:22-slim`, not wherever the host's
142
- * interpreter happens to live), and forwarding the host's `PATH` would
143
- * silently break that resolution or -- if it named a path that happens to
144
- * exist for a different binary inside the image -- run the wrong thing.
145
- * Every other variable is forwarded unfiltered, matching the same
146
- * unfiltered-inheritance precedent `process-manager.ts` already
147
- * established for the bwrap path (bwrap does not `--clearenv` either).
71
+ * Env vars the containerized process needs, forwarded via `-e KEY=VALUE`. `PATH` is
72
+ * never forwarded: the container must resolve `command` against its own image
73
+ * layout (`/usr/local/bin/node`, not the host's), and the host's PATH would break
74
+ * that or run the wrong binary. Everything else forwards unfiltered, matching
75
+ * bwrap's own unfiltered-inheritance precedent (no `--clearenv`).
148
76
  */
149
77
  readonly processEnv?: Readonly<Record<string, string | undefined>>;
150
- /** Env used only to run the `docker` CLI itself (PATH lookup for `docker`, `DOCKER_HOST`, etc.) -- plays the same role `SandboxOptions.env` plays for bwrap's own capability check. Defaults to `process.env`. */
78
+ /** Env for running the `docker` CLI itself (PATH, `DOCKER_HOST`). Defaults to `process.env`. */
151
79
  readonly env?: NodeJS.ProcessEnv;
152
- /** Explicit override -- bypasses `resolveContainerImage`'s name-based guess entirely. */
80
+ /** Explicit override, bypasses `resolveContainerImage`'s name-based guess. */
153
81
  readonly containerImage?: string;
154
82
  }
155
83
  /**
156
- * Wraps `command`/`args` with `docker run` so a real container boundary
157
- * enforces `filesystemPolicy`/`networkPolicy`. Neither declared returns
158
- * `command`/`args` unchanged and never invokes docker at all -- the same
159
- * non-regression contract `sandbox.ts`'s `applySandbox` established for
160
- * bwrap, applied here too: a caller who never opts into a policy is
161
- * completely unaffected by this module's existence.
84
+ * Wraps `command`/`args` with `docker run` so a container boundary enforces
85
+ * `filesystemPolicy`/`networkPolicy`. Neither declared returns them unchanged and
86
+ * never invokes docker -- same non-regression contract as `sandbox.ts`'s
87
+ * `applySandbox`.
162
88
  *
163
- * **Throws rather than silently spawning unconstrained** when a policy is
164
- * requested and cannot actually be backed -- no working Docker, or a
165
- * `networkPolicy` shape this mechanism doesn't implement -- matching
166
- * `applySandbox`'s own refuse-rather-than-guess precedent exactly.
89
+ * **Throws rather than silently spawning unconstrained** when a policy can't
90
+ * actually be backed (no working Docker, unimplemented `networkPolicy` shape).
167
91
  *
168
- * **Returns a real, unique `containerName` whenever it wraps.** This was
169
- * added after a genuine finding from this module's own real escape tests,
170
- * not assumed up front: `process-manager.ts`'s existing group-kill
171
- * (`process.kill(-pid, signal)`) targets the *local* `docker` CLI process's
172
- * group -- exactly right for bwrap, which execs the sandboxed process
173
- * directly and never creates a second process group. Docker is different:
174
- * the container runs under `dockerd`, not as a child of the local `docker`
175
- * CLI, so killing the CLI's process group does not reliably stop the
176
- * container -- observed directly (a `kill()` call left a real orphaned
177
- * `node:22-slim` container running after both the CLI process and its
178
- * grace period were gone). `containerName` lets the caller
179
- * (`process-manager.ts`) issue a real, explicit `docker stop <name>`
180
- * against the container itself as the actual mechanism, not the CLI
181
- * process, for the container backend.
92
+ * **Returns a real, unique `containerName` whenever it wraps.** Found via real
93
+ * escape testing: `process-manager.ts`'s group-kill targets the local `docker` CLI
94
+ * process's group, which is right for bwrap but not Docker -- the container runs
95
+ * under `dockerd`, not as the CLI's child, so killing the CLI's group left an
96
+ * orphaned `node:22-slim` container running in testing. `containerName` lets the
97
+ * caller issue an explicit `docker stop <name>` against the container itself.
182
98
  */
183
99
  export declare function applyContainerSandbox(options: ContainerSandboxOptions): {
184
100
  readonly command: string;
@@ -1 +1 @@
1
- {"version":3,"file":"container-sandbox.d.ts","sourceRoot":"","sources":["../src/container-sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgGG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEpG;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAUjG;AAED;;;;;;GAMG;AACH,wBAAgB,sCAAsC,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAE7G;AAED,wBAAgB,mCAAmC,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAE1G;AAED;;;;;;GAMG;AACH,wBAAgB,uCAAuC,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAO5F;AAkBD,wBAAgB,qBAAqB,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE/E;AAED,MAAM,WAAW,uBAAuB;IACtC,gHAAgH;IAChH,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,8KAA8K;IAC9K,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,4MAA4M;IAC5M,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IACnE,kNAAkN;IAClN,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,yFAAyF;IACzF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,uBAAuB,GAC/B;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAkDjG"}
1
+ {"version":3,"file":"container-sandbox.d.ts","sourceRoot":"","sources":["../src/container-sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEpG,2OAA2O;AAC3O,wBAAgB,0BAA0B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAUjG;AAKD,wBAAgB,sCAAsC,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAE7G;AAED,wBAAgB,mCAAmC,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAE1G;AAED,+NAA+N;AAC/N,wBAAgB,uCAAuC,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAO5F;AAYD,wBAAgB,qBAAqB,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE/E;AAED,MAAM,WAAW,uBAAuB;IACtC,oGAAoG;IACpG,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,qGAAqG;IACrG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,mHAAmH;IACnH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IACnE,gGAAgG;IAChG,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,uBAAuB,GAC/B;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAkDjG"}