@ashlr/hub 2.2.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +59 -2
  3. package/dist/api/plugin.d.ts +1 -0
  4. package/dist/cli/backlog.js +2 -0
  5. package/dist/cli/backlog.js.map +1 -1
  6. package/dist/cli/digest.d.ts +6 -0
  7. package/dist/cli/digest.js +127 -2
  8. package/dist/cli/digest.js.map +1 -1
  9. package/dist/cli/eval-fixtures.d.ts +18 -0
  10. package/dist/cli/eval-fixtures.js +38 -0
  11. package/dist/cli/eval-fixtures.js.map +1 -0
  12. package/dist/cli/eval.d.ts +56 -0
  13. package/dist/cli/eval.js +283 -0
  14. package/dist/cli/eval.js.map +1 -0
  15. package/dist/cli/fleet.d.ts +27 -0
  16. package/dist/cli/fleet.js +222 -0
  17. package/dist/cli/fleet.js.map +1 -0
  18. package/dist/cli/goal.d.ts +15 -0
  19. package/dist/cli/goal.js +91 -0
  20. package/dist/cli/goal.js.map +1 -0
  21. package/dist/cli/help.js +10 -3
  22. package/dist/cli/help.js.map +1 -1
  23. package/dist/cli/inbox.d.ts +6 -0
  24. package/dist/cli/inbox.js +183 -8
  25. package/dist/cli/inbox.js.map +1 -1
  26. package/dist/cli/index.js +42 -0
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/cli/loop.d.ts +16 -0
  29. package/dist/cli/loop.js +66 -0
  30. package/dist/cli/loop.js.map +1 -0
  31. package/dist/cli/mcp.d.ts +12 -0
  32. package/dist/cli/mcp.js +148 -0
  33. package/dist/cli/mcp.js.map +1 -1
  34. package/dist/cli/onboard.d.ts +16 -0
  35. package/dist/cli/onboard.js +71 -0
  36. package/dist/cli/onboard.js.map +1 -1
  37. package/dist/cli/plugins.js +9 -8
  38. package/dist/cli/plugins.js.map +1 -1
  39. package/dist/cli/pulse.d.ts +10 -0
  40. package/dist/cli/pulse.js +261 -0
  41. package/dist/cli/pulse.js.map +1 -1
  42. package/dist/cli/run.js +21 -3
  43. package/dist/cli/run.js.map +1 -1
  44. package/dist/cli/stack.d.ts +34 -0
  45. package/dist/cli/stack.js +221 -0
  46. package/dist/cli/stack.js.map +1 -0
  47. package/dist/cli/update.js +12 -5
  48. package/dist/cli/update.js.map +1 -1
  49. package/dist/core/daemon/loop.d.ts +10 -5
  50. package/dist/core/daemon/loop.js +167 -37
  51. package/dist/core/daemon/loop.js.map +1 -1
  52. package/dist/core/env-bridge.js +3 -2
  53. package/dist/core/env-bridge.js.map +1 -1
  54. package/dist/core/fleet/automerge-pass.d.ts +33 -0
  55. package/dist/core/fleet/automerge-pass.js +61 -0
  56. package/dist/core/fleet/automerge-pass.js.map +1 -0
  57. package/dist/core/fleet/quota.d.ts +70 -0
  58. package/dist/core/fleet/quota.js +198 -0
  59. package/dist/core/fleet/quota.js.map +1 -0
  60. package/dist/core/fleet/router.d.ts +46 -0
  61. package/dist/core/fleet/router.js +121 -0
  62. package/dist/core/fleet/router.js.map +1 -0
  63. package/dist/core/fleet/self.d.ts +58 -0
  64. package/dist/core/fleet/self.js +187 -0
  65. package/dist/core/fleet/self.js.map +1 -0
  66. package/dist/core/fleet/status.d.ts +54 -0
  67. package/dist/core/fleet/status.js +157 -0
  68. package/dist/core/fleet/status.js.map +1 -0
  69. package/dist/core/foundry/provenance.d.ts +78 -0
  70. package/dist/core/foundry/provenance.js +172 -0
  71. package/dist/core/foundry/provenance.js.map +1 -0
  72. package/dist/core/git.d.ts +13 -0
  73. package/dist/core/git.js +26 -0
  74. package/dist/core/git.js.map +1 -1
  75. package/dist/core/inbox/merge.d.ts +151 -0
  76. package/dist/core/inbox/merge.js +858 -0
  77. package/dist/core/inbox/merge.js.map +1 -0
  78. package/dist/core/inbox/store.js +10 -1
  79. package/dist/core/inbox/store.js.map +1 -1
  80. package/dist/core/integrations/markdown.d.ts +66 -0
  81. package/dist/core/integrations/markdown.js +132 -0
  82. package/dist/core/integrations/markdown.js.map +1 -0
  83. package/dist/core/integrations/secrets.d.ts +37 -0
  84. package/dist/core/integrations/secrets.js +67 -0
  85. package/dist/core/integrations/secrets.js.map +1 -0
  86. package/dist/core/integrations/stack.d.ts +47 -0
  87. package/dist/core/integrations/stack.js +107 -0
  88. package/dist/core/integrations/stack.js.map +1 -0
  89. package/dist/core/mcp-native-engineer.d.ts +123 -0
  90. package/dist/core/mcp-native-engineer.js +936 -0
  91. package/dist/core/mcp-native-engineer.js.map +1 -0
  92. package/dist/core/mcp-native.js +20 -42
  93. package/dist/core/mcp-native.js.map +1 -1
  94. package/dist/core/mcp-registry.js +2 -0
  95. package/dist/core/mcp-registry.js.map +1 -1
  96. package/dist/core/observability/codex-source.d.ts +53 -0
  97. package/dist/core/observability/codex-source.js +390 -0
  98. package/dist/core/observability/codex-source.js.map +1 -0
  99. package/dist/core/observability/estimate.d.ts +12 -0
  100. package/dist/core/observability/estimate.js +26 -0
  101. package/dist/core/observability/estimate.js.map +1 -1
  102. package/dist/core/observability/limits.d.ts +52 -0
  103. package/dist/core/observability/limits.js +316 -0
  104. package/dist/core/observability/limits.js.map +1 -0
  105. package/dist/core/observability/usage-source.js +3 -1
  106. package/dist/core/observability/usage-source.js.map +1 -1
  107. package/dist/core/portfolio/scanners.d.ts +1 -0
  108. package/dist/core/portfolio/scanners.js +40 -1
  109. package/dist/core/portfolio/scanners.js.map +1 -1
  110. package/dist/core/providers.js +4 -0
  111. package/dist/core/providers.js.map +1 -1
  112. package/dist/core/quality/health.js +1 -0
  113. package/dist/core/quality/health.js.map +1 -1
  114. package/dist/core/run/agent-loop.d.ts +8 -0
  115. package/dist/core/run/agent-loop.js +24 -14
  116. package/dist/core/run/agent-loop.js.map +1 -1
  117. package/dist/core/run/engine-registry.d.ts +48 -0
  118. package/dist/core/run/engine-registry.js +190 -0
  119. package/dist/core/run/engine-registry.js.map +1 -0
  120. package/dist/core/run/engines.d.ts +17 -3
  121. package/dist/core/run/engines.js +63 -44
  122. package/dist/core/run/engines.js.map +1 -1
  123. package/dist/core/run/learned-router.d.ts +121 -0
  124. package/dist/core/run/learned-router.js +374 -0
  125. package/dist/core/run/learned-router.js.map +1 -0
  126. package/dist/core/run/model-manager.js +3 -3
  127. package/dist/core/run/model-manager.js.map +1 -1
  128. package/dist/core/run/model-profile.d.ts +67 -0
  129. package/dist/core/run/model-profile.js +132 -0
  130. package/dist/core/run/model-profile.js.map +1 -0
  131. package/dist/core/run/orchestrator.d.ts +1 -1
  132. package/dist/core/run/orchestrator.js +642 -510
  133. package/dist/core/run/orchestrator.js.map +1 -1
  134. package/dist/core/run/prompts/budget.d.ts +25 -0
  135. package/dist/core/run/prompts/budget.js +51 -0
  136. package/dist/core/run/prompts/budget.js.map +1 -0
  137. package/dist/core/run/prompts/index.d.ts +14 -0
  138. package/dist/core/run/prompts/index.js +59 -0
  139. package/dist/core/run/prompts/index.js.map +1 -0
  140. package/dist/core/run/prompts/layers.d.ts +26 -0
  141. package/dist/core/run/prompts/layers.js +73 -0
  142. package/dist/core/run/prompts/layers.js.map +1 -0
  143. package/dist/core/run/prompts/roles.d.ts +23 -0
  144. package/dist/core/run/prompts/roles.js +48 -0
  145. package/dist/core/run/prompts/roles.js.map +1 -0
  146. package/dist/core/run/prompts/types.d.ts +38 -0
  147. package/dist/core/run/prompts/types.js +5 -0
  148. package/dist/core/run/prompts/types.js.map +1 -0
  149. package/dist/core/run/provider-client.d.ts +6 -24
  150. package/dist/core/run/provider-client.js +125 -61
  151. package/dist/core/run/provider-client.js.map +1 -1
  152. package/dist/core/run/router.js +8 -0
  153. package/dist/core/run/router.js.map +1 -1
  154. package/dist/core/run/sandboxed-engine.d.ts +66 -0
  155. package/dist/core/run/sandboxed-engine.js +262 -0
  156. package/dist/core/run/sandboxed-engine.js.map +1 -0
  157. package/dist/core/run/verify-commands.d.ts +83 -0
  158. package/dist/core/run/verify-commands.js +234 -0
  159. package/dist/core/run/verify-commands.js.map +1 -0
  160. package/dist/core/run/verify.d.ts +31 -1
  161. package/dist/core/run/verify.js +48 -0
  162. package/dist/core/run/verify.js.map +1 -1
  163. package/dist/core/sandbox/audit.d.ts +27 -0
  164. package/dist/core/sandbox/audit.js +34 -0
  165. package/dist/core/sandbox/audit.js.map +1 -1
  166. package/dist/core/sandbox/confine.d.ts +134 -0
  167. package/dist/core/sandbox/confine.js +370 -0
  168. package/dist/core/sandbox/confine.js.map +1 -0
  169. package/dist/core/types.d.ts +227 -5
  170. package/dist/core/web/api.js +77 -28
  171. package/dist/core/web/api.js.map +1 -1
  172. package/dist/core/web/control.d.ts +116 -0
  173. package/dist/core/web/control.js +307 -0
  174. package/dist/core/web/control.js.map +1 -0
  175. package/dist/core/web/public/app.js +512 -6
  176. package/dist/core/web/public/styles.css +468 -0
  177. package/package.json +1 -1
