@mono-agent/agent-app 0.6.2 → 0.8.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 (211) hide show
  1. package/README.md +210 -19
  2. package/dist/adapter-send-proxy.d.ts +18 -0
  3. package/dist/adapter-send-proxy.d.ts.map +1 -0
  4. package/dist/adapter-send-proxy.js +229 -0
  5. package/dist/adapter-send-proxy.js.map +1 -0
  6. package/dist/adapter-send-tools-main.js +62 -4
  7. package/dist/adapter-send-tools-main.js.map +1 -1
  8. package/dist/adapter-send-tools.d.ts +33 -6
  9. package/dist/adapter-send-tools.d.ts.map +1 -1
  10. package/dist/adapter-send-tools.js +485 -87
  11. package/dist/adapter-send-tools.js.map +1 -1
  12. package/dist/app-config.d.ts.map +1 -1
  13. package/dist/app-config.js +8 -0
  14. package/dist/app-config.js.map +1 -1
  15. package/dist/app.d.ts +2 -0
  16. package/dist/app.d.ts.map +1 -1
  17. package/dist/app.js +528 -135
  18. package/dist/app.js.map +1 -1
  19. package/dist/broadcast-recorder.d.ts.map +1 -1
  20. package/dist/broadcast-recorder.js +24 -2
  21. package/dist/broadcast-recorder.js.map +1 -1
  22. package/dist/channels.d.ts.map +1 -1
  23. package/dist/channels.js +16 -6
  24. package/dist/channels.js.map +1 -1
  25. package/dist/cli.d.ts +76 -4
  26. package/dist/cli.d.ts.map +1 -1
  27. package/dist/cli.js +2102 -102
  28. package/dist/cli.js.map +1 -1
  29. package/dist/config-reference.d.ts.map +1 -1
  30. package/dist/config-reference.js +195 -6
  31. package/dist/config-reference.js.map +1 -1
  32. package/dist/configuration-proposal-main.d.ts +3 -0
  33. package/dist/configuration-proposal-main.d.ts.map +1 -0
  34. package/dist/configuration-proposal-main.js +13 -0
  35. package/dist/configuration-proposal-main.js.map +1 -0
  36. package/dist/configuration-proposal-tool.d.ts +26 -0
  37. package/dist/configuration-proposal-tool.d.ts.map +1 -0
  38. package/dist/configuration-proposal-tool.js +139 -0
  39. package/dist/configuration-proposal-tool.js.map +1 -0
  40. package/dist/configured-agent.d.ts +12 -0
  41. package/dist/configured-agent.d.ts.map +1 -1
  42. package/dist/configured-agent.js +150 -18
  43. package/dist/configured-agent.js.map +1 -1
  44. package/dist/consumer-contract.d.ts +3 -0
  45. package/dist/consumer-contract.d.ts.map +1 -1
  46. package/dist/consumer-contract.js +44 -1
  47. package/dist/consumer-contract.js.map +1 -1
  48. package/dist/doctor.d.ts +27 -5
  49. package/dist/doctor.d.ts.map +1 -1
  50. package/dist/doctor.js +1197 -101
  51. package/dist/doctor.js.map +1 -1
  52. package/dist/first-run-managed-memory.d.ts +39 -0
  53. package/dist/first-run-managed-memory.d.ts.map +1 -0
  54. package/dist/first-run-managed-memory.js +521 -0
  55. package/dist/first-run-managed-memory.js.map +1 -0
  56. package/dist/first-run-readiness.d.ts +111 -0
  57. package/dist/first-run-readiness.d.ts.map +1 -0
  58. package/dist/first-run-readiness.js +757 -0
  59. package/dist/first-run-readiness.js.map +1 -0
  60. package/dist/index.d.ts +8 -3
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +4 -1
  63. package/dist/index.js.map +1 -1
  64. package/dist/init.d.ts +88 -3
  65. package/dist/init.d.ts.map +1 -1
  66. package/dist/init.js +1212 -23
  67. package/dist/init.js.map +1 -1
  68. package/dist/interaction-bridge.d.ts +25 -1
  69. package/dist/interaction-bridge.d.ts.map +1 -1
  70. package/dist/interaction-bridge.js +281 -19
  71. package/dist/interaction-bridge.js.map +1 -1
  72. package/dist/local-configuration.d.ts +56 -0
  73. package/dist/local-configuration.d.ts.map +1 -0
  74. package/dist/local-configuration.js +914 -0
  75. package/dist/local-configuration.js.map +1 -0
  76. package/dist/memory-command.d.ts +4 -0
  77. package/dist/memory-command.d.ts.map +1 -1
  78. package/dist/memory-command.js +751 -27
  79. package/dist/memory-command.js.map +1 -1
  80. package/dist/memory-recall.d.ts +38 -10
  81. package/dist/memory-recall.d.ts.map +1 -1
  82. package/dist/memory-recall.js +82 -25
  83. package/dist/memory-recall.js.map +1 -1
  84. package/dist/memory-retrieval.d.ts +66 -0
  85. package/dist/memory-retrieval.d.ts.map +1 -0
  86. package/dist/memory-retrieval.js +332 -0
  87. package/dist/memory-retrieval.js.map +1 -0
  88. package/dist/modules/base.d.ts +6 -5
  89. package/dist/modules/base.d.ts.map +1 -1
  90. package/dist/modules/base.js +11 -7
  91. package/dist/modules/base.js.map +1 -1
  92. package/dist/modules/catalog.d.ts.map +1 -1
  93. package/dist/modules/catalog.js +54 -20
  94. package/dist/modules/catalog.js.map +1 -1
  95. package/dist/modules/index.d.ts +2 -2
  96. package/dist/modules/index.d.ts.map +1 -1
  97. package/dist/modules/index.js +1 -1
  98. package/dist/modules/index.js.map +1 -1
  99. package/dist/modules/known-tools.d.ts +3 -0
  100. package/dist/modules/known-tools.d.ts.map +1 -1
  101. package/dist/modules/known-tools.js +11 -1
  102. package/dist/modules/known-tools.js.map +1 -1
  103. package/dist/modules/types.d.ts +20 -6
  104. package/dist/modules/types.d.ts.map +1 -1
  105. package/dist/modules/types.js.map +1 -1
  106. package/dist/package-version.d.ts +3 -0
  107. package/dist/package-version.d.ts.map +1 -0
  108. package/dist/package-version.js +14 -0
  109. package/dist/package-version.js.map +1 -0
  110. package/dist/pi-auth-store-inspection.d.ts +21 -0
  111. package/dist/pi-auth-store-inspection.d.ts.map +1 -0
  112. package/dist/pi-auth-store-inspection.js +120 -0
  113. package/dist/pi-auth-store-inspection.js.map +1 -0
  114. package/dist/pi-oauth-login-main.d.ts +3 -0
  115. package/dist/pi-oauth-login-main.d.ts.map +1 -0
  116. package/dist/pi-oauth-login-main.js +14 -0
  117. package/dist/pi-oauth-login-main.js.map +1 -0
  118. package/dist/pi-oauth-login.d.ts +22 -0
  119. package/dist/pi-oauth-login.d.ts.map +1 -0
  120. package/dist/pi-oauth-login.js +79 -0
  121. package/dist/pi-oauth-login.js.map +1 -0
  122. package/dist/project-skills.d.ts +40 -0
  123. package/dist/project-skills.d.ts.map +1 -0
  124. package/dist/project-skills.js +573 -0
  125. package/dist/project-skills.js.map +1 -0
  126. package/dist/provider-setup.d.ts +117 -2
  127. package/dist/provider-setup.d.ts.map +1 -1
  128. package/dist/provider-setup.js +1505 -49
  129. package/dist/provider-setup.js.map +1 -1
  130. package/dist/readiness-probe-worker.d.ts +12 -0
  131. package/dist/readiness-probe-worker.d.ts.map +1 -0
  132. package/dist/readiness-probe-worker.js +276 -0
  133. package/dist/readiness-probe-worker.js.map +1 -0
  134. package/dist/readiness-probe.d.ts +100 -0
  135. package/dist/readiness-probe.d.ts.map +1 -0
  136. package/dist/readiness-probe.js +780 -0
  137. package/dist/readiness-probe.js.map +1 -0
  138. package/dist/request-model-override.d.ts +43 -1
  139. package/dist/request-model-override.d.ts.map +1 -1
  140. package/dist/request-model-override.js +178 -19
  141. package/dist/request-model-override.js.map +1 -1
  142. package/dist/run-history.d.ts +32 -0
  143. package/dist/run-history.d.ts.map +1 -0
  144. package/dist/run-history.js +859 -0
  145. package/dist/run-history.js.map +1 -0
  146. package/dist/runs-health.d.ts.map +1 -1
  147. package/dist/runs-health.js +12 -4
  148. package/dist/runs-health.js.map +1 -1
  149. package/dist/runtime-option-extensions.d.ts +14 -0
  150. package/dist/runtime-option-extensions.d.ts.map +1 -0
  151. package/dist/runtime-option-extensions.js +98 -0
  152. package/dist/runtime-option-extensions.js.map +1 -0
  153. package/dist/runtime-routes.d.ts +9 -0
  154. package/dist/runtime-routes.d.ts.map +1 -0
  155. package/dist/runtime-routes.js +13 -0
  156. package/dist/runtime-routes.js.map +1 -0
  157. package/dist/sandbox-manager.d.ts +69 -0
  158. package/dist/sandbox-manager.d.ts.map +1 -0
  159. package/dist/sandbox-manager.js +856 -0
  160. package/dist/sandbox-manager.js.map +1 -0
  161. package/dist/supermemory-plugin.d.ts +54 -0
  162. package/dist/supermemory-plugin.d.ts.map +1 -0
  163. package/dist/supermemory-plugin.js +136 -0
  164. package/dist/supermemory-plugin.js.map +1 -0
  165. package/dist/tui-command.d.ts +15 -0
  166. package/dist/tui-command.d.ts.map +1 -1
  167. package/dist/tui-command.js +46 -6
  168. package/dist/tui-command.js.map +1 -1
  169. package/dist/web-command.d.ts +8 -0
  170. package/dist/web-command.d.ts.map +1 -1
  171. package/dist/web-command.js +174 -17
  172. package/dist/web-command.js.map +1 -1
  173. package/dist/wizard/answers.d.ts +31 -4
  174. package/dist/wizard/answers.d.ts.map +1 -1
  175. package/dist/wizard/answers.js +145 -16
  176. package/dist/wizard/answers.js.map +1 -1
  177. package/dist/wizard/from-flags.d.ts +7 -0
  178. package/dist/wizard/from-flags.d.ts.map +1 -1
  179. package/dist/wizard/from-flags.js +36 -2
  180. package/dist/wizard/from-flags.js.map +1 -1
  181. package/dist/wizard/index.d.ts +4 -4
  182. package/dist/wizard/index.d.ts.map +1 -1
  183. package/dist/wizard/index.js +2 -2
  184. package/dist/wizard/index.js.map +1 -1
  185. package/dist/wizard/model-discovery.d.ts +33 -2
  186. package/dist/wizard/model-discovery.d.ts.map +1 -1
  187. package/dist/wizard/model-discovery.js +826 -108
  188. package/dist/wizard/model-discovery.js.map +1 -1
  189. package/dist/wizard/presets.d.ts +1 -1
  190. package/dist/wizard/presets.d.ts.map +1 -1
  191. package/dist/wizard/presets.js +7 -12
  192. package/dist/wizard/presets.js.map +1 -1
  193. package/dist/wizard/prompts.d.ts +56 -9
  194. package/dist/wizard/prompts.d.ts.map +1 -1
  195. package/dist/wizard/prompts.js +172 -20
  196. package/dist/wizard/prompts.js.map +1 -1
  197. package/dist/wizard/run.d.ts +59 -3
  198. package/dist/wizard/run.d.ts.map +1 -1
  199. package/dist/wizard/run.js +1133 -191
  200. package/dist/wizard/run.js.map +1 -1
  201. package/package.json +21 -16
  202. package/resources/srt/package-lock.json +66 -0
  203. package/resources/srt/package.json +9 -0
  204. package/schema/mono-agent.config.schema.json +381 -17
  205. package/skills/mono-agent-composer/SKILL.md +4 -4
  206. package/skills/mono-agent-composer/references/config-blueprint.md +5 -4
  207. package/skills/mono-agent-composer/references/discovery-questions.md +8 -8
  208. package/skills/mono-agent-composer/references/feature-coverage.md +8 -6
  209. package/skills/mono-agent-composer/references/package-map.md +1 -1
  210. package/skills/mono-agent-composer/references/playbooks.md +8 -7
  211. package/skills/mono-agent-composer/references/validation.md +1 -1
