zidane 7.0.31 → 7.1.1

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 (125) hide show
  1. package/README.md +21 -2
  2. package/dist/{acp-m3TLCY_C.js → acp-B5UfEhMd.js} +4 -4
  3. package/dist/{acp-m3TLCY_C.js.map → acp-B5UfEhMd.js.map} +1 -1
  4. package/dist/acp-cli.js +6 -6
  5. package/dist/acp.d.ts +2 -2
  6. package/dist/acp.js +1 -1
  7. package/dist/{agent-9gf8ZlBR.js → agent-_xxElOgH.js} +307 -33
  8. package/dist/agent-_xxElOgH.js.map +1 -0
  9. package/dist/agent.d.ts +1 -1
  10. package/dist/agent.js +1 -1
  11. package/dist/{anthropic-B4UmPm4H.js → anthropic-DPPs3wjJ.js} +111 -7
  12. package/dist/anthropic-DPPs3wjJ.js.map +1 -0
  13. package/dist/{approvals-DaCEPCTV.js → approvals-u2T19Dc-.js} +297 -95
  14. package/dist/approvals-u2T19Dc-.js.map +1 -0
  15. package/dist/{auth-CJogE9q7.js → auth-HRKkOErn.js} +3 -3
  16. package/dist/{auth-CJogE9q7.js.map → auth-HRKkOErn.js.map} +1 -1
  17. package/dist/chat/pure.d.ts +3 -3
  18. package/dist/chat/pure.js +1 -1
  19. package/dist/chat.d.ts +6 -6
  20. package/dist/chat.js +8 -8
  21. package/dist/contexts/daytona.d.ts +3 -3
  22. package/dist/contexts/docker.d.ts +1 -1
  23. package/dist/contexts/e2b.d.ts +2 -2
  24. package/dist/contexts/sandbox.d.ts +1 -1
  25. package/dist/{contexts-BSFQVclW.js → contexts-CRE9pw07.js} +544 -17
  26. package/dist/contexts-CRE9pw07.js.map +1 -0
  27. package/dist/contexts.d.ts +4 -4
  28. package/dist/contexts.js +2 -2
  29. package/dist/daemon-cli.js +1 -1
  30. package/dist/eval.d.ts +1 -1
  31. package/dist/eval.js +3 -3
  32. package/dist/extensions.d.ts +1 -1
  33. package/dist/extensions.js +1 -1
  34. package/dist/headless.d.ts +1 -1
  35. package/dist/headless.js +3 -3
  36. package/dist/{index-B6FkK6ZN.d.ts → index-C37_ppWv.d.ts} +2 -2
  37. package/dist/index-C37_ppWv.d.ts.map +1 -0
  38. package/dist/{index-JkEO3MBV.d.ts → index-D3TTaaQ5.d.ts} +39 -7
  39. package/dist/index-D3TTaaQ5.d.ts.map +1 -0
  40. package/dist/{index-hd4zLhp8.d.ts → index-DZhrTn-v.d.ts} +307 -16
  41. package/dist/index-DZhrTn-v.d.ts.map +1 -0
  42. package/dist/index.d.ts +5 -5
  43. package/dist/index.js +8 -8
  44. package/dist/{ipc-BtN5SIc0.js → ipc-DwauKEE-.js} +3 -3
  45. package/dist/{ipc-BtN5SIc0.js.map → ipc-DwauKEE-.js.map} +1 -1
  46. package/dist/ipc.d.ts +5 -5
  47. package/dist/ipc.js +3 -3
  48. package/dist/{types-U-rIVUDw.d.ts → isolation-Kfoof0MM.d.ts} +93 -2
  49. package/dist/isolation-Kfoof0MM.d.ts.map +1 -0
  50. package/dist/mcp.d.ts +1 -1
  51. package/dist/output/stream-json.d.ts +1 -1
  52. package/dist/output/terminal.d.ts +1 -1
  53. package/dist/{plugins-B531Tz5b.js → plugins-gbI7SBA6.js} +3 -3
  54. package/dist/{plugins-B531Tz5b.js.map → plugins-gbI7SBA6.js.map} +1 -1
  55. package/dist/plugins.d.ts +1 -1
  56. package/dist/plugins.js +1 -1
  57. package/dist/presets.d.ts +1 -1
  58. package/dist/presets.js +1 -1
  59. package/dist/providers/anthropic.d.ts +2 -2
  60. package/dist/providers/anthropic.js +2 -2
  61. package/dist/providers/arcee.d.ts +1 -1
  62. package/dist/providers/baseten.d.ts +1 -1
  63. package/dist/providers/cerebras.d.ts +1 -1
  64. package/dist/providers/local.d.ts +1 -1
  65. package/dist/providers/openai-compat.d.ts +1 -1
  66. package/dist/providers/openai.d.ts +1 -1
  67. package/dist/providers/openrouter.d.ts +1 -1
  68. package/dist/providers/xai.d.ts +1 -1
  69. package/dist/providers.d.ts +2 -2
  70. package/dist/providers.js +1 -1
  71. package/dist/restate.d.ts +2 -2
  72. package/dist/restate.js +1 -1
  73. package/dist/{sandbox-CDl9tfTo.d.ts → sandbox-BH0WQkXn.d.ts} +2 -2
  74. package/dist/{sandbox-CDl9tfTo.d.ts.map → sandbox-BH0WQkXn.d.ts.map} +1 -1
  75. package/dist/{server-ktOMu5gk.js → server-DvPnPSZb.js} +8 -8
  76. package/dist/{server-ktOMu5gk.js.map → server-DvPnPSZb.js.map} +1 -1
  77. package/dist/session/sqlite.d.ts +1 -1
  78. package/dist/session/sqlite.d.ts.map +1 -1
  79. package/dist/session/sqlite.js +7 -0
  80. package/dist/session/sqlite.js.map +1 -1
  81. package/dist/{session-CVy-2mAy.js → session-BqRP1hS7.js} +33 -3
  82. package/dist/session-BqRP1hS7.js.map +1 -0
  83. package/dist/session.d.ts +1 -1
  84. package/dist/session.js +1 -1
  85. package/dist/{skills-PJyiSRCp.js → skills-BC3dodWp.js} +2 -2
  86. package/dist/{skills-PJyiSRCp.js.map → skills-BC3dodWp.js.map} +1 -1
  87. package/dist/skills.d.ts +1 -1
  88. package/dist/skills.js +1 -1
  89. package/dist/{src-CjkY6fBj.js → src-CkNQKFKr.js} +3 -3
  90. package/dist/{src-CjkY6fBj.js.map → src-CkNQKFKr.js.map} +1 -1
  91. package/dist/{tool-formatters-C4QIFCNa.d.ts → tool-formatters-C5s88MUd.d.ts} +2 -2
  92. package/dist/{tool-formatters-C4QIFCNa.d.ts.map → tool-formatters-C5s88MUd.d.ts.map} +1 -1
  93. package/dist/tools/fetch-url.d.ts +1 -1
  94. package/dist/tools/web-search.d.ts +1 -1
  95. package/dist/tools.d.ts +1 -1
  96. package/dist/tools.js +1 -1
  97. package/dist/{transcript-anchors-Bbn1HX5D.d.ts → transcript-anchors-DxofXw_y.d.ts} +3 -3
  98. package/dist/transcript-anchors-DxofXw_y.d.ts.map +1 -0
  99. package/dist/tui.d.ts +21 -28
  100. package/dist/tui.d.ts.map +1 -1
  101. package/dist/tui.js +200 -74
  102. package/dist/tui.js.map +1 -1
  103. package/dist/{turn-operations-BgqpLlOG.js → turn-operations-6JDu2ewy.js} +24 -2
  104. package/dist/{turn-operations-BgqpLlOG.js.map → turn-operations-6JDu2ewy.js.map} +1 -1
  105. package/dist/{turn-operations-BhHCpCkP.d.ts → turn-operations-BaQnxzhr.d.ts} +14 -4
  106. package/dist/{turn-operations-BhHCpCkP.d.ts.map → turn-operations-BaQnxzhr.d.ts.map} +1 -1
  107. package/dist/types-CWgENVLY.js.map +1 -1
  108. package/dist/types.d.ts +3 -3
  109. package/docs/ARCHITECTURE.md +13 -3
  110. package/docs/CHAT.md +9 -0
  111. package/docs/EXECUTION_CONTEXT.md +64 -3
  112. package/docs/INTERACTIONS.md +14 -2
  113. package/docs/SKILL.md +31 -1
  114. package/docs/TUI.md +6 -5
  115. package/package.json +1 -1
  116. package/dist/agent-9gf8ZlBR.js.map +0 -1
  117. package/dist/anthropic-B4UmPm4H.js.map +0 -1
  118. package/dist/approvals-DaCEPCTV.js.map +0 -1
  119. package/dist/contexts-BSFQVclW.js.map +0 -1
  120. package/dist/index-B6FkK6ZN.d.ts.map +0 -1
  121. package/dist/index-JkEO3MBV.d.ts.map +0 -1
  122. package/dist/index-hd4zLhp8.d.ts.map +0 -1
  123. package/dist/session-CVy-2mAy.js.map +0 -1
  124. package/dist/transcript-anchors-Bbn1HX5D.d.ts.map +0 -1
  125. package/dist/types-U-rIVUDw.d.ts.map +0 -1