@@ -0,0 +1,370 @@
1
+ /**
2
+ * confine.ts — M52: OS-level confinement (closes v4 read-residual).
3
+ *
4
+ * Wraps the contained engine spawn with a platform-native read-jail +
5
+ * network-egress gate. ENTIRELY OPT-IN — absent cfg.foundry.confinement the
6
+ * launcher is null and spawnEngine behaves byte-identically to v4.
7
+ *
8
+ * SUPPORTED PLATFORMS:
9
+ * macOS (darwin) — sandbox-exec(1) with a custom SBPL profile.
10
+ * Linux — bwrap (bubblewrap) when on PATH; else null.
11
+ * Other — null (env-only v4 behavior).
12
+ *
13
+ * THREAT MODEL (macOS sandbox-exec):
14
+ * Goal: prevent the contained CLI from reading arbitrary paths outside its
15
+ * worktree — specifically other source trees (e.g. sibling repos) and
16
+ * secrets dirs (e.g. ~/.ssh, ~/.gnupg, ~/.aws).
17
+ *
18
+ * Strategy: (allow default) keeps the process functional (dynamic linker,
19
+ * frameworks, sockets for IPC, etc.). We then layer targeted denials:
20
+ *
21
+ * 1. (deny file-read*) — blocks all file reads by default.
22
+ * 2. (allow file-read* (subpath "<worktree>")) — re-allow the worktree.
23
+ * 3. (allow file-read* (subpath "<$HOME/.claude>")) etc. — re-allow each
24
+ * vendor config home the agent needs for its own auth (read-only).
25
+ * 4. (allow file-read* (subpath "<readAllowed[i]>")) — caller extras.
26
+ * 5. (allow file-read* (subpath "/usr") (subpath "/lib") ...)
27
+ * — allow system paths needed for the OS and CLIs to function.
28
+ * 6. (deny network-outbound*) unless networkEgress:true.
29
+ * 7. (allow file-write* (subpath "<worktree>") (subpath "<TMPDIR>")
30
+ * (subpath "<home>/.claude") ...)
31
+ * — write inside worktree + tmp + vendor config dirs (HOME_CONFIG_SUBDIRS
32
+ * + VENDOR_HOME_ENVS values) so confined agents can write session state.
33
+ *
34
+ * What this DOES NOT protect:
35
+ * - Reads of /proc, /dev, and similar pseudo-filesystems that have no
36
+ * file-read* equivalent in SBPL. These are generally safe (no user data).
37
+ * - Writes to paths outside worktree/TMPDIR that are NOT covered by the
38
+ * allow-file-write* list. Those writes ARE blocked (allow default does
39
+ * not include file-write*).
40
+ * - Against a process that can exploit the macOS sandbox itself. This is
41
+ * an OS-level trust boundary, not a VM boundary.
42
+ *
43
+ * IMPORTANT LIMITATION: sandbox-exec's SBPL `(deny file-read*)` interacts
44
+ * with macOS's TCC/SIP stack. On some system paths (Frameworks, dylibs) the
45
+ * deny fires before dyld can load, causing the wrapped binary to crash. The
46
+ * system subpath allow-list below is empirically derived to keep `cat`,
47
+ * `sh`, and major CLIs (claude, codex) functional. For production hardening,
48
+ * run the profile under `sandbox-exec -D/tmp/sbpl.profile.tmp` and check
49
+ * sandbox-violation lines in the system log.
50
+ *
51
+ * INJECTION SAFETY:
52
+ * All caller-supplied paths are escaped via escapeSbplPath() before
53
+ * embedding in the SBPL profile string. The escaper replaces `"` and `\`
54
+ * with their escaped forms so a malicious path cannot break out of the
55
+ * (subpath "...") SBPL string literal.
56
+ */
57
+ import { execFileSync } from 'node:child_process';
58
+ import { tmpdir } from 'node:os';
59
+ import { realpathSync, existsSync } from 'node:fs';
60
+ /**
61
+ * Resolve a path to its canonical realpath. macOS `sandbox-exec` resolves
62
+ * symlinks before matching (subpath ...) clauses, so e.g. a worktree under
63
+ * `/var/folders/...` (a symlink to `/private/var/folders/...`) must be emitted
64
+ * as its resolved target or the re-allow clause never matches and the agent is
65
+ * denied its own workspace. Falls back to the input when the path does not yet
66
+ * exist or cannot be resolved.
67
+ */
68
+ function resolveReal(p) {
69
+ try {
70
+ return existsSync(p) ? realpathSync(p) : p;
71
+ }
72
+ catch {
73
+ return p;
74
+ }
75
+ }
76
+ import { audit } from './audit.js';
77
+ // ---------------------------------------------------------------------------
78
+ // SBPL path escaping — injection safety
79
+ // ---------------------------------------------------------------------------
80
+ /**
81
+ * Escape a path for safe embedding in a SBPL (sandbox profile language) string
82
+ * literal. SBPL string literals are delimited by `"`. Only `"` and `\` need
83
+ * escaping — forward slashes and parens in paths are literal and valid.
84
+ *
85
+ * A path containing `)` is safe because it appears inside `"..."` — the
86
+ * parser reads the closing `"` to end the literal before looking for `)`.
87
+ *
88
+ * PROOF: sandbox-exec's parser (Scheme-like) reads `(subpath "<escaped>")`.
89
+ * The literal ends at the first unescaped `"`. After escaping, `"` → `\"` and
90
+ * `\` → `\\`, so no injected character can terminate the string early.
91
+ */
92
+ export function escapeSbplPath(p) {
93
+ return p.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
94
+ }
95
+ // ---------------------------------------------------------------------------
96
+ // macOS SBPL profile builder
97
+ // ---------------------------------------------------------------------------
98
+ /**
99
+ * System paths the agent binary needs to read on macOS. Empirically derived
100
+ * to keep sandbox-exec-wrapped CLIs functional (dyld, shells, frameworks).
101
+ */
102
+ // (M52 revision) The macOS profile no longer enumerates system read paths: an
103
+ // allow-list of dyld/framework paths is brittle across macOS versions and aborts
104
+ // the agent process. The profile instead starts from (allow default) and denies
105
+ // reads under $HOME — see buildMacosSbplProfile's threat-model note.
106
+ /**
107
+ * Vendor config-home env vars whose values (absolute paths) the agent CLI
108
+ * may need to read for its own auth/subscription. We allow reading these
109
+ * paths but NOT writing to them (except worktree + TMPDIR).
110
+ */
111
+ const VENDOR_HOME_ENVS = [
112
+ 'CODEX_HOME',
113
+ 'CLAUDE_CONFIG_DIR',
114
+ 'XDG_CONFIG_HOME',
115
+ 'XDG_DATA_HOME',
116
+ ];
117
+ /**
118
+ * Subdirectories under HOME that are standard vendor config homes when the
119
+ * dedicated env vars are absent. We allow these so agent CLIs can read their
120
+ * own on-disk auth without needing an explicit env var.
121
+ */
122
+ const HOME_CONFIG_SUBDIRS = [
123
+ '.claude',
124
+ '.config',
125
+ '.local/share',
126
+ '.hermes',
127
+ '.opencode',
128
+ '.codex',
129
+ '.npm', // node_modules cache needed by some CLIs
130
+ '.node_repl_history',
131
+ ];
132
+ /**
133
+ * Build a macOS SBPL profile string for sandbox-exec.
134
+ *
135
+ * The profile:
136
+ * (version 1)
137
+ * (allow default) — keep process functional
138
+ * (deny file-read*) — block all file reads by default
139
+ * (allow file-read* ...) — re-allow: worktree, vendor homes, system
140
+ * (deny network-outbound*) — block egress (omitted when networkEgress)
141
+ * (allow file-write* ...) — write only under worktree + TMPDIR
142
+ */
143
+ export function buildMacosSbplProfile(profile, ctx) {
144
+ const env = ctx.env ?? process.env;
145
+ const home = ctx.home ?? env.HOME ?? env.USERPROFILE ?? '';
146
+ const tmp = env.TMPDIR ?? tmpdir();
147
+ const sub = (p) => `(subpath "${escapeSbplPath(resolveReal(p))}")`;
148
+ // Subtrees the agent may READ even under HOME: its worktree, its vendor config
149
+ // homes (subscription auth/session), and any caller-supplied extras.
150
+ const reallowRead = [ctx.worktree];
151
+ for (const key of VENDOR_HOME_ENVS) {
152
+ const val = env[key];
153
+ if (val)
154
+ reallowRead.push(val);
155
+ }
156
+ if (home) {
157
+ for (const s of HOME_CONFIG_SUBDIRS)
158
+ reallowRead.push(`${home}/${s}`);
159
+ }
160
+ if (profile.readAllowed) {
161
+ for (const p of profile.readAllowed)
162
+ if (p)
163
+ reallowRead.push(p);
164
+ }
165
+ // Subtrees the agent may WRITE: its worktree, vendor config homes (session
166
+ // state, logs, auth), and tmp. HOME_CONFIG_SUBDIRS are included so that when
167
+ // CLAUDE_CONFIG_DIR/CODEX_HOME/etc. are NOT set in the env (the common case —
168
+ // e.g. claude writes session state to ~/.claude by default), the confined
169
+ // agent can still write its own config dirs. The VENDOR_HOME_ENVS values cover
170
+ // the explicit-env-var override case. Other source trees and secrets dirs that
171
+ // are NOT in HOME_CONFIG_SUBDIRS or VENDOR_HOME_ENVS remain WRITE-denied
172
+ // (as they are READ-denied); the confinement residual is unchanged.
173
+ const reallowWrite = [ctx.worktree, tmp, '/tmp', '/private/tmp'];
174
+ for (const key of VENDOR_HOME_ENVS) {
175
+ const val = env[key];
176
+ if (val)
177
+ reallowWrite.push(val);
178
+ }
179
+ if (home) {
180
+ for (const s of HOME_CONFIG_SUBDIRS)
181
+ reallowWrite.push(`${home}/${s}`);
182
+ }
183
+ const reallowReadClauses = reallowRead.map(sub).join('\n ');
184
+ const reallowWriteClauses = reallowWrite.map(sub).join('\n ');
185
+ const networkClause = profile.networkEgress
186
+ ? '(comment "network egress allowed by profile")'
187
+ : '(deny network*)';
188
+ // THREAT MODEL — the documented v4 residual is that a contained CLI can READ
189
+ // arbitrary paths outside its worktree, chiefly the user's OTHER source trees
190
+ // and secrets, which all live under $HOME (e.g. ~/Desktop/github, ~/.ssh,
191
+ // ~/.aws). We start from (allow default) so system libraries load and the agent
192
+ // CLI can actually run — an exhaustive allow-list of dyld/system paths is
193
+ // brittle across macOS versions and aborts the process (SIGABRT) — then DENY
194
+ // all reads/writes under $HOME and re-allow ONLY the worktree, the agent's own
195
+ // vendor config homes (HOME_CONFIG_SUBDIRS + VENDOR_HOME_ENVS), and caller
196
+ // extras. Vendor config dirs are BOTH read- and write-allowed so an agent can
197
+ // write session state/logs even when CLAUDE_CONFIG_DIR/CODEX_HOME are unset.
198
+ // Network egress is denied unless opted in.
199
+ // Residual: non-$HOME system paths stay readable (acceptable — no user
200
+ // repos/secrets live there); other $HOME paths (source trees, ~/.ssh, ~/.aws,
201
+ // etc.) remain denied for BOTH read and write; full VM isolation is future work.
202
+ const lines = [
203
+ '(version 1)',
204
+ '',
205
+ '; Allow default (process exec, dyld, IPC, mach) so the agent CLI can run.',
206
+ '(allow default)',
207
+ '',
208
+ '; Network egress gate.',
209
+ networkClause,
210
+ ];
211
+ if (home) {
212
+ lines.push('', '; Deny reading the user HOME (other source trees + secrets) ...', `(deny file-read* ${sub(home)})`, '; ... but re-allow the worktree, vendor config homes, and caller extras.', '(allow file-read*', ` ${reallowReadClauses}`, ')', '', '; Restrict writes under HOME to the worktree + vendor homes (+ tmp).', `(deny file-write* ${sub(home)})`, '(allow file-write*', ` ${reallowWriteClauses}`, ')');
213
+ }
214
+ else {
215
+ // No resolvable HOME: the HOME read-jail is inexpressible; degrade to the
216
+ // network gate only — still never weaker than v4's env-only containment.
217
+ lines.push('', '; No resolvable HOME — read-jail not expressible; network gate only.');
218
+ }
219
+ return lines.join('\n');
220
+ }
221
+ // ---------------------------------------------------------------------------
222
+ // Linux bwrap launcher builder
223
+ // ---------------------------------------------------------------------------
224
+ /** Check whether a binary is on PATH. Returns the binary name or null. */
225
+ function findBinary(name) {
226
+ try {
227
+ execFileSync('which', [name], { stdio: 'ignore', timeout: 3_000 });
228
+ return name;
229
+ }
230
+ catch {
231
+ return null;
232
+ }
233
+ }
234
+ /**
235
+ * Build a Linux bwrap (bubblewrap) launcher for the contained engine.
236
+ *
237
+ * Strategy: bind the root filesystem read-only, bind the worktree read-write,
238
+ * and optionally unshare the network namespace.
239
+ */
240
+ function buildBwrapLauncher(profile, ctx) {
241
+ const bwrap = findBinary('bwrap');
242
+ if (!bwrap)
243
+ return null;
244
+ const env = ctx.env ?? process.env;
245
+ const tmp = env.TMPDIR ?? tmpdir();
246
+ const args = [
247
+ // Bind root read-only (gives access to system paths).
248
+ '--ro-bind', '/', '/',
249
+ // Bind worktree read-write (agent's workspace).
250
+ '--bind', ctx.worktree, ctx.worktree,
251
+ // Bind TMPDIR read-write.
252
+ '--bind', tmp, tmp,
253
+ // New temp filesystem.
254
+ '--tmpfs', '/tmp',
255
+ // Proc/dev for process functionality.
256
+ '--proc', '/proc',
257
+ '--dev', '/dev',
258
+ // Die with parent.
259
+ '--die-with-parent',
260
+ ];
261
+ if (!profile.networkEgress) {
262
+ args.push('--unshare-net');
263
+ }
264
+ // Extra read-allowed paths: bind them read-only.
265
+ if (profile.readAllowed) {
266
+ for (const p of profile.readAllowed) {
267
+ if (p) {
268
+ args.push('--ro-bind', p, p);
269
+ }
270
+ }
271
+ }
272
+ // Separator: everything after '--' is the command to run.
273
+ args.push('--');
274
+ return { bin: bwrap, prefixArgs: args };
275
+ }
276
+ // ---------------------------------------------------------------------------
277
+ // Primary public API
278
+ // ---------------------------------------------------------------------------
279
+ /**
280
+ * Build the OS sandbox launcher for the given confinement profile and context.
281
+ *
282
+ * Returns `{ bin, prefixArgs }` that callers prepend to the engine spawn, or
283
+ * `null` when confinement is off or not supported on this platform.
284
+ *
285
+ * This function is PURE on the happy path (macOS profile string is built from
286
+ * inputs only). The only side-effect is the bwrap PATH probe on Linux and the
287
+ * audit call on fallback/unsupported.
288
+ *
289
+ * Invariant: when `profile.mode === 'off'` (or undefined), always returns null
290
+ * with no side effects — byte-identical v4 behavior.
291
+ */
292
+ export function buildSandboxLauncher(profile, ctx) {
293
+ // Flag-off: v4 env-only behavior.
294
+ if (!profile.mode || profile.mode === 'off') {
295
+ return null;
296
+ }
297
+ const platform = process.platform;
298
+ const onUnsupported = profile.onUnsupported ?? 'fallback';
299
+ if (platform === 'darwin') {
300
+ const sbplProfile = buildMacosSbplProfile(profile, ctx);
301
+ return {
302
+ bin: 'sandbox-exec',
303
+ prefixArgs: ['-p', sbplProfile],
304
+ };
305
+ }
306
+ if (platform === 'linux') {
307
+ const launcher = buildBwrapLauncher(profile, ctx);
308
+ if (launcher)
309
+ return launcher;
310
+ // bwrap absent — fall through to unsupported handling.
311
+ }
312
+ // Platform unsupported or bwrap absent.
313
+ return _handleUnsupported(profile, ctx, onUnsupported, platform);
314
+ }
315
+ function _handleUnsupported(_profile, ctx, onUnsupported, platform) {
316
+ const reason = platform === 'linux'
317
+ ? 'bwrap not found on PATH'
318
+ : `unsupported platform: ${platform}`;
319
+ if (onUnsupported === 'fail') {
320
+ throw new ConfinementUnsupportedError(`M52 confinement required but unavailable: ${reason}. ` +
321
+ `Set onUnsupported:'fallback' to allow env-only v4 behavior.`);
322
+ }
323
+ // 'fallback': audit the downgrade and return null (env-only v4).
324
+ audit({
325
+ action: 'confinement.fallback',
326
+ repo: ctx.worktree,
327
+ sandboxId: null,
328
+ summary: `OS confinement not available (${reason}); running with env-only containment (v4 behavior).`,
329
+ result: 'ok',
330
+ });
331
+ return null;
332
+ }
333
+ // ---------------------------------------------------------------------------
334
+ // confinementProfileFor — resolve effective profile from cfg
335
+ // ---------------------------------------------------------------------------
336
+ /**
337
+ * Resolve the effective ConfinementProfile for a specific engine from
338
+ * `cfg.foundry.confinement`. Per-engine key overrides the `*` (fleet-wide)
339
+ * default; absent → mode:'off' (v4 env-only, no side effects).
340
+ *
341
+ * A `*` key in cfg.foundry.confinement sets the fleet-wide default.
342
+ */
343
+ export function confinementProfileFor(engine, cfg) {
344
+ const confinement = cfg.foundry?.confinement;
345
+ if (!confinement)
346
+ return { mode: 'off' };
347
+ // Per-engine key overrides the fleet-wide `*` default.
348
+ const perEngine = confinement[engine];
349
+ const fleetDefault = confinement['*'];
350
+ const merged = {
351
+ mode: 'off',
352
+ ...fleetDefault,
353
+ ...perEngine,
354
+ };
355
+ return merged;
356
+ }
357
+ // ---------------------------------------------------------------------------
358
+ // Error type
359
+ // ---------------------------------------------------------------------------
360
+ /**
361
+ * Terminal error thrown when OS confinement is required (mode:'os',
362
+ * onUnsupported:'fail') but the platform has no supported jail binary.
363
+ */
364
+ export class ConfinementUnsupportedError extends Error {
365
+ constructor(message) {
366
+ super(message);
367
+ this.name = 'ConfinementUnsupportedError';
368
+ }
369
+ }
370
+ //# sourceMappingURL=confine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confine.js","sourceRoot":"","sources":["../../../src/core/sandbox/confine.ts"],"names":[],"mappings":"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"}