@@ -0,0 +1,856 @@
1
+ import { execFile } from "node:child_process";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { constants as fsConstants } from "node:fs";
4
+ import { chmod, copyFile, lstat, mkdir, mkdtemp, open, opendir, readFile, realpath, rename, rm, stat, unlink, } from "node:fs/promises";
5
+ import { createServer } from "node:http";
6
+ import { homedir, tmpdir } from "node:os";
7
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
8
+ import { setTimeout as delay } from "node:timers/promises";
9
+ import { fileURLToPath } from "node:url";
10
+ import { promisify } from "node:util";
11
+ import { CodedError } from "@mono-agent/agent-contracts";
12
+ import { createSandboxPolicy, createSrtSandboxEngine, MANAGED_SRT_TREE_SHA256, } from "@mono-agent/runtime-adapter";
13
+ const execFileAsync = promisify(execFile);
14
+ export const MANAGED_SRT_PACKAGE = "@anthropic-ai/sandbox-runtime";
15
+ export const MANAGED_SRT_VERSION = "0.0.64";
16
+ export const MANAGED_SRT_LOCK_SHA256 = "0f34561ddc700ad43c31f254beb0e1e4b87bae61795acac5d7fa890dd5890924";
17
+ export const MANAGED_SRT_MARKER = ".mono-agent-srt.json";
18
+ const MANAGED_SRT_RESOURCE_ROOT = fileURLToPath(new URL("../resources/srt", import.meta.url));
19
+ const INSTALL_LOCK_NAME = ".install.lock";
20
+ const INSTALL_LOCK_WAIT_MS = 150_000;
21
+ export class SandboxManagerError extends CodedError {
22
+ }
23
+ export function managedSrtInstallRoot(options = {}) {
24
+ return resolve(managedSrtVersionRoot(options), MANAGED_SRT_LOCK_SHA256);
25
+ }
26
+ export async function sandboxRuntimeStatus(options = {}) {
27
+ const installRoot = managedSrtInstallRoot(options);
28
+ const platform = options.platform ?? process.platform;
29
+ if (platform === "darwin" && await pathExists(installRoot)) {
30
+ try {
31
+ await assertTrustedNodeExecutable(resolveNodePath(options));
32
+ const cliPath = await verifyManagedInstall(installRoot, options);
33
+ return {
34
+ state: "ready",
35
+ source: "managed",
36
+ version: MANAGED_SRT_VERSION,
37
+ installRoot,
38
+ nodePath: resolveNodePath(options),
39
+ cliPath,
40
+ message: `Managed SRT ${MANAGED_SRT_VERSION} is installed and integrity-verified.`,
41
+ };
42
+ }
43
+ catch (error) {
44
+ return {
45
+ state: "corrupt",
46
+ source: "managed",
47
+ version: MANAGED_SRT_VERSION,
48
+ installRoot,
49
+ message: errorMessage(error),
50
+ };
51
+ }
52
+ }
53
+ const externalCommand = options.externalCommand === undefined ? "srt" : options.externalCommand;
54
+ if (externalCommand !== false) {
55
+ const available = await createSrtSandboxEngine({ command: externalCommand }).isAvailable();
56
+ if (available) {
57
+ return {
58
+ state: "ready",
59
+ source: "external",
60
+ version: MANAGED_SRT_VERSION,
61
+ installRoot,
62
+ message: `External SRT at ${externalCommand} passed a functional enforcement proof.`,
63
+ };
64
+ }
65
+ }
66
+ return platform === "darwin"
67
+ ? {
68
+ state: "absent",
69
+ source: "none",
70
+ version: MANAGED_SRT_VERSION,
71
+ installRoot,
72
+ message: "SRT is not installed. Run `mono-agent sandbox setup` to install the pinned user-cache copy.",
73
+ }
74
+ : {
75
+ state: "unsupported",
76
+ source: "none",
77
+ version: MANAGED_SRT_VERSION,
78
+ installRoot,
79
+ message: `Managed SRT setup is currently supported on macOS only (${platform} detected). Install SRT and its platform prerequisites yourself, then rerun the check.`,
80
+ };
81
+ }
82
+ export async function setupManagedSrt(options = {}) {
83
+ const platform = options.platform ?? process.platform;
84
+ if (platform !== "darwin") {
85
+ throw new SandboxManagerError("managed_srt_unsupported", `Managed SRT setup is supported on macOS only (${platform} detected). No system packages were changed.`, { platform });
86
+ }
87
+ throwIfAborted(options.signal);
88
+ try {
89
+ assertSupportedNodeVersion(process.versions.node);
90
+ await assertTrustedNodeExecutable(resolveNodePath(options));
91
+ await verifyInstallResources(options.resourceRoot ?? MANAGED_SRT_RESOURCE_ROOT);
92
+ await ensurePrivateVersionRoot(options);
93
+ }
94
+ catch (error) {
95
+ if (error instanceof SandboxManagerError) {
96
+ throw error;
97
+ }
98
+ throw new SandboxManagerError("managed_srt_corrupt", `Managed SRT cache or install resources are unsafe: ${errorMessage(error)}`, { cause: errorMessage(error) });
99
+ }
100
+ const installRoot = managedSrtInstallRoot(options);
101
+ let initial = await inspectManagedOnly(options);
102
+ if (initial.state === "ready") {
103
+ const check = options.verify === false ? undefined : await checkSandboxRuntime(options);
104
+ return { installed: false, repaired: false, status: initial, ...(check === undefined ? {} : { check }) };
105
+ }
106
+ const heldLock = await acquireInstallLock(options);
107
+ let stagingRoot;
108
+ let repaired = false;
109
+ try {
110
+ throwIfAborted(options.signal);
111
+ await cleanupStaleStaging(managedSrtVersionRoot(options));
112
+ initial = await inspectManagedOnly(options);
113
+ if (initial.state === "ready") {
114
+ const check = options.verify === false ? undefined : await checkSandboxRuntime(options);
115
+ return { installed: false, repaired: false, status: initial, ...(check === undefined ? {} : { check }) };
116
+ }
117
+ if (initial.state === "corrupt") {
118
+ await quarantineManagedInstall(installRoot);
119
+ repaired = true;
120
+ }
121
+ stagingRoot = resolve(managedSrtVersionRoot(options), `.${MANAGED_SRT_LOCK_SHA256}.staging.${process.pid}.${randomUUID()}`);
122
+ await mkdir(stagingRoot, { mode: 0o700 });
123
+ await stageInstallResources(options.resourceRoot ?? MANAGED_SRT_RESOURCE_ROOT, stagingRoot);
124
+ throwIfAborted(options.signal);
125
+ if (options.hooks?.installDependencies !== undefined) {
126
+ await options.hooks.installDependencies(stagingRoot, options.signal);
127
+ }
128
+ else {
129
+ await runNpmCi(stagingRoot, options);
130
+ }
131
+ throwIfAborted(options.signal);
132
+ await rm(resolve(stagingRoot, "node_modules", ".bin"), { recursive: true, force: true });
133
+ await makeTreePrivateAndRejectLinks(stagingRoot);
134
+ await writeInstallMarker(stagingRoot, options);
135
+ await verifyManagedInstall(stagingRoot, options);
136
+ try {
137
+ await rename(stagingRoot, installRoot);
138
+ stagingRoot = undefined;
139
+ }
140
+ catch (error) {
141
+ if (!isNodeError(error, "EEXIST") && !isNodeError(error, "ENOTEMPTY")) {
142
+ throw error;
143
+ }
144
+ // Another actor can only win this race by bypassing our lock. Accept its
145
+ // result solely if it independently passes the full integrity check.
146
+ await verifyManagedInstall(installRoot, options);
147
+ }
148
+ const status = await inspectManagedOnly(options);
149
+ if (status.state !== "ready") {
150
+ throw new SandboxManagerError("managed_srt_install_failed", `Managed SRT did not validate after atomic installation: ${status.message}`, { installRoot });
151
+ }
152
+ const check = options.verify === false ? undefined : await checkSandboxRuntime(options);
153
+ return { installed: true, repaired, status, ...(check === undefined ? {} : { check }) };
154
+ }
155
+ catch (error) {
156
+ if (error instanceof SandboxManagerError) {
157
+ throw error;
158
+ }
159
+ throw new SandboxManagerError("managed_srt_install_failed", `Managed SRT installation failed without changing PATH or system packages: ${errorMessage(error)}`, { installRoot, cause: errorMessage(error) });
160
+ }
161
+ finally {
162
+ try {
163
+ if (stagingRoot !== undefined) {
164
+ await rm(stagingRoot, { recursive: true, force: true });
165
+ }
166
+ }
167
+ finally {
168
+ await releaseInstallLock(heldLock);
169
+ }
170
+ }
171
+ }
172
+ export async function checkSandboxRuntime(options = {}) {
173
+ const status = await sandboxRuntimeStatus(options);
174
+ if (status.state !== "ready") {
175
+ throw new SandboxManagerError(status.state === "corrupt" ? "managed_srt_corrupt" : "sandbox_check_failed", status.message, { status });
176
+ }
177
+ throwIfAborted(options.signal);
178
+ let engine;
179
+ if (status.source === "managed") {
180
+ const { nodePath, cliPath } = status;
181
+ if (nodePath === undefined || cliPath === undefined) {
182
+ throw new SandboxManagerError("managed_srt_corrupt", "Managed SRT status omitted its absolute Node or CLI path.", { status });
183
+ }
184
+ engine = createSrtSandboxEngine({
185
+ platform: options.platform ?? process.platform,
186
+ ...(options.cacheRoot === undefined ? {} : { cacheRoot: options.cacheRoot }),
187
+ ...(options.env === undefined ? {} : { env: options.env }),
188
+ ...(options.homeDir === undefined ? {} : { homeDir: options.homeDir }),
189
+ managedNodePath: nodePath,
190
+ });
191
+ }
192
+ else {
193
+ engine = createSrtSandboxEngine({ command: options.externalCommand === undefined ? "srt" : options.externalCommand || "srt" });
194
+ }
195
+ const checks = [];
196
+ if (!(await engine.isAvailable())) {
197
+ throw new SandboxManagerError("sandbox_check_failed", "SRT could be resolved but did not pass its initialization and filesystem enforcement proof.", { source: status.source });
198
+ }
199
+ checks.push({ id: "engine", ok: true, detail: "SRT initialized and enforced a deny boundary." });
200
+ const fixtureRoot = await mkdtemp(resolve(tmpdir(), "mono-agent-srt-check-"));
201
+ const workspace = resolve(fixtureRoot, "workspace");
202
+ const siblingSecret = resolve(fixtureRoot, "sibling-secret.txt");
203
+ const outsideWrite = resolve(fixtureRoot, "outside-write.txt");
204
+ const envPath = resolve(workspace, ".env");
205
+ const allowedPath = resolve(workspace, "allowed.txt");
206
+ const outputPath = resolve(workspace, "output.txt");
207
+ const server = createServer((_request, response) => {
208
+ response.writeHead(200, { "content-type": "text/plain" });
209
+ response.end("mono-agent-srt-ok");
210
+ });
211
+ let requestCount = 0;
212
+ server.on("request", () => {
213
+ requestCount += 1;
214
+ });
215
+ try {
216
+ await mkdir(workspace, { recursive: true });
217
+ await Promise.all([
218
+ writePrivateFile(allowedPath, "allowed\n"),
219
+ writePrivateFile(siblingSecret, "secret\n"),
220
+ writePrivateFile(envPath, "KEEP=true\n"),
221
+ ]);
222
+ await new Promise((resolveListen, rejectListen) => {
223
+ server.once("error", rejectListen);
224
+ server.listen(0, "127.0.0.1", () => {
225
+ server.off("error", rejectListen);
226
+ resolveListen();
227
+ });
228
+ });
229
+ const address = server.address();
230
+ if (address === null || typeof address === "string") {
231
+ throw new Error("local functional-check server did not expose a TCP port");
232
+ }
233
+ const policy = createSandboxPolicy({
234
+ root: workspace,
235
+ readableRoots: [workspace],
236
+ writableRoots: [workspace],
237
+ denyWrite: [".env", ".env.*"],
238
+ network: { mode: "localhost" },
239
+ fallback: "fail-closed",
240
+ });
241
+ await runPrepared(engine, policy, {
242
+ command: process.execPath,
243
+ args: [
244
+ "-e",
245
+ "const fs=require('node:fs');const [a,o]=process.argv.slice(1);if(fs.readFileSync(a,'utf8').trim()!=='allowed')process.exit(2);fs.writeFileSync(o,'written');",
246
+ allowedPath,
247
+ outputPath,
248
+ ],
249
+ cwd: workspace,
250
+ }, options.signal);
251
+ if (await readFile(outputPath, "utf8") !== "written") {
252
+ throw new Error("allowed write did not persist");
253
+ }
254
+ checks.push({ id: "allowed-filesystem", ok: true, detail: "Allowed workspace read and write succeeded." });
255
+ await expectPreparedDenied(engine, policy, {
256
+ command: process.execPath,
257
+ args: ["-e", "require('node:fs').readFileSync(process.argv[1])", siblingSecret],
258
+ cwd: workspace,
259
+ }, options.signal);
260
+ checks.push({ id: "sibling-read-denied", ok: true, detail: "A sibling secret outside readableRoots was denied." });
261
+ await expectPreparedDenied(engine, policy, {
262
+ command: process.execPath,
263
+ args: ["-e", "require('node:fs').writeFileSync(process.argv[1],'changed')", envPath],
264
+ cwd: workspace,
265
+ }, options.signal);
266
+ if (await readFile(envPath, "utf8") !== "KEEP=true\n") {
267
+ throw new Error(".env content changed despite denyWrite");
268
+ }
269
+ checks.push({ id: "env-write-denied", ok: true, detail: ".env mutation was denied and content stayed intact." });
270
+ await expectPreparedDenied(engine, policy, {
271
+ command: process.execPath,
272
+ args: ["-e", "require('node:fs').writeFileSync(process.argv[1],'bad')", outsideWrite],
273
+ cwd: workspace,
274
+ }, options.signal);
275
+ checks.push({ id: "outside-write-denied", ok: true, detail: "A write outside writableRoots was denied." });
276
+ const allowedUrl = `http://127.0.0.1:${address.port}/allowed`;
277
+ const allowedNetwork = await runPrepared(engine, policy, {
278
+ command: "/usr/bin/curl",
279
+ args: ["--fail", "--silent", "--show-error", "--max-time", "5", allowedUrl],
280
+ cwd: workspace,
281
+ }, options.signal);
282
+ if (allowedNetwork.stdout.trim() !== "mono-agent-srt-ok" || requestCount !== 1) {
283
+ throw new Error("localhost request did not reach the deterministic check server exactly once");
284
+ }
285
+ checks.push({ id: "localhost-allowed", ok: true, detail: "127.0.0.1 reached the local check server." });
286
+ await expectPreparedDenied(engine, policy, {
287
+ command: "/usr/bin/curl",
288
+ args: [
289
+ "--fail",
290
+ "--silent",
291
+ "--show-error",
292
+ "--max-time",
293
+ "5",
294
+ "--resolve",
295
+ `blocked.test:${address.port}:127.0.0.1`,
296
+ `http://blocked.test:${address.port}/blocked`,
297
+ ],
298
+ cwd: workspace,
299
+ }, options.signal);
300
+ if (requestCount !== 1) {
301
+ throw new Error("blocked.test bypassed domain filtering and reached the local server");
302
+ }
303
+ checks.push({ id: "domain-denied", ok: true, detail: "blocked.test was denied before reaching the same local server." });
304
+ return { status, checks };
305
+ }
306
+ catch (error) {
307
+ throw new SandboxManagerError("sandbox_check_failed", `SRT functional enforcement check failed: ${errorMessage(error)}`, { status, completedChecks: checks.map((check) => check.id), cause: errorMessage(error) });
308
+ }
309
+ finally {
310
+ await new Promise((resolveClose) => server.close(() => resolveClose()));
311
+ await rm(fixtureRoot, { recursive: true, force: true });
312
+ }
313
+ }
314
+ async function inspectManagedOnly(options) {
315
+ return sandboxRuntimeStatus({ ...options, externalCommand: false });
316
+ }
317
+ function managedSrtVersionRoot(options) {
318
+ const cacheRoot = options.cacheRoot ?? defaultCacheRoot(options);
319
+ return resolve(cacheRoot, "mono-agent", "tools", "srt", MANAGED_SRT_VERSION);
320
+ }
321
+ function defaultCacheRoot(options) {
322
+ const env = options.env ?? process.env;
323
+ const xdg = env.XDG_CACHE_HOME?.trim();
324
+ if (xdg !== undefined && xdg.length > 0 && isAbsolute(xdg)) {
325
+ return resolve(xdg);
326
+ }
327
+ const home = options.homeDir ?? homedir();
328
+ return (options.platform ?? process.platform) === "darwin"
329
+ ? resolve(home, "Library", "Caches")
330
+ : resolve(home, ".cache");
331
+ }
332
+ async function ensurePrivateVersionRoot(options) {
333
+ const cacheRoot = options.cacheRoot ?? defaultCacheRoot(options);
334
+ const roots = [
335
+ resolve(cacheRoot, "mono-agent"),
336
+ resolve(cacheRoot, "mono-agent", "tools"),
337
+ resolve(cacheRoot, "mono-agent", "tools", "srt"),
338
+ managedSrtVersionRoot(options),
339
+ ];
340
+ for (const [index, root] of roots.entries()) {
341
+ try {
342
+ await mkdir(root, { mode: 0o700, recursive: index === 0 });
343
+ }
344
+ catch (error) {
345
+ if (!isNodeError(error, "EEXIST")) {
346
+ throw error;
347
+ }
348
+ }
349
+ await assertPrivateDirectory(root);
350
+ }
351
+ }
352
+ async function verifyInstallResources(resourceRoot) {
353
+ const lockPath = resolve(resourceRoot, "package-lock.json");
354
+ const packagePath = resolve(resourceRoot, "package.json");
355
+ await assertSingleRegularFile(lockPath, false, false);
356
+ await assertSingleRegularFile(packagePath, false, false);
357
+ const [lockContent, packageContent] = await Promise.all([readFile(lockPath), readFile(packagePath, "utf8")]);
358
+ if (sha256(lockContent) !== MANAGED_SRT_LOCK_SHA256) {
359
+ throw new SandboxManagerError("managed_srt_corrupt", "Bundled SRT package-lock.json failed its pinned SHA-256 check; refusing network installation.", { resourceRoot });
360
+ }
361
+ const manifest = JSON.parse(packageContent);
362
+ if (manifest.dependencies?.[MANAGED_SRT_PACKAGE] !== MANAGED_SRT_VERSION) {
363
+ throw new SandboxManagerError("managed_srt_corrupt", "Bundled SRT package.json does not pin the expected exact version.", { resourceRoot });
364
+ }
365
+ }
366
+ async function stageInstallResources(resourceRoot, stagingRoot) {
367
+ for (const name of ["package.json", "package-lock.json"]) {
368
+ const source = resolve(resourceRoot, name);
369
+ const target = resolve(stagingRoot, name);
370
+ await copyFile(source, target, fsConstants.COPYFILE_EXCL);
371
+ await chmod(target, 0o600);
372
+ }
373
+ await verifyInstallResources(stagingRoot);
374
+ }
375
+ async function runNpmCi(stagingRoot, options) {
376
+ const nodePath = resolveNodePath(options);
377
+ const npmCliPath = await resolveNpmCliPath(options);
378
+ const env = options.env ?? process.env;
379
+ await execFileAsync(nodePath, [npmCliPath, "ci", "--ignore-scripts", "--no-audit", "--no-fund"], {
380
+ cwd: stagingRoot,
381
+ env: {
382
+ ...env,
383
+ npm_config_audit: "false",
384
+ npm_config_fund: "false",
385
+ npm_config_ignore_scripts: "true",
386
+ npm_config_package_lock: "true",
387
+ },
388
+ timeout: options.installTimeoutMs ?? 120_000,
389
+ signal: options.signal,
390
+ maxBuffer: 4_000_000,
391
+ });
392
+ }
393
+ function resolveNodePath(options) {
394
+ const nodePath = options.nodePath ?? process.execPath;
395
+ if (!isAbsolute(nodePath)) {
396
+ throw new SandboxManagerError("managed_srt_install_failed", "Node executable path is not absolute.", { nodePath });
397
+ }
398
+ return nodePath;
399
+ }
400
+ async function resolveNpmCliPath(options) {
401
+ const candidates = [
402
+ options.npmCliPath,
403
+ options.env?.npm_execpath,
404
+ process.env.npm_execpath,
405
+ resolve(dirname(resolveNodePath(options)), "npm"),
406
+ ].filter((candidate) => typeof candidate === "string" && candidate.length > 0);
407
+ for (const candidate of candidates) {
408
+ if (!isAbsolute(candidate)) {
409
+ continue;
410
+ }
411
+ try {
412
+ const real = await realpath(candidate);
413
+ const candidateStat = await stat(real);
414
+ if (candidateStat.isFile()
415
+ && (candidateStat.mode & 0o022) === 0
416
+ && (process.getuid === undefined || candidateStat.uid === process.getuid() || candidateStat.uid === 0)) {
417
+ return real;
418
+ }
419
+ }
420
+ catch {
421
+ // Try the next deterministic absolute candidate.
422
+ }
423
+ }
424
+ throw new SandboxManagerError("managed_srt_install_failed", "Could not resolve a trusted absolute npm CLI next to the running Node installation.", { nodePath: resolveNodePath(options) });
425
+ }
426
+ async function assertTrustedNodeExecutable(nodePath) {
427
+ const nodeStat = await lstat(nodePath);
428
+ if (nodeStat.isSymbolicLink() || !nodeStat.isFile()) {
429
+ throw new Error(`Node executable is not a regular non-symbolic file: ${nodePath}`);
430
+ }
431
+ if (nodeStat.nlink !== 1) {
432
+ throw new Error(`Node executable has ${nodeStat.nlink} hard links instead of one: ${nodePath}`);
433
+ }
434
+ if ((nodeStat.mode & 0o022) !== 0) {
435
+ throw new Error(`Node executable is writable by group or other users: ${nodePath}`);
436
+ }
437
+ if (process.getuid !== undefined && nodeStat.uid !== process.getuid() && nodeStat.uid !== 0) {
438
+ throw new Error(`Node executable is not owned by the current user or root: ${nodePath}`);
439
+ }
440
+ }
441
+ async function writeInstallMarker(stagingRoot, options) {
442
+ const cliPath = managedCliPath(stagingRoot);
443
+ const packageJsonPath = managedPackageJsonPath(stagingRoot);
444
+ const treeSha256 = await hashPrivateManagedTree(stagingRoot);
445
+ const expectedTreeSha256 = await trustedTreeSha256(stagingRoot, options);
446
+ if (treeSha256 !== expectedTreeSha256) {
447
+ throw new SandboxManagerError("managed_srt_corrupt", "Installed SRT dependency tree does not match the independently pinned release manifest.", { expectedTreeSha256, treeSha256 });
448
+ }
449
+ const marker = {
450
+ schemaVersion: 2,
451
+ package: MANAGED_SRT_PACKAGE,
452
+ version: MANAGED_SRT_VERSION,
453
+ lockSha256: MANAGED_SRT_LOCK_SHA256,
454
+ cliSha256: sha256(await readFile(cliPath)),
455
+ packageJsonSha256: sha256(await readFile(packageJsonPath)),
456
+ treeSha256: expectedTreeSha256,
457
+ };
458
+ const markerPath = resolve(stagingRoot, MANAGED_SRT_MARKER);
459
+ const handle = await open(markerPath, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY | (fsConstants.O_NOFOLLOW ?? 0), 0o600);
460
+ try {
461
+ await handle.writeFile(`${JSON.stringify(marker, null, 2)}\n`, "utf8");
462
+ await handle.sync();
463
+ }
464
+ finally {
465
+ await handle.close();
466
+ }
467
+ }
468
+ async function verifyManagedInstall(installRoot, options) {
469
+ try {
470
+ await assertPrivateDirectory(installRoot);
471
+ let ancestor = dirname(installRoot);
472
+ for (let index = 0; index < 4; index += 1) {
473
+ await assertPrivateDirectory(ancestor);
474
+ ancestor = dirname(ancestor);
475
+ }
476
+ const markerPath = resolve(installRoot, MANAGED_SRT_MARKER);
477
+ const lockPath = resolve(installRoot, "package-lock.json");
478
+ const cliPath = managedCliPath(installRoot);
479
+ const packageJsonPath = managedPackageJsonPath(installRoot);
480
+ for (const path of [markerPath, lockPath, cliPath, packageJsonPath]) {
481
+ await assertSingleRegularFile(path, true);
482
+ }
483
+ const marker = JSON.parse(await readFile(markerPath, "utf8"));
484
+ if (marker.schemaVersion !== 2
485
+ || marker.package !== MANAGED_SRT_PACKAGE
486
+ || marker.version !== MANAGED_SRT_VERSION
487
+ || marker.lockSha256 !== MANAGED_SRT_LOCK_SHA256
488
+ || !isSha256(marker.cliSha256)
489
+ || !isSha256(marker.packageJsonSha256)
490
+ || marker.treeSha256 !== await trustedTreeSha256(installRoot, options)) {
491
+ throw new Error("install marker identity is invalid");
492
+ }
493
+ const [lockHash, cliHash, packageJsonHash, treeHash] = await Promise.all([
494
+ hashFile(lockPath),
495
+ hashFile(cliPath),
496
+ hashFile(packageJsonPath),
497
+ hashPrivateManagedTree(installRoot),
498
+ ]);
499
+ if (lockHash !== MANAGED_SRT_LOCK_SHA256) {
500
+ throw new Error("installed package-lock.json differs from the pinned lock");
501
+ }
502
+ if (cliHash !== marker.cliSha256 || packageJsonHash !== marker.packageJsonSha256) {
503
+ throw new Error("installed SRT content differs from its marker");
504
+ }
505
+ if (treeHash !== marker.treeSha256 || treeHash !== await trustedTreeSha256(installRoot, options)) {
506
+ throw new Error("installed SRT dependency tree differs from the independently pinned release manifest");
507
+ }
508
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
509
+ if (packageJson.name !== MANAGED_SRT_PACKAGE || packageJson.version !== MANAGED_SRT_VERSION) {
510
+ throw new Error("installed package name or version is not pinned");
511
+ }
512
+ return cliPath;
513
+ }
514
+ catch (error) {
515
+ throw new SandboxManagerError("managed_srt_corrupt", `Managed SRT install at ${installRoot} is unsafe or corrupt: ${errorMessage(error)}`, { installRoot, cause: errorMessage(error) });
516
+ }
517
+ }
518
+ async function quarantineManagedInstall(installRoot) {
519
+ const quarantine = `${installRoot}.corrupt.${Date.now()}.${randomUUID()}`;
520
+ try {
521
+ await rename(installRoot, quarantine);
522
+ }
523
+ catch (error) {
524
+ throw new SandboxManagerError("managed_srt_corrupt", `Could not quarantine corrupt managed SRT safely: ${errorMessage(error)}`, { installRoot, quarantine, cause: errorMessage(error) });
525
+ }
526
+ }
527
+ async function cleanupStaleStaging(versionRoot) {
528
+ const directory = await opendir(versionRoot);
529
+ const prefix = `.${MANAGED_SRT_LOCK_SHA256}.staging.`;
530
+ for await (const entry of directory) {
531
+ if (!entry.name.startsWith(prefix)) {
532
+ continue;
533
+ }
534
+ const path = resolve(versionRoot, entry.name);
535
+ const entryStat = await lstat(path);
536
+ if (entryStat.isSymbolicLink()
537
+ || !entryStat.isDirectory()
538
+ || (entryStat.mode & 0o077) !== 0
539
+ || (process.getuid !== undefined && entryStat.uid !== process.getuid())) {
540
+ throw new SandboxManagerError("managed_srt_corrupt", `Stale SRT staging entry is unsafe and was left untouched: ${path}`, { path });
541
+ }
542
+ await rm(path, { recursive: true, force: true });
543
+ }
544
+ }
545
+ async function acquireInstallLock(options) {
546
+ const lockPath = resolve(managedSrtVersionRoot(options), INSTALL_LOCK_NAME);
547
+ const deadline = Date.now() + INSTALL_LOCK_WAIT_MS;
548
+ while (true) {
549
+ throwIfAborted(options.signal);
550
+ const token = randomUUID();
551
+ let handle;
552
+ try {
553
+ handle = await open(lockPath, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY | (fsConstants.O_NOFOLLOW ?? 0), 0o600);
554
+ const record = {
555
+ schemaVersion: 1,
556
+ pid: process.pid,
557
+ uid: process.getuid?.() ?? null,
558
+ token,
559
+ startedAt: new Date().toISOString(),
560
+ };
561
+ await handle.writeFile(`${JSON.stringify(record)}\n`, "utf8");
562
+ await handle.sync();
563
+ const lockStat = await handle.stat({ bigint: true });
564
+ await handle.close();
565
+ return { path: lockPath, token, dev: lockStat.dev, ino: lockStat.ino };
566
+ }
567
+ catch (error) {
568
+ await handle?.close().catch(() => undefined);
569
+ if (!isNodeError(error, "EEXIST")) {
570
+ throw error;
571
+ }
572
+ }
573
+ let existing;
574
+ try {
575
+ existing = await readSecureInstallLock(lockPath);
576
+ }
577
+ catch (error) {
578
+ if (isNodeError(error, "ENOENT")) {
579
+ continue;
580
+ }
581
+ throw error;
582
+ }
583
+ const liveness = options.hooks?.processIsAlive?.(existing.record.pid) ?? processLiveness(existing.record.pid);
584
+ if (liveness === "dead") {
585
+ try {
586
+ await unlinkLockIfUnchanged(lockPath, existing);
587
+ }
588
+ catch (error) {
589
+ if (!isNodeError(error, "ENOENT")) {
590
+ throw error;
591
+ }
592
+ }
593
+ continue;
594
+ }
595
+ if (liveness === "unknown") {
596
+ throw new SandboxManagerError("managed_srt_lock_unsafe", `Cannot prove whether SRT install lock owner PID ${existing.record.pid} is alive; refusing to remove it.`, { lockPath, pid: existing.record.pid });
597
+ }
598
+ if (Date.now() >= deadline) {
599
+ throw new SandboxManagerError("managed_srt_lock_unsafe", `Another live process (PID ${existing.record.pid}) still owns the SRT install lock.`, { lockPath, pid: existing.record.pid });
600
+ }
601
+ await (options.hooks?.sleep ?? abortableSleep)(100, options.signal);
602
+ }
603
+ }
604
+ async function readSecureInstallLock(lockPath) {
605
+ let handle;
606
+ try {
607
+ handle = await open(lockPath, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
608
+ const lockStat = await handle.stat({ bigint: true });
609
+ if (!lockStat.isFile() || lockStat.nlink !== 1n || (lockStat.mode & 63n) !== 0n) {
610
+ throw new Error("lock is not an owner-only single-link regular file");
611
+ }
612
+ if (process.getuid !== undefined && lockStat.uid !== BigInt(process.getuid())) {
613
+ throw new Error("lock owner differs from the current user");
614
+ }
615
+ const content = await handle.readFile("utf8");
616
+ const record = JSON.parse(content);
617
+ if (record.schemaVersion !== 1
618
+ || !Number.isSafeInteger(record.pid)
619
+ || record.pid <= 0
620
+ || typeof record.token !== "string"
621
+ || !/^[a-f0-9-]{36}$/iu.test(record.token)
622
+ || typeof record.startedAt !== "string"
623
+ || (record.uid !== null && !Number.isSafeInteger(record.uid))
624
+ || (process.getuid !== undefined && record.uid !== process.getuid())) {
625
+ throw new Error("lock record is malformed or belongs to another user");
626
+ }
627
+ return { record, content, dev: lockStat.dev, ino: lockStat.ino };
628
+ }
629
+ catch (error) {
630
+ if (isNodeError(error, "ENOENT")) {
631
+ throw error;
632
+ }
633
+ throw new SandboxManagerError("managed_srt_lock_unsafe", `SRT install lock is unsafe and was left untouched: ${errorMessage(error)}`, { lockPath, cause: errorMessage(error) });
634
+ }
635
+ finally {
636
+ await handle?.close().catch(() => undefined);
637
+ }
638
+ }
639
+ async function unlinkLockIfUnchanged(lockPath, expected) {
640
+ const current = await readSecureInstallLock(lockPath);
641
+ if (current.dev !== expected.dev
642
+ || current.ino !== expected.ino
643
+ || current.content !== expected.content
644
+ || current.record.token !== expected.record.token) {
645
+ throw new SandboxManagerError("managed_srt_lock_unsafe", "SRT install lock changed during stale-lock verification; it was left untouched.", { lockPath });
646
+ }
647
+ await unlink(lockPath);
648
+ }
649
+ async function releaseInstallLock(lock) {
650
+ if (!(await pathExists(lock.path))) {
651
+ return;
652
+ }
653
+ let current;
654
+ try {
655
+ current = await readSecureInstallLock(lock.path);
656
+ }
657
+ catch (error) {
658
+ if (isNodeError(error, "ENOENT")) {
659
+ return;
660
+ }
661
+ throw error;
662
+ }
663
+ if (current.dev !== lock.dev || current.ino !== lock.ino || current.record.token !== lock.token) {
664
+ throw new SandboxManagerError("managed_srt_lock_unsafe", "SRT install lock identity changed; refusing to remove another process's lock.", { lockPath: lock.path });
665
+ }
666
+ await unlink(lock.path);
667
+ }
668
+ function processLiveness(pid) {
669
+ try {
670
+ process.kill(pid, 0);
671
+ return "alive";
672
+ }
673
+ catch (error) {
674
+ if (isNodeError(error, "ESRCH")) {
675
+ return "dead";
676
+ }
677
+ if (isNodeError(error, "EPERM")) {
678
+ return "alive";
679
+ }
680
+ return "unknown";
681
+ }
682
+ }
683
+ async function makeTreePrivateAndRejectLinks(root) {
684
+ const directory = await opendir(root);
685
+ for await (const entry of directory) {
686
+ const path = resolve(root, entry.name);
687
+ const entryStat = await lstat(path);
688
+ if (entryStat.isSymbolicLink()) {
689
+ throw new SandboxManagerError("managed_srt_corrupt", `Managed SRT staging tree contains an unexpected symbolic link: ${path}`, { path });
690
+ }
691
+ if (entryStat.isDirectory()) {
692
+ await chmod(path, 0o700);
693
+ await makeTreePrivateAndRejectLinks(path);
694
+ }
695
+ else if (entryStat.isFile() && entryStat.nlink === 1) {
696
+ await chmod(path, 0o600);
697
+ }
698
+ else {
699
+ throw new SandboxManagerError("managed_srt_corrupt", `Managed SRT staging tree contains an unsupported filesystem entry: ${path}`, { path });
700
+ }
701
+ }
702
+ await chmod(root, 0o700);
703
+ }
704
+ async function hashPrivateManagedTree(installRoot) {
705
+ const hash = createHash("sha256");
706
+ async function walk(directoryPath) {
707
+ const directory = await opendir(directoryPath);
708
+ const entries = [];
709
+ for await (const entry of directory) {
710
+ entries.push(entry);
711
+ }
712
+ entries.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
713
+ for (const entry of entries) {
714
+ const path = resolve(directoryPath, entry.name);
715
+ const relativePath = relative(installRoot, path);
716
+ if (relativePath === MANAGED_SRT_MARKER || relativePath === "node_modules/.package-lock.json") {
717
+ continue;
718
+ }
719
+ const entryStat = await lstat(path);
720
+ if (entryStat.isSymbolicLink()
721
+ || (process.getuid !== undefined && entryStat.uid !== process.getuid())
722
+ || (entryStat.mode & 0o077) !== 0) {
723
+ throw new Error(`unsafe managed dependency tree entry: ${relativePath}`);
724
+ }
725
+ if (entryStat.isDirectory()) {
726
+ hash.update(`D\0${relativePath}\0`);
727
+ await walk(path);
728
+ }
729
+ else if (entryStat.isFile() && entryStat.nlink === 1) {
730
+ hash.update(`F\0${relativePath}\0${entryStat.size}\0`);
731
+ hash.update(await readFile(path));
732
+ }
733
+ else {
734
+ throw new Error(`unsupported managed dependency tree entry: ${relativePath}`);
735
+ }
736
+ }
737
+ }
738
+ await walk(installRoot);
739
+ return hash.digest("hex");
740
+ }
741
+ async function trustedTreeSha256(installRoot, options) {
742
+ return options.hooks?.expectedTreeSha256 === undefined
743
+ ? MANAGED_SRT_TREE_SHA256
744
+ : await options.hooks.expectedTreeSha256(installRoot);
745
+ }
746
+ async function assertPrivateDirectory(path) {
747
+ const pathStat = await lstat(path);
748
+ if (pathStat.isSymbolicLink()
749
+ || !pathStat.isDirectory()
750
+ || (pathStat.mode & 0o077) !== 0
751
+ || (process.getuid !== undefined && pathStat.uid !== process.getuid())) {
752
+ throw new Error(`${path} must be a real owner-only directory owned by the current user`);
753
+ }
754
+ }
755
+ async function assertSingleRegularFile(path, privateFile, requireSingleLink = true) {
756
+ const pathStat = await lstat(path);
757
+ if (pathStat.isSymbolicLink()
758
+ || !pathStat.isFile()
759
+ || (requireSingleLink && pathStat.nlink !== 1)
760
+ || (privateFile && (pathStat.mode & 0o077) !== 0)) {
761
+ throw new Error(`${path} must be a ${privateFile ? "private " : ""}${requireSingleLink ? "single-link " : ""}regular file`);
762
+ }
763
+ }
764
+ async function runPrepared(engine, policy, spec, signal) {
765
+ let prepared;
766
+ try {
767
+ prepared = await engine.prepareCommand(spec, policy);
768
+ const result = await execFileAsync(prepared.command, [...prepared.args], {
769
+ cwd: prepared.cwd,
770
+ env: prepared.env === undefined ? process.env : { ...process.env, ...prepared.env },
771
+ timeout: 15_000,
772
+ signal,
773
+ maxBuffer: 1_000_000,
774
+ });
775
+ return { stdout: result.stdout, stderr: result.stderr };
776
+ }
777
+ finally {
778
+ await prepared?.cleanup?.().catch(() => undefined);
779
+ }
780
+ }
781
+ async function expectPreparedDenied(engine, policy, spec, signal) {
782
+ try {
783
+ await runPrepared(engine, policy, spec, signal);
784
+ }
785
+ catch (error) {
786
+ const candidate = error;
787
+ const evidence = [candidate.message, candidate.stdout, candidate.stderr]
788
+ .filter((value) => typeof value === "string")
789
+ .join("\n");
790
+ if (/operation not permitted|permission denied|eacces|eperm|not allowed|denied|http[^\n]*403|requested url returned error:\s*403/iu.test(evidence)) {
791
+ return;
792
+ }
793
+ throw new Error(`Command failed without sandbox-denial evidence: ${evidence || errorMessage(error)}`);
794
+ }
795
+ throw new Error(`Sandbox unexpectedly allowed: ${spec.command} ${spec.args.join(" ")}`);
796
+ }
797
+ async function writePrivateFile(path, content) {
798
+ const handle = await open(path, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY | (fsConstants.O_NOFOLLOW ?? 0), 0o600);
799
+ try {
800
+ await handle.writeFile(content, "utf8");
801
+ }
802
+ finally {
803
+ await handle.close();
804
+ }
805
+ }
806
+ function managedCliPath(installRoot) {
807
+ return resolve(installRoot, "node_modules", "@anthropic-ai", "sandbox-runtime", "dist", "cli.js");
808
+ }
809
+ function managedPackageJsonPath(installRoot) {
810
+ return resolve(installRoot, "node_modules", "@anthropic-ai", "sandbox-runtime", "package.json");
811
+ }
812
+ async function hashFile(path) {
813
+ return sha256(await readFile(path));
814
+ }
815
+ function sha256(content) {
816
+ return createHash("sha256").update(content).digest("hex");
817
+ }
818
+ function isSha256(value) {
819
+ return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
820
+ }
821
+ function assertSupportedNodeVersion(version) {
822
+ const match = /^(\d+)\.(\d+)\.(\d+)/u.exec(version);
823
+ if (match === null) {
824
+ throw new SandboxManagerError("managed_srt_install_failed", `Could not parse Node version ${version}.`);
825
+ }
826
+ const major = Number(match[1]);
827
+ const minor = Number(match[2]);
828
+ if (major < 20 || (major === 20 && minor < 11)) {
829
+ throw new SandboxManagerError("managed_srt_install_failed", `Managed SRT requires Node >=20.11.0; current Node is ${version}.`, { version });
830
+ }
831
+ }
832
+ async function pathExists(path) {
833
+ try {
834
+ await lstat(path);
835
+ return true;
836
+ }
837
+ catch (error) {
838
+ if (isNodeError(error, "ENOENT")) {
839
+ return false;
840
+ }
841
+ throw error;
842
+ }
843
+ }
844
+ async function abortableSleep(milliseconds, signal) {
845
+ await delay(milliseconds, undefined, signal === undefined ? undefined : { signal });
846
+ }
847
+ function throwIfAborted(signal) {
848
+ signal?.throwIfAborted();
849
+ }
850
+ function errorMessage(error) {
851
+ return error instanceof Error ? error.message : String(error);
852
+ }
853
+ function isNodeError(error, code) {
854
+ return error instanceof Error && error.code === code;
855
+ }
856
+ //# sourceMappingURL=sandbox-manager.js.map