@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.04a1e5b9e484

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 (223) hide show
  1. package/README.md +165 -1
  2. package/dist/bridge/bridge-chat.types.d.ts +111 -0
  3. package/dist/bridge/bridge-chat.types.js +401 -0
  4. package/dist/bridge/bridge.base.d.ts +9 -1
  5. package/dist/bridge/bridge.service.d.ts +129 -3
  6. package/dist/bridge/bridge.service.js +2427 -70
  7. package/dist/bridge/bridge.types.d.ts +218 -2
  8. package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
  9. package/dist/bridge/dispatch-result-outbox.js +62 -0
  10. package/dist/bridge/dispatch-result-worker.d.ts +2 -0
  11. package/dist/bridge/dispatch-result-worker.js +122 -0
  12. package/dist/bridge/internals.d.ts +2 -0
  13. package/dist/bridge/internals.js +1 -0
  14. package/dist/bridge/readiness-result-outbox.d.ts +21 -0
  15. package/dist/bridge/readiness-result-outbox.js +54 -0
  16. package/dist/bridge/readiness-result-worker.d.ts +2 -0
  17. package/dist/bridge/readiness-result-worker.js +121 -0
  18. package/dist/bridge/runner-host-capabilities.d.ts +15 -0
  19. package/dist/bridge/runner-host-capabilities.js +93 -0
  20. package/dist/cleanup/cleanup.service.d.ts +14 -2
  21. package/dist/cleanup/cleanup.service.js +496 -3
  22. package/dist/cleanup/cleanup.types.d.ts +10 -0
  23. package/dist/cleanup/index.d.ts +1 -1
  24. package/dist/cleanup/index.js +1 -1
  25. package/dist/cleanup/internals.d.ts +2 -2
  26. package/dist/cleanup/internals.js +1 -1
  27. package/dist/cli/cli.help.d.ts +2 -0
  28. package/dist/cli/cli.help.js +217 -0
  29. package/dist/cli/cli.service.d.ts +55 -7
  30. package/dist/cli/cli.service.js +429 -219
  31. package/dist/cli/local-config.d.ts +7 -0
  32. package/dist/cli/local-config.js +51 -3
  33. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  34. package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
  35. package/dist/runtime/attempt-database-broker.d.ts +89 -0
  36. package/dist/runtime/attempt-database-broker.js +390 -0
  37. package/dist/runtime/attempt-node-runtime-launcher.d.ts +18 -0
  38. package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
  39. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  40. package/dist/runtime/attempt-path-operation.js +94 -0
  41. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  42. package/dist/runtime/attempt-private-directory.js +54 -0
  43. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  44. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  45. package/dist/runtime/attempt-process-supervisor.d.ts +102 -0
  46. package/dist/runtime/attempt-process-supervisor.js +421 -0
  47. package/dist/runtime/canonical-source-file.d.ts +3 -0
  48. package/dist/runtime/canonical-source-file.js +54 -0
  49. package/dist/runtime/docker-engine-probe.d.ts +90 -0
  50. package/dist/runtime/docker-engine-probe.js +333 -0
  51. package/dist/runtime/generated-output-reset.d.ts +20 -0
  52. package/dist/runtime/generated-output-reset.js +157 -0
  53. package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
  54. package/dist/runtime/host-database-capability-boundary.js +62 -0
  55. package/dist/runtime/host-diagnostics.d.ts +11 -0
  56. package/dist/runtime/host-diagnostics.js +192 -0
  57. package/dist/runtime/host-execution-root.d.ts +4 -0
  58. package/dist/runtime/host-execution-root.js +39 -0
  59. package/dist/runtime/host-listener-inspector.d.ts +51 -0
  60. package/dist/runtime/host-listener-inspector.js +385 -0
  61. package/dist/runtime/host-port-broker.d.ts +76 -0
  62. package/dist/runtime/host-port-broker.js +333 -0
  63. package/dist/runtime/host-runtime-contract.d.ts +58 -0
  64. package/dist/runtime/host-runtime-contract.js +813 -0
  65. package/dist/runtime/host-runtime-lease.d.ts +217 -0
  66. package/dist/runtime/host-runtime-lease.js +2487 -0
  67. package/dist/runtime/host-runtime-promotion.d.ts +32 -0
  68. package/dist/runtime/host-runtime-promotion.js +206 -0
  69. package/dist/runtime/memory-capacity.d.ts +70 -0
  70. package/dist/runtime/memory-capacity.js +123 -0
  71. package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
  72. package/dist/runtime/native-test-postgres-fallback.js +91 -0
  73. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  74. package/dist/runtime/node-host-runtime-adapter.js +286 -0
  75. package/dist/runtime/pinned-directory-process.d.ts +9 -0
  76. package/dist/runtime/pinned-directory-process.js +61 -0
  77. package/dist/runtime/pinned-source-directory.d.ts +17 -0
  78. package/dist/runtime/pinned-source-directory.js +94 -0
  79. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  80. package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
  81. package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
  82. package/dist/runtime/prepared-runtime-identity.js +218 -0
  83. package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
  84. package/dist/runtime/prepared-runtime-readiness.js +285 -0
  85. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  86. package/dist/runtime/runtime-isolation-lease.js +161 -0
  87. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  88. package/dist/runtime/workspace-resource-estimator.js +174 -0
  89. package/dist/sandbox/command-runner.d.ts +4 -0
  90. package/dist/sandbox/command-runner.js +47 -0
  91. package/dist/sandbox/contract.d.ts +5 -0
  92. package/dist/sandbox/contract.js +276 -0
  93. package/dist/sandbox/event-budget.d.ts +11 -0
  94. package/dist/sandbox/event-budget.js +27 -0
  95. package/dist/sandbox/image-policy.d.ts +35 -0
  96. package/dist/sandbox/image-policy.js +247 -0
  97. package/dist/sandbox/index.d.ts +5 -0
  98. package/dist/sandbox/index.js +5 -0
  99. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  100. package/dist/sandbox/lease-watchdog.js +67 -0
  101. package/dist/sandbox/preflight.d.ts +84 -0
  102. package/dist/sandbox/preflight.js +521 -0
  103. package/dist/sandbox/process.d.ts +20 -0
  104. package/dist/sandbox/process.js +209 -0
  105. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  106. package/dist/sandbox/provider-auth/codex.js +212 -0
  107. package/dist/sandbox/repository-lock.d.ts +6 -0
  108. package/dist/sandbox/repository-lock.js +129 -0
  109. package/dist/sandbox/runtime.d.ts +302 -0
  110. package/dist/sandbox/runtime.js +2368 -0
  111. package/dist/sandbox/sandbox.types.d.ts +93 -0
  112. package/dist/sandbox/sandbox.types.js +1 -0
  113. package/dist/sandbox/smoke.d.ts +76 -0
  114. package/dist/sandbox/smoke.js +380 -0
  115. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  116. package/dist/sandbox/worker-supervisor.js +36 -0
  117. package/dist/sandbox/worker.d.ts +1 -0
  118. package/dist/sandbox/worker.js +225 -0
  119. package/dist/sandbox/workspace.service.d.ts +13 -0
  120. package/dist/sandbox/workspace.service.js +74 -0
  121. package/dist/session/managed-attempt-tools.d.ts +28 -0
  122. package/dist/session/managed-attempt-tools.js +1016 -0
  123. package/dist/session/managed-build-coordinator.d.ts +5 -0
  124. package/dist/session/managed-build-coordinator.js +106 -0
  125. package/dist/session/managed-build-receipt.d.ts +21 -0
  126. package/dist/session/managed-build-receipt.js +337 -0
  127. package/dist/session/managed-verification-timeouts.d.ts +8 -0
  128. package/dist/session/managed-verification-timeouts.js +17 -0
  129. package/dist/session/phase-completion.artifacts.d.ts +27 -0
  130. package/dist/session/phase-completion.artifacts.js +829 -0
  131. package/dist/session/phase-completion.d.ts +53 -42
  132. package/dist/session/phase-completion.helpers.d.ts +117 -0
  133. package/dist/session/phase-completion.helpers.js +707 -0
  134. package/dist/session/phase-completion.js +125 -3007
  135. package/dist/session/phase-completion.prompts.d.ts +14 -0
  136. package/dist/session/phase-completion.prompts.js +233 -0
  137. package/dist/session/phase-completion.runtime.d.ts +49 -0
  138. package/dist/session/phase-completion.runtime.js +840 -0
  139. package/dist/session/phase-completion.schemas.d.ts +3 -0
  140. package/dist/session/phase-completion.schemas.js +859 -0
  141. package/dist/session/phase-completion.validation.d.ts +38 -0
  142. package/dist/session/phase-completion.validation.js +918 -0
  143. package/dist/session/pre-publication-validation.d.ts +75 -0
  144. package/dist/session/pre-publication-validation.js +661 -0
  145. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
  146. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
  147. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  148. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  149. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
  150. package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
  151. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  152. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  153. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  154. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  155. package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
  156. package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
  157. package/dist/session/provider-cli/provider-cli.process.js +874 -0
  158. package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
  159. package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
  160. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  161. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
  162. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  163. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  164. package/dist/session/provider-cli/provider-cli.runtime.d.ts +22 -0
  165. package/dist/session/provider-cli/provider-cli.runtime.js +92 -0
  166. package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
  167. package/dist/session/provider-cli/provider-cli.service.js +1258 -2497
  168. package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
  169. package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
  170. package/dist/session/provider-cli/provider-output-state.js +817 -0
  171. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  172. package/dist/session/provider-cli/provider-usage.js +28 -5
  173. package/dist/session/session-redaction.d.ts +3 -0
  174. package/dist/session/session-redaction.js +448 -0
  175. package/dist/session/session.base.d.ts +6 -1
  176. package/dist/session/session.service.d.ts +8 -2
  177. package/dist/session/session.service.js +586 -59
  178. package/dist/session/session.types.d.ts +324 -3
  179. package/dist/session/session.types.js +939 -10
  180. package/dist/session/unreferenced-exports.d.ts +12 -0
  181. package/dist/session/unreferenced-exports.js +59 -0
  182. package/dist/shared/format/format-bytes.d.ts +1 -0
  183. package/dist/shared/format/format-bytes.js +9 -0
  184. package/dist/shared/git/git.js +2 -1
  185. package/dist/shared/path/path.d.ts +1 -0
  186. package/dist/shared/path/path.js +21 -1
  187. package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
  188. package/dist/shared/process/dotnet-toolchain.js +307 -0
  189. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  190. package/dist/shared/process/host-process-recovery.js +79 -0
  191. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  192. package/dist/shared/process/host-process-supervisor.js +331 -0
  193. package/dist/shared/process/process.d.ts +3 -0
  194. package/dist/shared/process/process.js +13 -7
  195. package/dist/shared/process/windows-launch.d.ts +8 -0
  196. package/dist/shared/process/windows-launch.js +41 -0
  197. package/dist/shared/provider-capabilities.d.ts +4 -0
  198. package/dist/shared/provider-capabilities.js +88 -0
  199. package/dist/shared/redaction/redaction.d.ts +1 -0
  200. package/dist/shared/redaction/redaction.js +47 -4
  201. package/dist/shared/time/monotonic.d.ts +1 -0
  202. package/dist/shared/time/monotonic.js +8 -0
  203. package/dist/version.d.ts +1 -1
  204. package/dist/version.js +1 -1
  205. package/dist/worktree/artifact-injector.d.ts +3 -2
  206. package/dist/worktree/artifact-injector.js +82 -10
  207. package/dist/worktree/dependency-warmer.d.ts +53 -1
  208. package/dist/worktree/dependency-warmer.js +619 -11
  209. package/dist/worktree/index.d.ts +2 -1
  210. package/dist/worktree/index.js +1 -0
  211. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  212. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  213. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  214. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  215. package/dist/worktree/worktree-paths.d.ts +4 -0
  216. package/dist/worktree/worktree-paths.js +30 -0
  217. package/dist/worktree/worktree.base.d.ts +3 -0
  218. package/dist/worktree/worktree.service.d.ts +16 -3
  219. package/dist/worktree/worktree.service.js +334 -54
  220. package/dist/worktree/worktree.types.d.ts +9 -1
  221. package/package.json +12 -7
  222. package/runtime/windows-job-supervisor.ps1 +722 -0
  223. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -0,0 +1,247 @@