@@ -1,9 +1,493 @@
1
1
  import { t as lazyAsync } from "./lazy-DLOurOC_.js";
2
2
  import { n as globLiteralPrefix, t as globFilesLocal } from "./glob-DluQFSw1.js";
3
3
  import { a as outputExceededMessage, n as appendCapped, t as DEFAULT_MAX_OUTPUT_BUFFER } from "./output-cap-C9zukcCj.js";
4
- import { dirname, isAbsolute, relative, resolve } from "node:path";
5
- import { createWriteStream } from "node:fs";
4
+ import { delimiter, dirname, isAbsolute, join, relative, resolve } from "node:path";
5
+ import { accessSync, constants, createWriteStream, realpathSync, statSync } from "node:fs";
6
+ import { homedir } from "node:os";
6
7
  import { mkdir, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
8
+ //#region src/contexts/isolation.ts
9
+ /**
10
+ * Only `/usr/bin/sandbox-exec` is trusted — never a `PATH` lookup — so a
11
+ * tampered binary earlier in `PATH` cannot silently replace the sandbox.
12
+ */
13
+ const SEATBELT_EXECUTABLE = "/usr/bin/sandbox-exec";
14
+ /**
15
+ * Credential material hidden from sandboxed commands by default. Relative
16
+ * to `$HOME`; resolved at policy-build time. Covers the usual suspects plus
17
+ * zidane's own OAuth / API-key store.
18
+ */
19
+ const DEFAULT_UNREADABLE_DIRS = [
20
+ ".ssh",
21
+ ".aws",
22
+ ".gnupg",
23
+ ".azure",
24
+ ".config/gcloud",
25
+ ".kube",
26
+ ".docker/config.json",
27
+ ".netrc",
28
+ ".zidane/credentials.json",
29
+ ".zidane/mcp-credentials.json",
30
+ ".credentials.json"
31
+ ];
32
+ /** Backend for `platform` (defaults to the current process). `null` = unsupported OS. */
33
+ function detectIsolationBackend(platform = process.platform) {
34
+ if (platform === "darwin") return "seatbelt";
35
+ if (platform === "linux") return "bwrap";
36
+ return null;
37
+ }
38
+ function isExecutable(path) {
39
+ try {
40
+ accessSync(path, constants.X_OK);
41
+ return true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+ function whichSync(name, env = process.env) {
47
+ for (const dir of (env.PATH ?? "").split(delimiter)) {
48
+ if (!dir) continue;
49
+ const candidate = join(dir, name);
50
+ if (isExecutable(candidate)) return candidate;
51
+ }
52
+ return null;
53
+ }
54
+ /**
55
+ * Whether the sandbox can actually be enforced on this host. Callers that
56
+ * requested isolation and get `ok: false` MUST fail closed (refuse to run
57
+ * the command) — silently running unsandboxed would turn a security
58
+ * setting into a placebo.
59
+ */
60
+ function isolationAvailability(platform = process.platform, env = process.env) {
61
+ const backend = detectIsolationBackend(platform);
62
+ if (backend === null) return {
63
+ ok: false,
64
+ reason: `OS sandboxing is not supported on ${platform} (macOS Seatbelt and Linux bwrap only)`
65
+ };
66
+ if (backend === "seatbelt") return isExecutable("/usr/bin/sandbox-exec") ? {
67
+ ok: true,
68
+ backend,
69
+ executable: SEATBELT_EXECUTABLE
70
+ } : {
71
+ ok: false,
72
+ reason: `${SEATBELT_EXECUTABLE} is missing or not executable`
73
+ };
74
+ const bwrap = whichSync("bwrap", env);
75
+ return bwrap ? {
76
+ ok: true,
77
+ backend,
78
+ executable: bwrap
79
+ } : {
80
+ ok: false,
81
+ reason: "bwrap (bubblewrap) is not installed — install it with your package manager (e.g. `apt install bubblewrap`)"
82
+ };
83
+ }
84
+ /** Expand a leading `~` / `~/` to `home`. Other paths returned unchanged. */
85
+ function expandHome(path, home = homedir()) {
86
+ if (path === "~") return home;
87
+ if (path.startsWith("~/")) return join(home, path.slice(2));
88
+ return path;
89
+ }
90
+ /**
91
+ * Parse the user-facing "extra writable paths" setting: comma- or
92
+ * newline-separated, `~` expanded, relative entries resolved against `cwd`,
93
+ * blanks dropped.
94
+ */
95
+ function parseAllowWriteSetting(raw, cwd, home = homedir()) {
96
+ return raw.split(/[,\n]/).map((s) => s.trim()).filter(Boolean).map((p) => resolve(cwd, expandHome(p, home)));
97
+ }
98
+ /**
99
+ * `realpath` that also works for paths that don't exist yet (an `extra`
100
+ * root the user hasn't created): the deepest existing ancestor is
101
+ * canonicalized and the missing tail re-appended. Without this, `/tmp/x`
102
+ * and `/private/tmp/x` would look like different roots on macOS.
103
+ */
104
+ function canonical(path) {
105
+ let existing = resolve(path);
106
+ const tail = [];
107
+ while (true) try {
108
+ existing = realpathSync(existing);
109
+ break;
110
+ } catch {
111
+ const parent = dirname(existing);
112
+ if (parent === existing) break;
113
+ tail.unshift(relative(parent, existing));
114
+ existing = parent;
115
+ }
116
+ return tail.length ? resolve(existing, ...tail) : existing;
117
+ }
118
+ /** True when `target` is `root` or lives under it. */
119
+ function isWithinRoot(root, target) {
120
+ const rel = relative(root, target);
121
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
122
+ }
123
+ /** Drop roots already covered by another root in the list; keeps first-seen order. */
124
+ function dedupeRoots(roots) {
125
+ const out = [];
126
+ for (const root of roots) {
127
+ if (out.some((existing) => isWithinRoot(existing, root))) continue;
128
+ for (let i = out.length - 1; i >= 0; i--) if (isWithinRoot(root, out[i])) out.splice(i, 1);
129
+ out.push(root);
130
+ }
131
+ return out;
132
+ }
133
+ /**
134
+ * Why git's common dir matters: `git rev-parse --git-common-dir` prints a
135
+ * path relative to cwd for the primary checkout (`.git`) and an absolute
136
+ * path for linked worktrees. Without the worktree's common dir writable,
137
+ * `git commit` / `git add` inside a worktree fail under the sandbox —
138
+ * the index and objects live in the main repo's `.git`.
139
+ *
140
+ * Two probes, one contract (`null` on any failure, including no `git`
141
+ * binary, a non-repo, or a slow git — a wedged git must not wedge the
142
+ * host, so slow reads as "not a worktree"):
143
+ * - {@link defaultGitCommonDir}: synchronous, for the in-process TUI whose
144
+ * `buildAgent` is sync. Blocks that window only; 1 s cap.
145
+ * - {@link defaultGitCommonDirAsync}: for the daemon, where a sync probe
146
+ * would stall every window and session on the shared event loop.
147
+ *
148
+ * `node:child_process` is resolved through `process.getBuiltinModule` (not a
149
+ * static import) for the same reason `process.ts` lazy-loads `spawn`: this
150
+ * module sits in the default execution context's import graph, and merely
151
+ * importing zidane must stay possible on shell-less runtimes (edge isolates)
152
+ * where that builtin is gated. Node 20.16+ / Bun; `undefined` → `null`.
153
+ */
154
+ const GIT_PROBE_ARGS = ["rev-parse", "--git-common-dir"];
155
+ const GIT_PROBE_SYNC_TIMEOUT_MS = 1e3;
156
+ const GIT_PROBE_ASYNC_TIMEOUT_MS = 3e3;
157
+ function childProcess() {
158
+ return process.getBuiltinModule?.("node:child_process");
159
+ }
160
+ function defaultGitCommonDir(cwd) {
161
+ try {
162
+ const cp = childProcess();
163
+ if (!cp) return null;
164
+ const out = cp.execFileSync("git", GIT_PROBE_ARGS, {
165
+ cwd,
166
+ timeout: GIT_PROBE_SYNC_TIMEOUT_MS,
167
+ encoding: "utf8",
168
+ stdio: [
169
+ "ignore",
170
+ "pipe",
171
+ "ignore"
172
+ ]
173
+ }).trim();
174
+ return out ? resolve(cwd, out) : null;
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+ function defaultGitCommonDirAsync(cwd) {
180
+ const cp = childProcess();
181
+ if (!cp) return Promise.resolve(null);
182
+ return new Promise((resolveP) => {
183
+ try {
184
+ cp.execFile("git", GIT_PROBE_ARGS, {
185
+ cwd,
186
+ timeout: GIT_PROBE_ASYNC_TIMEOUT_MS,
187
+ encoding: "utf8"
188
+ }, (err, stdout) => {
189
+ if (err) return resolveP(null);
190
+ const out = stdout.trim();
191
+ resolveP(out ? resolve(cwd, out) : null);
192
+ });
193
+ } catch {
194
+ resolveP(null);
195
+ }
196
+ });
197
+ }
198
+ /**
199
+ * The set of directories a sandboxed command may write to: the workspace
200
+ * (`cwd`), the system temp dirs, git's common dir when `cwd` is a linked
201
+ * worktree, plus any user-configured extras. All canonicalized.
202
+ */
203
+ function resolveWritableRoots(cwd, options = {}) {
204
+ return assembleWritableRoots(cwd, (options.gitCommonDir ?? defaultGitCommonDir)(cwd), options);
205
+ }
206
+ /** Async twin of {@link resolveWritableRoots} for hosts that must not block (the daemon). */
207
+ async function resolveWritableRootsAsync(cwd, options = {}) {
208
+ return assembleWritableRoots(cwd, await (options.gitCommonDir ?? defaultGitCommonDirAsync)(cwd), options);
209
+ }
210
+ function assembleWritableRoots(cwd, gitCommonDir, options) {
211
+ const env = options.env ?? process.env;
212
+ const platform = options.platform ?? process.platform;
213
+ const raw = [cwd];
214
+ if (env.TMPDIR) raw.push(env.TMPDIR);
215
+ raw.push("/tmp", "/var/tmp");
216
+ if (platform === "darwin") raw.push("/private/tmp", "/private/var/tmp");
217
+ if (gitCommonDir) raw.push(gitCommonDir);
218
+ raw.push(...options.extra ?? []);
219
+ return dedupeRoots(raw.map(canonical));
220
+ }
221
+ /** Resolve {@link DEFAULT_UNREADABLE_DIRS} (or a custom list) against `home`. */
222
+ function resolveUnreadableDirs(dirs = DEFAULT_UNREADABLE_DIRS, home = homedir()) {
223
+ return dirs.map((d) => isAbsolute(d) ? d : join(home, d));
224
+ }
225
+ /**
226
+ * Deny-default base. Derived from Codex's `seatbelt_base_policy.sbpl`
227
+ * (Apache-2.0), itself from Chromium's renderer/common sandbox policies.
228
+ * Filesystem READ is allowed broadly (below); WRITE and NETWORK are added
229
+ * per policy in {@link buildSeatbeltProfile}.
230
+ */
231
+ const SEATBELT_BASE_POLICY = `(version 1)
232
+
233
+ ; deny-default; everything below is an explicit allow
234
+ (deny default)
235
+
236
+ ; child processes inherit the policy of their parent
237
+ (allow process-exec)
238
+ (allow process-fork)
239
+ (allow signal (target same-sandbox))
240
+ (allow process-info* (target same-sandbox))
241
+
242
+ ; reads are allowed everywhere except the unreadable roots. SBPL is
243
+ ; LAST-MATCH-WINS: the deny block for those roots is emitted at the very
244
+ ; end of the profile by buildSeatbeltProfile and must stay there.
245
+ (allow file-read*)
246
+ (allow file-write* (literal "/dev/null"))
247
+ (allow file-read* file-write* (literal "/dev/stdin"))
248
+ (allow file-read* file-write* (literal "/dev/stdout"))
249
+ (allow file-read* file-write* (literal "/dev/stderr"))
250
+ (allow file-read* file-write* (literal "/dev/tty"))
251
+ (allow file-read* file-write* (literal "/dev/random"))
252
+ (allow file-read* file-write* (literal "/dev/urandom"))
253
+ (allow file-read* file-write* (literal "/dev/zero"))
254
+ (allow file-ioctl (literal "/dev/dtracehelper"))
255
+ (allow file-read* file-write* (literal "/dev/dtracehelper"))
256
+
257
+ ; sysctls
258
+ (allow sysctl-read
259
+ (sysctl-name "hw.activecpu")
260
+ (sysctl-name "hw.busfrequency_compat")
261
+ (sysctl-name "hw.byteorder")
262
+ (sysctl-name "hw.cacheconfig")
263
+ (sysctl-name "hw.cachelinesize_compat")
264
+ (sysctl-name "hw.cpufamily")
265
+ (sysctl-name "hw.cpufrequency_compat")
266
+ (sysctl-name "hw.cputype")
267
+ (sysctl-name "hw.l1dcachesize_compat")
268
+ (sysctl-name "hw.l1icachesize_compat")
269
+ (sysctl-name "hw.l2cachesize_compat")
270
+ (sysctl-name "hw.l3cachesize_compat")
271
+ (sysctl-name "hw.logicalcpu_max")
272
+ (sysctl-name "hw.machine")
273
+ (sysctl-name "hw.model")
274
+ (sysctl-name "hw.memsize")
275
+ (sysctl-name "hw.ncpu")
276
+ (sysctl-name "hw.nperflevels")
277
+ (sysctl-name-prefix "hw.optional.arm.")
278
+ (sysctl-name-prefix "hw.optional.armv8_")
279
+ (sysctl-name "hw.packages")
280
+ (sysctl-name "hw.pagesize_compat")
281
+ (sysctl-name "hw.pagesize")
282
+ (sysctl-name "hw.physicalcpu")
283
+ (sysctl-name "hw.physicalcpu_max")
284
+ (sysctl-name "hw.logicalcpu")
285
+ (sysctl-name "hw.cpufrequency")
286
+ (sysctl-name "hw.tbfrequency_compat")
287
+ (sysctl-name "hw.vectorunit")
288
+ (sysctl-name "machdep.cpu.brand_string")
289
+ (sysctl-name "kern.argmax")
290
+ (sysctl-name "kern.hostname")
291
+ (sysctl-name "kern.maxfilesperproc")
292
+ (sysctl-name "kern.maxproc")
293
+ (sysctl-name "kern.osproductversion")
294
+ (sysctl-name "kern.osrelease")
295
+ (sysctl-name "kern.ostype")
296
+ (sysctl-name "kern.osvariant_status")
297
+ (sysctl-name "kern.osversion")
298
+ (sysctl-name "kern.secure_kernel")
299
+ (sysctl-name "kern.sysv.semmns")
300
+ (sysctl-name "kern.usrstack64")
301
+ (sysctl-name "kern.version")
302
+ (sysctl-name "sysctl.proc_cputype")
303
+ (sysctl-name "vm.loadavg")
304
+ (sysctl-name-prefix "hw.perflevel")
305
+ (sysctl-name-prefix "kern.proc.pgrp.")
306
+ (sysctl-name-prefix "kern.proc.pid.")
307
+ (sysctl-name-prefix "net.routetable.")
308
+ )
309
+ ; Java CPU probe (a read that the kernel classifies as a write)
310
+ (allow sysctl-write (sysctl-name "kern.grade_cputype"))
311
+
312
+ (allow iokit-open (iokit-registry-entry-class "RootDomainUserClient"))
313
+
314
+ ; user lookup (getpwuid & co)
315
+ (allow mach-lookup (global-name "com.apple.system.opendirectoryd.libinfo"))
316
+ (allow mach-lookup (global-name "com.apple.PowerManagement.control"))
317
+
318
+ ; python multiprocessing / OpenMP
319
+ (allow ipc-posix-sem)
320
+ (allow ipc-posix-shm-read-data
321
+ ipc-posix-shm-write-create
322
+ ipc-posix-shm-write-unlink
323
+ (ipc-posix-name-regex #"^/__KMP_REGISTERED_LIB_[0-9]+$"))
324
+
325
+ ; openpty()
326
+ (allow pseudo-tty)
327
+ (allow file-read* file-write* file-ioctl (literal "/dev/ptmx"))
328
+ (allow file-read* file-write*
329
+ (require-all
330
+ (regex #"^/dev/ttys[0-9]+")
331
+ (extension "com.apple.sandbox.pty")))
332
+ (allow file-ioctl (regex #"^/dev/ttys[0-9]+"))
333
+ `;
334
+ /**
335
+ * Network allow block. Mirrors Codex's `seatbelt_network_policy.sbpl`: full
336
+ * outbound/inbound plus the mach services TLS + DNS resolution need.
337
+ */
338
+ const SEATBELT_NETWORK_POLICY = `
339
+ ; ---- network: allow ----
340
+ (allow network-outbound)
341
+ (allow network-inbound)
342
+ (allow network-bind)
343
+ (allow system-socket
344
+ (require-all
345
+ (socket-domain AF_SYSTEM)
346
+ (socket-protocol 2)))
347
+ (allow mach-lookup
348
+ (global-name "com.apple.bsd.dirhelper")
349
+ (global-name "com.apple.system.opendirectoryd.membership")
350
+ (global-name "com.apple.SecurityServer")
351
+ (global-name "com.apple.networkd")
352
+ (global-name "com.apple.ocspd")
353
+ (global-name "com.apple.trustd.agent")
354
+ (global-name "com.apple.SystemConfiguration.DNSConfiguration")
355
+ (global-name "com.apple.SystemConfiguration.configd"))
356
+ (allow sysctl-read (sysctl-name-regex #"^net.routetable"))
357
+ `;
358
+ const WRITABLE_ROOT_PARAM = "WRITABLE_ROOT_";
359
+ const UNREADABLE_ROOT_PARAM = "UNREADABLE_ROOT_";
360
+ /**
361
+ * Build the SBPL profile + params for a policy. Paths are NEVER inlined in
362
+ * the profile text: each is bound to a `-D` param and referenced via
363
+ * `(param "…")`, so spaces, quotes and parentheses in paths need no
364
+ * escaping and the profile text is identical across machines (only the
365
+ * params differ) — which makes it unit-testable as a string.
366
+ *
367
+ * ORDERING IS A SECURITY INVARIANT. Seatbelt resolves overlapping rules by
368
+ * position — the LAST matching rule wins; there is no deny-over-allow
369
+ * precedence. The credential mask (`deny file-read*` on the unreadable
370
+ * roots) only holds because it is the final block, after `(allow
371
+ * file-read*)` in the base policy, the writable-root allows and the
372
+ * network block. Never hoist or reorder it; the profile test asserts every
373
+ * `(deny` line follows every `(allow` line.
374
+ */
375
+ function buildSeatbeltProfile(policy, home = homedir()) {
376
+ const params = [];
377
+ const lines = [SEATBELT_BASE_POLICY];
378
+ lines.push("; ---- writable roots ----");
379
+ policy.writableRoots.forEach((root, i) => {
380
+ const key = `${WRITABLE_ROOT_PARAM}${i}`;
381
+ params.push([key, root]);
382
+ lines.push(`(allow file-write* (subpath (param "${key}")))`);
383
+ });
384
+ if (policy.network === "allow") lines.push(SEATBELT_NETWORK_POLICY);
385
+ else lines.push("; ---- network: deny (default deny covers all socket ops) ----");
386
+ const unreadable = resolveUnreadableDirs(policy.unreadableDirs, home);
387
+ if (unreadable.length > 0) {
388
+ lines.push("; ---- unreadable roots (must be the final block: last match wins) ----");
389
+ unreadable.forEach((dir, i) => {
390
+ const key = `${UNREADABLE_ROOT_PARAM}${i}`;
391
+ params.push([key, dir]);
392
+ lines.push(`(deny file-read* file-write* (subpath (param "${key}")))`);
393
+ });
394
+ }
395
+ return {
396
+ profile: `${lines.join("\n")}\n`,
397
+ params
398
+ };
399
+ }
400
+ /**
401
+ * bwrap argv for a policy (without the trailing command). The whole host FS
402
+ * is bound read-only, writable roots are re-bound read-write on top, and
403
+ * unreadable dirs are masked with an empty tmpfs. `--die-with-parent` ties
404
+ * the sandbox to the zidane process; no `--new-session`/`--unshare-pid` so
405
+ * the child stays in the caller's process group (the process context kills
406
+ * by `-pgid`) and `ps`/`kill` inside the sandbox behave as usual.
407
+ */
408
+ function buildBwrapArgs(policy, home = homedir(), kind = statKind) {
409
+ const args = [
410
+ "--ro-bind",
411
+ "/",
412
+ "/",
413
+ "--dev",
414
+ "/dev",
415
+ "--proc",
416
+ "/proc",
417
+ "--die-with-parent"
418
+ ];
419
+ for (const root of policy.writableRoots) args.push("--bind-try", root, root);
420
+ for (const path of resolveUnreadableDirs(policy.unreadableDirs, home)) {
421
+ const k = kind(path);
422
+ if (k === "dir") args.push("--tmpfs", path);
423
+ else if (k === "file") args.push("--ro-bind", "/dev/null", path);
424
+ }
425
+ if (policy.network === "deny") args.push("--unshare-net");
426
+ return args;
427
+ }
428
+ function statKind(path) {
429
+ try {
430
+ return statSync(path).isDirectory() ? "dir" : "file";
431
+ } catch {
432
+ return null;
433
+ }
434
+ }
435
+ /**
436
+ * Produce the `{ file, args }` that runs `/bin/sh -c command` under the
437
+ * sandbox. `home` is injectable for tests.
438
+ */
439
+ function wrapCommand(backend, executable, policy, command, home = homedir()) {
440
+ if (backend === "seatbelt") {
441
+ const { profile, params } = buildSeatbeltProfile(policy, home);
442
+ const args = ["-p", profile];
443
+ for (const [key, value] of params) args.push(`-D${key}=${value}`);
444
+ args.push("--", "/bin/sh", "-c", command);
445
+ return {
446
+ file: executable,
447
+ args
448
+ };
449
+ }
450
+ return {
451
+ file: executable,
452
+ args: [
453
+ ...buildBwrapArgs(policy, home),
454
+ "--",
455
+ "/bin/sh",
456
+ "-c",
457
+ command
458
+ ]
459
+ };
460
+ }
461
+ /**
462
+ * Patterns a sandboxed command emits when the kernel refuses an operation.
463
+ * Seatbelt surfaces `Operation not permitted` (EPERM); bwrap's read-only
464
+ * root yields `Read-only file system` (EROFS); an unshared netns gives
465
+ * DNS / connect failures that vary by tool, so only the two FS ones are
466
+ * matched — network denial is reported through `network: deny` text.
467
+ */
468
+ const DENIAL_RE = /Operation not permitted|Read-only file system|sandbox-exec: |deny\(1\)|bwrap: /i;
469
+ /**
470
+ * Append a one-line explanation to a failed result when the failure looks
471
+ * like a sandbox denial. The model otherwise sees a bare EPERM and guesses;
472
+ * this names the mechanism and the exact setting that widens it.
473
+ */
474
+ function annotateDenial(result, policy) {
475
+ if (result.exitCode === 0 || !DENIAL_RE.test(result.stderr)) return result;
476
+ const hint = `[sandbox] this command ran under zidane's OS sandbox: writes are limited to ${policy.writableRoots.join(", ")}${policy.network === "deny" ? "; network is blocked" : ""}. If the failure is a sandbox denial, ask the user to add the path under Settings → "Sandbox writable paths" or turn off "Sandbox shell commands".`;
477
+ return {
478
+ ...result,
479
+ stderr: result.stderr ? `${result.stderr}\n${hint}` : hint
480
+ };
481
+ }
482
+ /** Result returned for every command when isolation was requested but cannot be enforced. */
483
+ function unavailableResult(reason) {
484
+ return {
485
+ stdout: "",
486
+ stderr: `[sandbox] "Sandbox shell commands" is on but cannot be enforced: ${reason}. Refusing to run unsandboxed — ask the user to fix the sandbox or turn the setting off.`,
487
+ exitCode: 127
488
+ };
489
+ }
490
+ //#endregion
7
491
  //#region src/contexts/process.ts
8
492
  /**
9
493
  * Lazily load `node:child_process`'s `spawn` (memoized after first use).
@@ -113,10 +597,23 @@ function createProcessContext(config) {
113
597
  "TMPDIR"
114
598
  ]) : process.env;
115
599
  const workspaceRoot = config?.workspaceRoot !== void 0 ? resolve(defaultCwd, config.workspaceRoot) : void 0;
116
- /** True when `target` is the root itself or lives under it. */
117
- function isWithinRoot(root, target) {
118
- const rel = relative(root, target);
119
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
600
+ const isolation = config?.isolation;
601
+ const isolationState = isolation ? isolationAvailability() : void 0;
602
+ /**
603
+ * `{ file, args }` for `/bin/sh -c command`, wrapped in the sandbox when
604
+ * isolation is on. Callers check `isolationState.ok` first (the three
605
+ * shell entry points return `unavailableResult` / throw before spawning).
606
+ */
607
+ function shellCommand(command) {
608
+ if (isolation && isolationState?.ok) return wrapCommand(isolationState.backend, isolationState.executable, isolation, command);
609
+ return {
610
+ file: "/bin/sh",
611
+ args: ["-c", command]
612
+ };
613
+ }
614
+ /** Failure result when isolation was requested but cannot be enforced; `undefined` = proceed. */
615
+ function isolationBlocker() {
616
+ return isolationState && !isolationState.ok ? unavailableResult(isolationState.reason) : void 0;
120
617
  }
121
618
  /**
122
619
  * Resolve `path` against the handle cwd and, when a `workspaceRoot` is
@@ -127,6 +624,16 @@ function createProcessContext(config) {
127
624
  async function resolveContained(handle, path) {
128
625
  const full = resolve(handle.cwd, path);
129
626
  if (workspaceRoot === void 0) return full;
627
+ const canonical = await canonicalizeForCheck(full);
628
+ const canonicalRoot = await realpath(workspaceRoot).catch(() => workspaceRoot);
629
+ if (!isWithinRoot(canonicalRoot, canonical)) throw new Error(`Path escapes the workspace root: ${path} (resolved outside ${canonicalRoot})`);
630
+ return full;
631
+ }
632
+ /**
633
+ * Canonicalize the deepest existing ancestor of `full` to defeat symlink
634
+ * escapes; the non-existent tail (for writes) is appended back verbatim.
635
+ */
636
+ async function canonicalizeForCheck(full) {
130
637
  let existing = full;
131
638
  const tail = [];
132
639
  while (true) try {
@@ -141,10 +648,18 @@ function createProcessContext(config) {
141
648
  tail.unshift(relative(parent, existing));
142
649
  existing = parent;
143
650
  }
144
- const canonical = tail.length ? resolve(existing, ...tail) : existing;
145
- const canonicalRoot = await realpath(workspaceRoot).catch(() => workspaceRoot);
146
- if (!isWithinRoot(canonicalRoot, canonical)) throw new Error(`Path escapes the workspace root: ${path} (resolved outside ${canonicalRoot})`);
147
- return full;
651
+ return tail.length ? resolve(existing, ...tail) : existing;
652
+ }
653
+ /**
654
+ * In-process counterpart of the sandbox's write policy: `writeFile` (and
655
+ * so the `write_file` / `edit` tools) may only target `isolation.writableRoots`.
656
+ * Reads are deliberately unrestricted — same boundary the kernel enforces
657
+ * for shell commands, so the two never disagree about where writes land.
658
+ */
659
+ async function assertWritable(full, path) {
660
+ if (!isolation) return;
661
+ const canonical = await canonicalizeForCheck(full);
662
+ if (!isolation.writableRoots.some((root) => isWithinRoot(root, canonical))) throw new Error(`[sandbox] write denied: ${path} is outside the writable roots (${isolation.writableRoots.join(", ")}). Ask the user to add it under Settings → "Sandbox writable paths" or turn off "Sandbox shell commands".`);
148
663
  }
149
664
  /**
150
665
  * Per-context background-task registry. Entries live for the context's
@@ -224,6 +739,7 @@ function createProcessContext(config) {
224
739
  network: true,
225
740
  gpu: false,
226
741
  stream: true,
742
+ isolated: isolation !== void 0 && isolationState?.ok === true,
227
743
  detachedTasks: "process-lifetime"
228
744
  },
229
745
  async spawn(overrides) {
@@ -251,11 +767,14 @@ function createProcessContext(config) {
251
767
  stderr: "aborted by signal before spawn",
252
768
  exitCode: 143
253
769
  };
770
+ const blocked = isolationBlocker();
771
+ if (blocked) return blocked;
254
772
  const timeoutMs = (options?.timeout ?? config?.limits?.timeout ?? 30) * 1e3;
255
773
  const maxBuffer = DEFAULT_MAX_OUTPUT_BUFFER;
256
774
  const spawnChild = await loadSpawn();
257
775
  return new Promise((resolveP) => {
258
- const child = spawnChild("/bin/sh", ["-c", command], {
776
+ const shell = shellCommand(command);
777
+ const child = spawnChild(shell.file, shell.args, {
259
778
  cwd,
260
779
  env: {
261
780
  ...baseEnv,
@@ -313,11 +832,12 @@ function createProcessContext(config) {
313
832
  if (exitGraceTimer) clearTimeout(exitGraceTimer);
314
833
  if (userSignal) userSignal.removeEventListener("abort", onAbort);
315
834
  const finalStderr = extraStderr ? stderrSlot.text ? `${stderrSlot.text}\n${extraStderr}` : extraStderr : stderrSlot.text;
316
- resolveP({
835
+ const result = {
317
836
  stdout: stdoutSlot.text,
318
837
  stderr: finalStderr,
319
838
  exitCode
320
- });
839
+ };
840
+ resolveP(isolation ? annotateDenial(result, isolation) : result);
321
841
  };
322
842
  const settleFromStatus = (code, signal) => {
323
843
  if (killedByAbort) {
@@ -370,6 +890,7 @@ function createProcessContext(config) {
370
890
  },
371
891
  async writeFile(handle, path, content, encoding = "utf-8") {
372
892
  const fullPath = await resolveContained(handle, path);
893
+ await assertWritable(fullPath, path);
373
894
  await mkdir(dirname(fullPath), { recursive: true });
374
895
  if (typeof content === "string") await writeFile(fullPath, content, encoding);
375
896
  else await writeFile(fullPath, content);
@@ -414,7 +935,10 @@ function createProcessContext(config) {
414
935
  const cwd = options?.cwd ? resolve(handle.cwd, options.cwd) : handle.cwd;
415
936
  const spawnChild = await loadSpawn();
416
937
  options?.signal?.throwIfAborted();
417
- const child = spawnChild("/bin/sh", ["-c", command], {
938
+ const blocked = isolationBlocker();
939
+ if (blocked) throw new Error(blocked.stderr);
940
+ const shell = shellCommand(command);
941
+ const child = spawnChild(shell.file, shell.args, {
418
942
  cwd,
419
943
  env: {
420
944
  ...baseEnv,
@@ -477,6 +1001,8 @@ function createProcessContext(config) {
477
1001
  async execBackground(handle, command, options) {
478
1002
  const cwd = options.cwd ? resolve(handle.cwd, options.cwd) : handle.cwd;
479
1003
  const spawnChild = await loadSpawn();
1004
+ const blocked = isolationBlocker();
1005
+ if (blocked) throw new Error(blocked.stderr);
480
1006
  await mkdir(options.outputDir, { recursive: true });
481
1007
  const taskId = `bash_${++taskCounter}`;
482
1008
  assertSafeTaskId(taskId);
@@ -486,7 +1012,8 @@ function createProcessContext(config) {
486
1012
  outputStream.on("error", (err) => {
487
1013
  if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/contexts] task ${taskId} log stream error: ${err.message}\n`);
488
1014
  });
489
- const child = spawnChild("/bin/sh", ["-c", command], {
1015
+ const shell = shellCommand(command);
1016
+ const child = spawnChild(shell.file, shell.args, {
490
1017
  cwd,
491
1018
  env: {
492
1019
  ...baseEnv,
@@ -785,6 +1312,6 @@ function resolveDetachedTasksCapability(context) {
785
1312
  return context.capabilities.detachedTasks ?? (context.execBackground ? "process-lifetime" : "none");
786
1313
  }
787
1314
  //#endregion
788
- export { killProcessGroup as i, SUPPORTS_PROCESS_GROUPS as n, createProcessContext as r, resolveDetachedTasksCapability as t };
1315
+ export { DEFAULT_UNREADABLE_DIRS as a, resolveUnreadableDirs as c, killProcessGroup as i, resolveWritableRoots as l, SUPPORTS_PROCESS_GROUPS as n, isolationAvailability as o, createProcessContext as r, parseAllowWriteSetting as s, resolveDetachedTasksCapability as t, resolveWritableRootsAsync as u };
789
1316
 
790
- //# sourceMappingURL=contexts-BSFQVclW.js.map
1317
+ //# sourceMappingURL=contexts-CRE9pw07.js.map