1
+ import { createHash, createPrivateKey, createPublicKey, sign, verify } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { runSandboxProcess } from "./process.js";
5
+ export async function evaluateSandboxImagePolicy(request) {
6
+ const environment = request.environment ?? process.env;
7
+ const required = request.executionPolicy === "sandbox_required";
8
+ const enforced = environment.FORGE_SANDBOX_IMAGE_POLICY_ENFORCED === "true";
9
+ const sbomExecutable = environment.FORGE_SANDBOX_SBOM_EXECUTABLE?.trim();
10
+ const vulnerabilityExecutable = environment.FORGE_SANDBOX_VULNERABILITY_EXECUTABLE?.trim();
11
+ const signingKeyPath = environment.FORGE_SANDBOX_PROVENANCE_SIGNING_KEY?.trim();
12
+ const signatureExecutable = environment.FORGE_SANDBOX_SIGNATURE_EXECUTABLE?.trim();
13
+ const signaturePublicKey = environment.FORGE_SANDBOX_SIGNATURE_PUBLIC_KEY?.trim();
14
+ if (required && (!enforced || !sbomExecutable || !vulnerabilityExecutable || !signingKeyPath || !signatureExecutable || !signaturePublicKey)) {
15
+ throw new Error("sandbox_build_policy_failed: sandbox_required needs enforced SBOM, vulnerability, provenance-signing, and upstream-signature configuration.");
16
+ }
17
+ const root = path.join(request.stateRoot, "attestations", request.key);
18
+ await fs.mkdir(root, { recursive: true, mode: 0o700 });
19
+ const artifacts = {};
20
+ let status = "passed";
21
+ const privateKey = signingKeyPath ? await readSigningKey(signingKeyPath) : null;
22
+ const runCommand = request.runCommand ?? runSandboxProcess;
23
+ const upstreamSignatures = {};
24
+ for (const image of [...new Set([request.toolingImage, ...request.upstreamImages])]) {
25
+ const signatureStatus = await verifySandboxImageSignature({
26
+ image,
27
+ executionPolicy: request.executionPolicy,
28
+ environment,
29
+ signal: request.signal,
30
+ runCommand
31
+ });
32
+ upstreamSignatures[image] = signatureStatus;
33
+ if (signatureStatus === "warning")
34
+ status = "warning";
35
+ }
36
+ for (const [layer, imageDigest] of Object.entries(request.images)) {
37
+ const scanTarget = request.imageScanTargets?.[layer] ?? imageDigest;
38
+ const provenance = {
39
+ schemaVersion: 1,
40
+ predicateType: "https://slsa.dev/provenance/v1",
41
+ subject: [{ name: `forge/${layer}`, digest: { sha256: imageDigest.slice("sha256:".length) } }],
42
+ builder: { id: `forge-bridge/${request.builderVersion}` },
43
+ buildType: "https://forge.dev/buildkit/project-agent-overlay/v1",
44
+ invocation: { parameters: { key: request.key, project: request.project, layer } },
45
+ materials: [
46
+ { uri: "forge:tracked-context", digest: { sha256: request.contextDigest } },
47
+ { uri: "forge:sandbox-contract", digest: { sha256: request.contractDigest.replace(/^sha256:/u, "") } },
48
+ { uri: request.toolingImage },
49
+ { uri: `pkg:npm/@openai/codex@${request.codexVersion}` }
50
+ ]
51
+ };
52
+ const provenanceBytes = Buffer.from(`${canonicalJson(provenance)}\n`, "utf8");
53
+ const provenanceName = `${layer}.provenance.json`;
54
+ await writeRestricted(path.join(root, provenanceName), provenanceBytes);
55
+ let signatureDigest = null;
56
+ if (privateKey) {
57
+ const signature = sign("sha256", provenanceBytes, privateKey);
58
+ if (!verify("sha256", provenanceBytes, createPublicKey(privateKey), signature)) {
59
+ throw new Error("sandbox_build_policy_failed: generated provenance signature did not verify.");
60
+ }
61
+ await writeRestricted(path.join(root, `${provenanceName}.sig`), signature);
62
+ signatureDigest = sha256(signature);
63
+ }
64
+ else {
65
+ status = "warning";
66
+ }
67
+ let sbomDigest = null;
68
+ if (sbomExecutable) {
69
+ const sbom = await runCommand({
70
+ command: sbomExecutable,
71
+ args: [scanTarget, "-o", "spdx-json"],
72
+ timeoutMs: 10 * 60_000,
73
+ maxOutputBytes: 32 * 1024 * 1024,
74
+ signal: request.signal
75
+ });
76
+ if (sbom.exitCode !== 0 || sbom.truncated || !validJsonObject(sbom.stdout)) {
77
+ throw new Error(`sandbox_build_policy_failed: SPDX SBOM generation failed for ${layer}.`);
78
+ }
79
+ const bytes = Buffer.from(sbom.stdout, "utf8");
80
+ await writeRestricted(path.join(root, `${layer}.sbom.spdx.json`), bytes);
81
+ sbomDigest = sha256(bytes);
82
+ }
83
+ else {
84
+ status = "warning";
85
+ }
86
+ let vulnerabilityDigest = null;
87
+ if (vulnerabilityExecutable) {
88
+ const scan = await runCommand({
89
+ command: vulnerabilityExecutable,
90
+ args: ["image", "--scanners", "vuln", "--format", "json", "--severity", "HIGH,CRITICAL", "--exit-code", "1", "--no-progress", scanTarget],
91
+ timeoutMs: 15 * 60_000,
92
+ maxOutputBytes: 32 * 1024 * 1024,
93
+ signal: request.signal
94
+ });
95
+ const bytes = Buffer.from(scan.stdout || scan.stderr, "utf8");
96
+ await writeRestricted(path.join(root, `${layer}.vulnerabilities.json`), bytes);
97
+ vulnerabilityDigest = sha256(bytes);
98
+ if (scan.exitCode !== 0 || scan.truncated) {
99
+ throw new Error(`sandbox_build_policy_failed: ${layer} violates the HIGH/CRITICAL vulnerability policy.`);
100
+ }
101
+ }
102
+ else {
103
+ status = "warning";
104
+ }
105
+ artifacts[layer] = {
106
+ imageDigest,
107
+ scanTarget,
108
+ provenanceSha256: sha256(provenanceBytes),
109
+ provenanceSignatureSha256: signatureDigest,
110
+ sbomSha256: sbomDigest,
111
+ vulnerabilityReportSha256: vulnerabilityDigest
112
+ };
113
+ }
114
+ const policyRecord = {
115
+ schemaVersion: 1,
116
+ key: request.key,
117
+ project: request.project,
118
+ status,
119
+ enforced,
120
+ generatedAt: new Date().toISOString(),
121
+ upstreamSignatures,
122
+ artifacts
123
+ };
124
+ const recordBytes = Buffer.from(`${canonicalJson(policyRecord)}\n`, "utf8");
125
+ const policyDigest = sha256(recordBytes);
126
+ await writeRestricted(path.join(root, "policy.json"), recordBytes);
127
+ return { status, policyDigest, root };
128
+ }
129
+ export async function verifySandboxImageSignature(request) {
130
+ const required = request.executionPolicy === "sandbox_required";
131
+ if (!/@sha256:[a-f0-9]{64}$/iu.test(request.image)) {
132
+ if (required)
133
+ throw new Error(`sandbox_image_signature_failed: image '${request.image}' is not digest-pinned.`);
134
+ return "warning";
135
+ }
136
+ const environment = request.environment ?? process.env;
137
+ const executable = environment.FORGE_SANDBOX_SIGNATURE_EXECUTABLE?.trim();
138
+ const publicKeyPath = environment.FORGE_SANDBOX_SIGNATURE_PUBLIC_KEY?.trim();
139
+ if (!executable || !publicKeyPath) {
140
+ if (required)
141
+ throw new Error("sandbox_image_signature_failed: sandbox_required needs an image signature verifier and public key.");
142
+ return "warning";
143
+ }
144
+ await readBoundedRegularFile(publicKeyPath, "image signature public key", "sandbox_image_signature_failed");
145
+ const runCommand = request.runCommand ?? runSandboxProcess;
146
+ const result = await runCommand({
147
+ command: executable,
148
+ args: ["verify", "--key", publicKeyPath, request.image],
149
+ timeoutMs: 5 * 60_000,
150
+ maxOutputBytes: 4 * 1024 * 1024,
151
+ terminateOnOutputLimit: true,
152
+ signal: request.signal
153
+ });
154
+ if (result.exitCode !== 0 || result.truncated || !validSignatureResult(result.stdout)) {
155
+ throw new Error(`sandbox_image_signature_failed: signature verification failed for '${request.image}'.`);
156
+ }
157
+ return "passed";
158
+ }
159
+ export async function hasSandboxImagePolicyRecord(stateRoot, key, expectedDigest, requirePassed = false) {
160
+ const target = path.join(stateRoot, "attestations", key, "policy.json");
161
+ const stat = await fs.lstat(target).catch(() => null);
162
+ if (!stat || !stat.isFile() || stat.isSymbolicLink() || stat.size > 1024 * 1024)
163
+ return false;
164
+ const bytes = await fs.readFile(target);
165
+ if (sha256(bytes) !== expectedDigest)
166
+ return false;
167
+ if (!requirePassed)
168
+ return true;
169
+ try {
170
+ const record = JSON.parse(bytes.toString("utf8"));
171
+ return record.status === "passed" && record.enforced === true;
172
+ }
173
+ catch {
174
+ return false;
175
+ }
176
+ }
177
+ export async function sandboxImagePolicyConfigurationDigest(environment = process.env) {
178
+ const signingKeyPath = environment.FORGE_SANDBOX_PROVENANCE_SIGNING_KEY?.trim() ?? "";
179
+ const signaturePublicKeyPath = environment.FORGE_SANDBOX_SIGNATURE_PUBLIC_KEY?.trim() ?? "";
180
+ const fields = [
181
+ environment.FORGE_SANDBOX_IMAGE_POLICY_ENFORCED ?? "",
182
+ environment.FORGE_SANDBOX_SBOM_EXECUTABLE?.trim() ?? "",
183
+ environment.FORGE_SANDBOX_VULNERABILITY_EXECUTABLE?.trim() ?? "",
184
+ environment.FORGE_SANDBOX_SIGNATURE_EXECUTABLE?.trim() ?? "",
185
+ await policyFileFingerprint(signingKeyPath),
186
+ await policyFileFingerprint(signaturePublicKeyPath)
187
+ ];
188
+ return sha256(Buffer.from(fields.join("\0"), "utf8"));
189
+ }
190
+ async function readSigningKey(target) {
191
+ const bytes = await readBoundedRegularFile(target, "provenance signing key");
192
+ try {
193
+ return createPrivateKey(bytes);
194
+ }
195
+ catch (error) {
196
+ throw new Error("sandbox_build_policy_failed: provenance signing key is invalid.", { cause: error });
197
+ }
198
+ }
199
+ async function readBoundedRegularFile(target, label, failureCode = "sandbox_build_policy_failed") {
200
+ const stat = await fs.lstat(target).catch(() => null);
201
+ if (!stat || !stat.isFile() || stat.isSymbolicLink() || stat.size === 0 || stat.size > 64 * 1024) {
202
+ throw new Error(`${failureCode}: ${label} must be a bounded regular file.`);
203
+ }
204
+ return await fs.readFile(target);
205
+ }
206
+ async function policyFileFingerprint(target) {
207
+ if (!target)
208
+ return "not-configured";
209
+ const stat = await fs.lstat(target).catch(() => null);
210
+ if (!stat || !stat.isFile() || stat.isSymbolicLink() || stat.size === 0 || stat.size > 64 * 1024)
211
+ return "invalid";
212
+ return sha256(await fs.readFile(target));
213
+ }
214
+ async function writeRestricted(target, bytes) {
215
+ const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
216
+ await fs.writeFile(temporary, bytes, { mode: 0o600, flag: "wx" });
217
+ await fs.rename(temporary, target);
218
+ }
219
+ function validJsonObject(value) {
220
+ try {
221
+ const parsed = JSON.parse(value);
222
+ return Boolean(parsed && typeof parsed === "object" && !Array.isArray(parsed));
223
+ }
224
+ catch {
225
+ return false;
226
+ }
227
+ }
228
+ function validSignatureResult(value) {
229
+ try {
230
+ const parsed = JSON.parse(value);
231
+ return Array.isArray(parsed) && parsed.length > 0 && parsed.every(entry => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
232
+ }
233
+ catch {
234
+ return false;
235
+ }
236
+ }
237
+ function sha256(value) {
238
+ return createHash("sha256").update(value).digest("hex");
239
+ }
240
+ function canonicalJson(value) {
241
+ if (Array.isArray(value))
242
+ return `[${value.map(canonicalJson).join(",")}]`;
243
+ if (value && typeof value === "object") {
244
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => `${JSON.stringify(key)}:${canonicalJson(child)}`).join(",")}}`;
245
+ }
246
+ return JSON.stringify(value);
247
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./sandbox.types.js";
2
+ export * from "./contract.js";
3
+ export * from "./preflight.js";
4
+ export * from "./command-runner.js";
5
+ export * from "./runtime.js";
@@ -0,0 +1,5 @@
1
+ export * from "./sandbox.types.js";
2
+ export * from "./contract.js";
3
+ export * from "./preflight.js";
4
+ export * from "./command-runner.js";
5
+ export * from "./runtime.js";
@@ -0,0 +1,18 @@
1
+ export interface SignedLeaseUpdate {
2
+ readonly schemaVersion: 1;
3
+ readonly dispatchAttemptId: string;
4
+ readonly fencingToken: string;
5
+ readonly expiresAt: string;
6
+ readonly mac: string;
7
+ }
8
+ export declare function createLeaseUpdate(dispatchAttemptId: string, fencingToken: string, expiresAt: string, controlKey: string): SignedLeaseUpdate;
9
+ export declare class MonotonicLeaseDeadline {
10
+ readonly dispatchAttemptId: string;
11
+ readonly fencingToken: string;
12
+ private readonly controlKey;
13
+ private expiresAtMs;
14
+ private deadlineMs;
15
+ constructor(dispatchAttemptId: string, fencingToken: string, initialExpiresAt: string, controlKey: string, wallNowMs?: number, monotonicNowMs?: number);
16
+ apply(update: unknown, wallNowMs?: number, monotonicNowMs?: number): boolean;
17
+ expired(monotonicNowMs?: number): boolean;
18
+ }
@@ -0,0 +1,67 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ export function createLeaseUpdate(dispatchAttemptId, fencingToken, expiresAt, controlKey) {
3
+ return {
4
+ schemaVersion: 1,
5
+ dispatchAttemptId,
6
+ fencingToken,
7
+ expiresAt,
8
+ mac: leaseMac(dispatchAttemptId, fencingToken, expiresAt, controlKey)
9
+ };
10
+ }
11
+ export class MonotonicLeaseDeadline {
12
+ dispatchAttemptId;
13
+ fencingToken;
14
+ controlKey;
15
+ expiresAtMs;
16
+ deadlineMs;
17
+ constructor(dispatchAttemptId, fencingToken, initialExpiresAt, controlKey, wallNowMs = Date.now(), monotonicNowMs = performance.now()) {
18
+ this.dispatchAttemptId = dispatchAttemptId;
19
+ this.fencingToken = fencingToken;
20
+ this.controlKey = controlKey;
21
+ this.expiresAtMs = parsedExpiry(initialExpiresAt);
22
+ this.deadlineMs = monotonicNowMs + Math.max(0, this.expiresAtMs - wallNowMs);
23
+ }
24
+ apply(update, wallNowMs = Date.now(), monotonicNowMs = performance.now()) {
25
+ if (!isSignedLeaseUpdate(update)
26
+ || update.dispatchAttemptId !== this.dispatchAttemptId
27
+ || update.fencingToken !== this.fencingToken
28
+ || parsedExpiry(update.expiresAt) <= this.expiresAtMs)
29
+ return false;
30
+ const expected = leaseMac(update.dispatchAttemptId, update.fencingToken, update.expiresAt, this.controlKey);
31
+ if (!safeEqual(expected, update.mac))
32
+ return false;
33
+ this.expiresAtMs = parsedExpiry(update.expiresAt);
34
+ this.deadlineMs = monotonicNowMs + Math.max(0, this.expiresAtMs - wallNowMs);
35
+ return true;
36
+ }
37
+ expired(monotonicNowMs = performance.now()) {
38
+ return monotonicNowMs >= this.deadlineMs;
39
+ }
40
+ }
41
+ function leaseMac(dispatchAttemptId, fencingToken, expiresAt, controlKey) {
42
+ return createHmac("sha256", controlKey)
43
+ .update(`${dispatchAttemptId}\n${fencingToken}\n${expiresAt}`, "utf8")
44
+ .digest("base64url");
45
+ }
46
+ function safeEqual(left, right) {
47
+ const leftBytes = Buffer.from(left, "utf8");
48
+ const rightBytes = Buffer.from(right, "utf8");
49
+ return leftBytes.length === rightBytes.length && timingSafeEqual(leftBytes, rightBytes);
50
+ }
51
+ function parsedExpiry(value) {
52
+ const parsed = Date.parse(value);
53
+ if (!Number.isFinite(parsed))
54
+ throw new Error("Lease expiry is invalid.");
55
+ return parsed;
56
+ }
57
+ function isSignedLeaseUpdate(value) {
58
+ if (!value || typeof value !== "object" || Array.isArray(value))
59
+ return false;
60
+ const update = value;
61
+ return update.schemaVersion === 1
62
+ && typeof update.dispatchAttemptId === "string"
63
+ && typeof update.fencingToken === "string"
64
+ && typeof update.expiresAt === "string"
65
+ && Number.isFinite(Date.parse(update.expiresAt))
66
+ && typeof update.mac === "string";
67
+ }
@@ -0,0 +1,84 @@
1
+ import type { CommandRunner, SandboxPreflightResult } from "./sandbox.types.js";
2
+ export interface DockerPreflightOptions {
3
+ readonly dockerExecutable?: string;
4
+ readonly dockerContext?: string;
5
+ readonly stateRoot?: string;
6
+ readonly runnerInstanceId?: string;
7
+ readonly maxConcurrentSandboxes?: number;
8
+ readonly diskHighWatermarkPercent?: number;
9
+ readonly diskLowWatermarkPercent?: number;
10
+ readonly minimumWorkspaceBytes?: number;
11
+ readonly hostMemoryReserveBytes?: number;
12
+ readonly defaultWorkerMemoryLimitBytes?: number;
13
+ readonly defaultServiceMemoryLimitBytes?: number;
14
+ readonly runtimeOverheadBytes?: number;
15
+ readonly requireRootless?: boolean;
16
+ }
17
+ export declare class DockerPreflight {
18
+ private readonly commands;
19
+ private readonly cacheMs;
20
+ private generation;
21
+ private cached;
22
+ private fullPreflightAt;
23
+ private staticPreflight;
24
+ private inspectionInFlight;
25
+ private lastCapacitySample;
26
+ private readonly options;
27
+ private readonly docker;
28
+ private readonly ownership;
29
+ private diskPressureLatched;
30
+ private readonly memoryAvailability;
31
+ constructor(options?: DockerPreflightOptions | string, commands?: CommandRunner, cacheMs?: number);
32
+ private unavailable;
33
+ private memoryReserveBytes;
34
+ private defaultAdmissionMemoryBytes;
35
+ current(): SandboxPreflightResult | null;
36
+ refresh(): Promise<SandboxPreflightResult>;
37
+ release(): Promise<void>;
38
+ run(force?: boolean): Promise<SandboxPreflightResult>;
39
+ /**
40
+ * Refreshes only the admission inputs that change while the daemon's static
41
+ * capability snapshot remains valid. Heartbeats use this path so periodic
42
+ * liveness reporting does not wake Docker/WSL to rerun feature probes.
43
+ */
44
+ refreshCapacity(): Promise<SandboxPreflightResult>;
45
+ /**
46
+ * When the cached static preflight remains usable, produces a capacity sample
47
+ * that began after this admission request. An older inspection is awaited,
48
+ * not preempted or reused. Later admission callers may share the next sample
49
+ * only when it began after they entered this method. Without a usable static
50
+ * snapshot, the existing fail-closed result is returned without probing.
51
+ */
52
+ refreshCapacityForAdmission(): Promise<SandboxPreflightResult>;
53
+ private startInspection;
54
+ private inspect;
55
+ private inspectCapacity;
56
+ }
57
+ /**
58
+ * Uses the same reviewed reserve vector as host-runtime admission. The input
59
+ * is always the physical host total; Docker Desktop's VM total is tracked as
60
+ * a separate daemon commitment domain.
61
+ */
62
+ export declare function resolveSandboxHostMemoryReserveBytes(totalMemoryBytes: number, explicitBytes?: number, environment?: NodeJS.ProcessEnv): number;
63
+ export declare function canRunDockerMaintenance(preflight: {
64
+ readonly healthy: boolean;
65
+ readonly unavailableCode?: string | null;
66
+ }): boolean;
67
+ export declare function evaluateDiskPressure(availableBytes: number, totalBytes: number, minimumAvailableBytes: number, highWatermarkPercent: number, lowWatermarkPercent: number, pressureLatched: boolean): boolean;
68
+ export declare function requiredAvailableBytesToClearDiskPressure(totalBytes: number, minimumAvailableBytes: number, lowWatermarkPercent: number): number;
69
+ export declare function storagePolicyAvailable(storageQuota: boolean, environment?: NodeJS.ProcessEnv): boolean;
70
+ export declare class DockerContextOwnershipLock {
71
+ private lockPath;
72
+ private lockToken;
73
+ acquire(contextName?: string): Promise<void>;
74
+ release(): Promise<void>;
75
+ }
76
+ export declare function parseDockerMemoryUsage(value: string): number;
77
+ interface DockerContextLockOwner {
78
+ readonly token?: string;
79
+ readonly pid?: number;
80
+ readonly processIdentity?: string | null;
81
+ readonly acquiredAt?: string;
82
+ }
83
+ export declare function dockerContextOwnerIsActive(owner: DockerContextLockOwner | null): boolean;
84
+ export {};