toolroll 0.9.68 → 0.9.70

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 (78) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +43 -17
  3. package/THIRD_PARTY_NOTICES.md +21 -1
  4. package/dist/agent-fence.d.ts +204 -81
  5. package/dist/agent-fence.js +467 -397
  6. package/dist/agentconfig.d.ts +1 -1
  7. package/dist/agentconfig.js +11 -2
  8. package/dist/batch-checks.js +14 -26
  9. package/dist/browser/THIRD_PARTY_NOTICES.txt +21 -0
  10. package/dist/builder.d.ts +9 -0
  11. package/dist/builder.js +50 -4
  12. package/dist/check-access.d.ts +51 -0
  13. package/dist/check-access.js +77 -0
  14. package/dist/coding-provider.d.ts +14 -0
  15. package/dist/coding-provider.js +33 -1
  16. package/dist/coding-workspace.d.ts +15 -0
  17. package/dist/coding-workspace.js +9 -2
  18. package/dist/contracts/console-api.d.ts +2 -0
  19. package/dist/contracts/console-api.js +2 -0
  20. package/dist/doctor.d.ts +7 -0
  21. package/dist/doctor.js +4 -0
  22. package/dist/egress.d.ts +2 -0
  23. package/dist/egress.js +13 -4
  24. package/dist/exec.d.ts +17 -3
  25. package/dist/exec.js +54 -17
  26. package/dist/flow-actions.d.ts +1 -0
  27. package/dist/flow-actions.js +3 -2
  28. package/dist/flow-code.d.ts +31 -2
  29. package/dist/flow-code.js +57 -5
  30. package/dist/flow-steps.d.ts +4 -0
  31. package/dist/flow-steps.js +9 -5
  32. package/dist/flow-triggers.d.ts +4 -0
  33. package/dist/flow-triggers.js +8 -3
  34. package/dist/invoke.d.ts +50 -3
  35. package/dist/invoke.js +204 -66
  36. package/dist/operate.d.ts +4 -0
  37. package/dist/operate.js +119 -0
  38. package/dist/project-delete-ui.d.ts +23 -6
  39. package/dist/project-delete-ui.js +94 -1
  40. package/dist/project-sandbox.d.ts +59 -0
  41. package/dist/project-sandbox.js +83 -0
  42. package/dist/project-tools.d.ts +29 -5
  43. package/dist/project-tools.js +43 -7
  44. package/dist/release.d.ts +1 -1
  45. package/dist/release.js +1 -0
  46. package/dist/result-follow-ups.js +10 -15
  47. package/dist/sandbox-launch.d.ts +1 -0
  48. package/dist/sandbox-launch.js +148 -0
  49. package/dist/sandbox-runtime.d.ts +62 -0
  50. package/dist/sandbox-runtime.js +140 -0
  51. package/dist/scope.d.ts +37 -0
  52. package/dist/scope.js +32 -5
  53. package/dist/scout-browser-lock.d.ts +69 -0
  54. package/dist/scout-browser-lock.js +69 -0
  55. package/dist/scout-browser.d.ts +60 -1
  56. package/dist/scout-browser.js +200 -4
  57. package/dist/scout.d.ts +9 -4
  58. package/dist/scout.js +14 -4
  59. package/dist/serve.js +12 -1
  60. package/dist/server/http.d.ts +2 -0
  61. package/dist/server/render-results.d.ts +3 -0
  62. package/dist/server/render-results.js +1 -0
  63. package/dist/server/render-tasks.js +5 -1
  64. package/dist/server/route-table.js +2 -0
  65. package/dist/server/settings.js +64 -0
  66. package/dist/server/tasks.js +2 -0
  67. package/dist/store.d.ts +59 -2
  68. package/dist/store.js +166 -4
  69. package/dist/subagent-tools.d.ts +1 -0
  70. package/dist/subagent-tools.js +7 -5
  71. package/dist/surface.js +2 -1
  72. package/dist/tool-broker.d.ts +2 -1
  73. package/dist/tool-broker.js +2 -2
  74. package/dist/tool-launcher.js +10 -16
  75. package/package.json +2 -1
  76. package/scripts/provider-canary.mjs +5 -6
  77. package/dist/egress-bridge.d.ts +0 -17
  78. package/dist/egress-bridge.js +0 -111
package/CHANGELOG.md CHANGED
@@ -1,5 +1,86 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.70 — 2026-10-24
4
+
5
+ - **Every sandbox is now Anthropic's sandbox-runtime.** Builds, their setup
6
+ and check commands, a saved result's checks, flows and project tool servers
7
+ all run in `@anthropic-ai/sandbox-runtime` 0.0.79, the same sandbox Claude
8
+ Code uses: its own code writes the macOS Seatbelt profile and the Linux
9
+ bubblewrap and seccomp command for each launch. Toolroll hands it only a
10
+ short list: its own folders and database are off limits, the build's
11
+ checkout is writable, other tasks' checkouts are read and never written,
12
+ your project's read-deny list, and the run's proxy as the only way out.
13
+ `toolroll doctor` and Settings → Project → Home folder name the engine and
14
+ its version. Codex builds keep Codex's own sandbox with the same rules.
15
+ - **Checks that start Chrome don't run sandboxed on macOS yet.** The sandbox
16
+ runtime has no setting for the service Chromium registers at startup, so a
17
+ Playwright or browser check fails inside it. Until a follow-up adds that one
18
+ rule, set such a project's check access to **Not sandboxed** in
19
+ Settings → Project → Check access.
20
+ - **Your home folder is readable to new scopes, as in the sandbox runtime.**
21
+ A project hides what it wants back with Settings → Project → **Home
22
+ folder**, or `toolroll project sandbox --repo <p> --read-deny "~/.ssh"`.
23
+ Each approved scope seals its list. Scopes approved before 0.9.70 keep
24
+ hiding your credentials and agent settings, as they were approved.
25
+ - **The macOS services a project's builds may also reach** (`--mach`, for Go
26
+ tools that verify TLS through trustd) seal only on a person's password.
27
+ Approval cards and the CLI's scope view say what a scope reads and which
28
+ services it adds.
29
+ - **Checks can use your Claude, Codex and GitHub sign-ins again.** The
30
+ "Sandboxed, with model and GitHub sign-ins" level is on: a check reads
31
+ exactly those files, by name, inside its sandbox, never through a link.
32
+ - Flows, tool servers started outside a build and interactive Codex coding
33
+ sessions now run in the same sandbox as builds. A machine that can't
34
+ sandbox runs none of them unless an instance operator allowed unconfined
35
+ agents.
36
+ - On Linux the sandbox needs bubblewrap, socat and ripgrep; without them
37
+ nothing starts (as before, unless unconfined agents are allowed).
38
+ - A result that adds or changes a Git hook or config, editor or Claude
39
+ settings, a shell profile, `.envrc`, `.npmrc` or `.mcp.json` is refused at
40
+ the handoff, with its work kept. Toolroll's own install and the sandbox
41
+ runtime can't be written from inside a sandbox.
42
+ - The scout's browser runs outside the sandbox, as an explicit exception: a
43
+ pinned Playwright release installed from a lockfile into Toolroll's own
44
+ folder, with exactly the arguments and environment it was approved with.
45
+ - Limits that come with the sandbox runtime: rules are fixed when a launch
46
+ starts, concurrent builds share its temp folder, and on macOS a build can
47
+ read another process's arguments and environment. Write unlocks for
48
+ protected files are gone (the runtime can't grant them).
49
+
50
+ ## 0.9.69 — 2026-10-23
51
+
52
+ - **Choose how far a project's checks reach.** Settings → Project has a new
53
+ Check access section, and `toolroll project check-access --repo <p>` shows
54
+ or sets the same thing. **Sandboxed** (the default) runs a project's setup
55
+ and check commands in the plane's fence, as before. **Not sandboxed** runs
56
+ them with your full access, as before 0.9.66: for a project whose checks
57
+ must create sandboxes themselves, such as Toolroll's own.
58
+ - Widening takes an instance operator with their password typed now (a
59
+ remembered sign-in or a single sign-on recheck doesn't count). Any approver
60
+ for the project can narrow it back. Each change is in the ledger.
61
+ - The level is part of what you approve. A scope seals it, the approval card
62
+ says "Checks: not sandboxed", and tasks already approved keep the level
63
+ they were approved with. Each run records the level its checks actually ran
64
+ at, and the result page says when they ran without a sandbox. Later checks
65
+ of a saved result never run wider than that run did. A mode, a grant or a
66
+ chat confirmation can't approve a wider level; only a person's password can.
67
+ - **A third level, "Sandboxed, with model and GitHub sign-ins", is built but
68
+ off.** It would let a check read your Claude, Codex and GitHub sign-in files.
69
+ Reading those files safely needs a way to open a file that a running build
70
+ can't redirect partway (Node can't do that yet), so the option is shown
71
+ disabled with the reason. Choosing it is refused, and a level saved before
72
+ runs sandboxed and says so.
73
+ - **The fence holds your home configuration by name.** `~/.codex`,
74
+ `~/.config/gh`, `~/.ssh` and the rest are denied whether or not they exist
75
+ when a build starts, so a build can't create one or swap it for a link
76
+ later. The folders above them can't be renamed or made during a build.
77
+ - A check's temporary folder on a Mac is now short (under `/private/tmp`), so
78
+ tests that open a unix socket there work inside the fence. Other launches'
79
+ folders stay out of reach.
80
+ - With no OS sandbox on the machine and unconfined running allowed, the
81
+ settings page and `project check-access` say checks run without a sandbox,
82
+ whatever level is saved.
83
+
3
84
  ## 0.9.68 — 2026-10-23
4
85
 
5
86
  - **The lead chat is redesigned.** Your messages sit in grey bubbles and the
package/README.md CHANGED
@@ -565,24 +565,47 @@ Use Full access only for repositories and setup commands you trust.
565
565
 
566
566
  ### What an agent can reach
567
567
 
568
- Agents run as your own user, so Toolroll fences its own secrets off
569
- from them at the operating system, in every permission mode. The fence covers
570
- the state folder beside the database (your remembered login `up-login.txt`,
571
- runner and coordinator tokens, chat bot tokens, the database and its backups,
572
- other runs' evidence) — everything there except the build's own worktree —
573
- and `~/.toolroll` (stored provider keys and project tool secrets).
574
-
575
- Seatbelt and bubblewrap deny the whole checkout pool and allow the build’s own
576
- checkout back, covering other projects created after launch too. Codex keeps
577
- the own checkout’s ancestors traversable and denies each other project folder
578
- found in trusted checkout pools at launch. A project added after a Codex build
579
- starts is covered on the next launch. Own Git data, linked shared dependencies
580
- and launch tools remain available.
568
+ #### Sandboxing: built on Anthropic's sandbox-runtime (v0.0.79)
569
+
570
+ Every fenced launch (a build's agent, its setup and check commands, a saved
571
+ result's checks, a flow's setup and script, a project tool's server) runs inside
572
+ [Anthropic's sandbox-runtime](https://github.com/anthropics/sandbox-runtime)
573
+ (`@anthropic-ai/sandbox-runtime`, pinned to 0.0.79): the same sandbox Claude Code
574
+ uses. Its own code writes the macOS Seatbelt profile and the Linux bubblewrap and
575
+ seccomp command for each launch, with its baseline, its protected names and its
576
+ limits; Toolroll only hands it a short config. `toolroll doctor` and Settings →
577
+ Project → **Home folder** name the engine and its version. Where it can't run
578
+ (Windows; Linux without bubblewrap, socat or ripgrep, or with unprivileged user
579
+ namespaces off), nothing starts unless an instance operator allows unconfined
580
+ agents (`toolroll config unconfined-agents allow`). Codex builds use Codex's own
581
+ sandbox, given the same rules.
582
+
583
+ What Toolroll adds to the runtime's config, and nothing else:
584
+
585
+ - **Toolroll's own folders** are denied to read and write: the state folder
586
+ beside the database (your remembered login `up-login.txt`, runner and
587
+ coordinator tokens, chat bot tokens, the database and its backups, other runs'
588
+ evidence) except the checkout pool, and `~/.toolroll` (stored provider keys and
589
+ project tool secrets); other launches' private folders are unread.
590
+ - **The build's own checkout** is writable; **other tasks' checkouts** are read,
591
+ never written (those there when it starts).
592
+ - **Your home folder** is readable, as in the runtime (a scope approved before
593
+ 0.9.70 keeps hiding your credentials and agent settings, as it was approved). A
594
+ project hides more with Settings → Project → **Home folder**, or `toolroll
595
+ project sandbox --repo <p> --read-deny "~/.ssh,~/.aws"`; each approved scope
596
+ seals that list.
597
+ - **The network**: open, or only through the run's own proxy (below).
598
+
599
+ The runtime's limits are accepted as they are: rules are fixed when a launch
600
+ starts; on Linux a deny for a path that isn't there yet is the runtime's to handle;
601
+ concurrent builds share the system temp folder; another process's arguments and
602
+ environment can be read on macOS; and a folder or checkout made after a build
603
+ starts is writable to it unless something above denies it.
581
604
 
582
605
  | | Auto | Full access |
583
606
  |---|---|---|
584
- | **Codex / OpenRouter** (any OS) | Codex's sandbox with the fence: writes only the checkout, its Git data and the project's caches (anywhere outside the fence, under a project set to write anywhere); no network | the same writes, with the fence; its commands reach the network only when the project's network is open |
585
- | **Claude, Gemini, ACP agents** | macOS: inside a Seatbelt sandbox that denies the fence and writes only the checkout, its Git data, the project's caches, the run's temp folder and the agent's own session files. Linux: the same in bubblewrap, with a /tmp of its own. Neither (Windows, or Linux without bubblewrap): the build doesn't start, unless an instance operator allows unconfined agents | same |
607
+ | **Claude, Gemini, ACP agents** | Anthropic's sandbox-runtime (Seatbelt on macOS; bubblewrap and its seccomp filter on Linux, which also needs socat and ripgrep): reads your home folder except the folders the project lists, never Toolroll's folders; other tasks' work read, never written; writes only the checkout, its Git data, the project's caches, the run's temp folder and the agent's own session files (anywhere outside the fence, under a project set to write anywhere), never the runtime's protected names in the checkout (Git hooks and config, shell profiles, `.mcp.json`, editor settings); no Apple Events. Neither (Windows, or a Linux missing one of those): the build doesn't start, unless an instance operator allows unconfined agents | same |
608
+ | **Codex / OpenRouter** (any OS) | Codex's own sandbox, given the same fence: reads your home folder except the folders the project lists, never Toolroll's folders; other tasks' work read, never written; writes only the checkout, its Git data and the project's caches (anywhere outside the fence, under a project set to write anywhere); no network | the same reads and writes, with the fence; its commands reach the network only when the project's network is open |
586
609
 
587
610
  **Where a build writes.** Each approved scope also seals where its build may
588
611
  write. By default (Settings → Project → **Where builds write**, or `toolroll
@@ -618,8 +641,9 @@ mode** an agent's own provider key is in its environment, so its shell can
618
641
  read it (subscription mode, the default, puts no key there); and a project's
619
642
  own tool secrets reach that project's tools. They stay in a per-run broker
620
643
  outside the fence: the agent gets only its address and a token that stops
621
- working when the run ends, and on macOS it can't read another process's
622
- arguments or environment. Reviews and the lead chat run with no tools and are
644
+ working when the run ends. A tool server's own environment (its secrets among it)
645
+ is readable to a build on macOS through the process list, a limit of the sandbox
646
+ runtime's baseline. Reviews and the lead chat run with no tools and are
623
647
  confined to their own files. See [SECURITY.md](SECURITY.md).
624
648
 
625
649
  ### How build sessions are protected
@@ -1198,6 +1222,8 @@ about seven minutes, and writes `report.md`, screenshots and both logs to
1198
1222
 
1199
1223
  ## Credits
1200
1224
 
1225
+ Every sandbox Toolroll runs is [Anthropic's sandbox-runtime](https://github.com/anthropics/sandbox-runtime) (Apache-2.0), used as a dependency; see THIRD_PARTY_NOTICES.md.
1226
+
1201
1227
  The workflow this formalizes comes from [Jason Ku's agentic engineering session](https://youtu.be/Ukju3maxbEQ) and his [`agents-md-snippets`](https://github.com/jasonku09/agents-md-snippets), plus Kun Chen's `treehouse`, `no-mistakes`, `gnhf`, `tasks-axi`, and `axi`. The design was reviewed adversarially by Codex; the appendix in `docs/DESIGN.md` lists every claim that review falsified, because the corrections are more useful than a clean spec would have been.
1202
1228
 
1203
1229
  ## License
@@ -487,4 +487,24 @@ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
487
487
  INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
488
488
  DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
489
489
  FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
490
- OTHER DEALINGS IN THE FONT SOFTWARE.
490
+ OTHER DEALINGS IN THE FONT SOFTWARE.
491
+
492
+ ## Anthropic sandbox-runtime — Apache-2.0
493
+
494
+ Copyright Anthropic PBC. https://github.com/anthropics/sandbox-runtime
495
+
496
+ `@anthropic-ai/sandbox-runtime` 0.0.79 is a dependency, installed whole from npm with its own `LICENSE` (and its
497
+ prebuilt Linux seccomp helpers, `vendor/seccomp/<arch>/apply-seccomp`). Toolroll calls it for every sandbox it runs
498
+ (`src/sandbox-runtime.ts`, `src/sandbox-launch.ts`) and copies or modifies none of its code.
499
+
500
+ Licensed under the Apache License, Version 2.0 (the "License");
501
+ you may not use this file except in compliance with the License.
502
+ You may obtain a copy of the License at
503
+
504
+ http://www.apache.org/licenses/LICENSE-2.0
505
+
506
+ Unless required by applicable law or agreed to in writing, software
507
+ distributed under the License is distributed on an "AS IS" BASIS,
508
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
509
+ See the License for the specific language governing permissions and
510
+ limitations under the License.
@@ -1,6 +1,6 @@
1
1
  import type { LoopbackMode } from "./egress.js";
2
- /** Beside the database: where Linux builds' network bridges are made (its name reads as secret wherever it is). */
3
- export declare const BRIDGE_DIR = "bridge-secrets";
2
+ import type { ExecutionProfile } from "./scope.js";
3
+ import { type SrtTerms } from "./sandbox-runtime.js";
4
4
  /** How a launch was fenced, as recorded on the run and said on the task. */
5
5
  export type FenceMethod = "codex-profile" | "macos-sandbox" | "linux-bubblewrap" | "claude-rules" | "none";
6
6
  /** Only the plane's recorded repository may supply an own-Git exemption. */
@@ -38,6 +38,9 @@ export type TaskBoundary = {
38
38
  fileTools: string[];
39
39
  readOnly: string[];
40
40
  lookup: string[];
41
+ /** Every home configuration name the launch may not reach, by name, whether or not it exists now (macOS: Seatbelt
42
+ * denies a path that isn't there yet, so a build can't create it, or later replace it with a link). */
43
+ homeNames?: string[];
41
44
  };
42
45
  /**
43
46
  * Everything outside a task's own folders that a build could otherwise
@@ -52,26 +55,30 @@ export declare function taskBoundary(options: {
52
55
  provider: string;
53
56
  home?: string;
54
57
  binding?: WorktreeBinding | null;
58
+ policy?: FencePolicy;
55
59
  }): TaskBoundary;
56
- /** Where launches keep their private folders (the system temp folder), and this launch's own tool folder. */
60
+ /** Where launches keep their private folders: `root` (the system temp folder) and, on a Mac, `also` the short shared one
61
+ * a check's launch folder is made in (/private/tmp, so its unix sockets fit); `own` is this launch's. */
57
62
  export type RunFolders = {
58
63
  root: string;
59
64
  own: string | null;
65
+ also?: string[];
60
66
  };
61
- /** The prefixes of every launch's private folders: its tools' config, a Codex build's own home, and a Linux build's
62
- * network bridge (egress-bridge.ts). */
67
+ /** The prefixes of every launch's private folders: its tools' config and a Codex build's own home (and a check's). */
63
68
  export declare const RUN_FOLDER_PREFIXES: readonly ["so-tools-", "so-codex-", "so-net-", "so-cmd-"];
64
69
  export declare function runFolders(own: string | null, root?: string): RunFolders;
70
+ /** Where a check's launch folder is made on a Mac: a short shared folder, so its TMPDIR leaves room for a unix socket
71
+ * path (about 104 bytes). */
72
+ export declare const CHECK_LAUNCH_ROOT_MACOS = "/private/tmp";
73
+ /** The folder a check's launch folder is made in: on a Mac the short one, elsewhere the system temp folder. */
74
+ export declare const checkLaunchRoot: () => string;
75
+ /** Every root launch folders are made under (the system temp folder, and on a Mac the short shared one). */
76
+ export declare const runRoots: (folders: RunFolders) => string[];
65
77
  /**
66
- * What a fence gives back beside its denied paths. Each is narrow and none
67
- * ever writes: `readOnly` is read, never written (other tasks' git
68
- * metadata); `readable` is read and run inside a denied folder (the shared
69
- * dependencies this checkout links); `lookup` is only looked up, never
70
- * listed or read (the deps root and the copy linked, other tasks' checkout
71
- * roots); `runFolders` denies every launch folder in the temp folder but
72
- * this launch's own (macOS: by pattern, so a later one is denied too);
73
- * `pool` denies the plane's checkout pools whole and gives this build's own
74
- * checkout back (CheckoutPool).
78
+ * What a fence gives back beside its denied paths. `readOnly` is read, never written (other tasks' git metadata, the
79
+ * primary checkout under v2); `readable` is read inside a denied folder (the shared dependencies this checkout links);
80
+ * `lookup` is kept for Codex (looked up, never listed); `runFolders` names this launch's own private folder and where
81
+ * the others are; `pool` denies the plane's checkout pools around this build's own checkout (CheckoutPool).
75
82
  */
76
83
  export type FenceGrants = {
77
84
  readOnly: string[];
@@ -81,6 +88,14 @@ export type FenceGrants = {
81
88
  pool?: CheckoutPool;
82
89
  network?: NetworkFence;
83
90
  writes?: WriteAllowlist;
91
+ /** Policy v1: the home configuration names denied by name, present or not (TaskBoundary.homeNames). */
92
+ homeNames?: string[];
93
+ /** macOS: the project's sealed mach services beyond the runtime's baseline (its `allowMachLookup`). */
94
+ machLookup?: string[];
95
+ /** A `sign-ins` check's exact sign-in files, read again inside the home denies (check-access.ts, signInGrants). */
96
+ signIns?: SignInGrants;
97
+ /** Never written, wherever the launch writes (homeWriteDenies): what runs for the operator next or signs them in. */
98
+ writeDenies?: string[];
84
99
  };
85
100
  export { isWritesMode, WRITES_DEFAULT, type WritesMode } from "./writes-mode.js";
86
101
  /**
@@ -136,13 +151,10 @@ export declare function buildCacheFolder(repo: string, home?: string, platform?:
136
151
  /** The environment that points a build's package managers at its project's cache folder, not the user's own. */
137
152
  export declare function buildCacheEnv(folder: string): Record<string, string>;
138
153
  /**
139
- * A launch's network confinement (egress.ts, docs/plans/agent-baseline-hardening.md arc 1). `open`: the network as it
140
- * always was. `allowlist`: nothing leaves the machine but through the run's own proxy (`proxyPort`); the run's tool
141
- * broker (`brokerPorts`) stays reachable; on macOS this Mac's other local services are reachable only under
142
- * `loopback: "any"` (all of them: Seatbelt can't refuse one local port once it admits the rest); unix sockets only
143
- * under `socketRoots` (the run's own temp folder and checkout). On Linux the agent gets a network of its own and
144
- * reaches the proxy and broker through `bridge` (a folder of unix sockets the plane serves, and the forwarder that
145
- * carries them onto the namespace's own loopback).
154
+ * A launch's network confinement (egress.ts). `open`: the network as it always was. `allowlist`: nothing leaves the
155
+ * machine but through the run's own proxy (`proxyPort`, which admits only this run's `token`); the run's tool broker
156
+ * (`brokerPorts`, at most one) rides the sandbox runtime's second relay; this machine's other local services are
157
+ * reachable only under `loopback: "any"`; unix sockets only under `socketRoots` (macOS).
146
158
  */
147
159
  export type NetworkFence = {
148
160
  mode: "open";
@@ -152,21 +164,8 @@ export type NetworkFence = {
152
164
  brokerPorts: number[];
153
165
  loopback: LoopbackMode;
154
166
  socketRoots: string[];
155
- bridge?: {
156
- folder: string;
157
- forwarder: string;
158
- node: string;
159
- };
160
- /** Linux: the run's own folders a private /tmp gives back (its checkout, its launch folder), when they are there. */
161
- keep?: string[];
167
+ token?: string;
162
168
  };
163
- /**
164
- * Services a sandboxed process could ask to act outside the sandbox for it (macOS): open a URL in a browser (Launch
165
- * Services), script another application (Apple Events), fetch in the background (nsurlsessiond), look a name up
166
- * (the DNS service: a lookup is itself a message out), or read and write the clipboard. Denied when the network is
167
- * confined; the run's proxy resolves names for it.
168
- */
169
- export declare const DELEGATION_SERVICES: readonly string[];
170
169
  export declare const NO_GRANTS: FenceGrants;
171
170
  /**
172
171
  * The plane's checkout pools, denied whole: every project's worktrees, and any made there after launch. `agentFence`
@@ -178,6 +177,11 @@ export type CheckoutPool = {
178
177
  own: string | null;
179
178
  lookup: string[];
180
179
  gitPaths?: string[];
180
+ /** Policy v2 (owner Q-C): other tasks' checkouts are read, never written (agents can work together); v1 denies both. */
181
+ readable?: true;
182
+ /** Folders inside the pool that stay denied whole whatever the policy (flows' scratch folders hold their inputs and
183
+ * outputs), this launch's own folder given back. */
184
+ sealed?: string[];
181
185
  };
182
186
  /**
183
187
  * The pool deny for one launch. `roots` come from the plane's own records (the pool beside the database, and the one
@@ -224,6 +228,13 @@ export declare function fencedGitEnv(base?: Readonly<Record<string, string | und
224
228
  * `codexPool`, which denies them around the build's own checkout, written back.
225
229
  */
226
230
  export declare function codexFenceArgv(fence: readonly string[], fullAccess: boolean, grants?: FenceGrants): string[];
231
+ /**
232
+ * The same permission profile as codexFenceArgv, as the config overrides a Codex app-server thread takes
233
+ * (`thread/start` and `thread/resume` `config`; checked against codex-cli 0.156: the thread reports
234
+ * `activePermissionProfile` standing-orders, and its commands are refused the denied paths). Approval policy is the
235
+ * caller's own (an interactive session asks).
236
+ */
237
+ export declare function codexFenceConfig(fence: readonly string[], fullAccess: boolean, grants?: FenceGrants): Record<string, unknown>;
227
238
  /**
228
239
  * Codex cannot deny an ancestor and still traverse it to the own checkout. Deny existing
229
240
  * siblings individually, from the trusted pool roots and a fresh listing, never through
@@ -256,39 +267,6 @@ export declare function flattened(fence: readonly string[], grants?: FenceGrants
256
267
  /** Claude's own file tools refuse the fenced paths in every mode (its shell is fenced by the macOS sandbox), and
257
268
  * never edit a read-only one. */
258
269
  export declare function claudeFenceSettings(fence: readonly string[], readOnly?: readonly string[]): string;
259
- /**
260
- * The folders the user owns above each path (never `/`, nor the path itself), and each `roots` folder itself when the
261
- * user owns it. A Seatbelt path rule follows the name, not the folder: renaming one of these would bring a fenced path
262
- * back under a name no rule mentions. A folder the user doesn't own can't be renamed by a build anyway.
263
- */
264
- export declare function ownedAncestors(paths: readonly string[], roots?: readonly string[], uid?: number | undefined): string[];
265
- /**
266
- * A Seatbelt profile that allows everything except reading or writing the
267
- * fenced paths, less what `grants` gives back (a later Seatbelt rule
268
- * outranks an earlier one). Whatever the paths, the process can't read
269
- * another's arguments and environment (`kern.procargs`, which `ps -E` and
270
- * KERN_PROCARGS2 use) or inspect a process outside its own sandbox:
271
- * `process-info*` limited by target alone doesn't stop the sysctl. It can't
272
- * take another process's task port either (`task_for_pid`, what lldb attaches
273
- * with; a task name port too), so a tool server's or Toolroll's own memory
274
- * and environment stay out of reach. Last, every folder of the user's above
275
- * a fenced or read-only path and above the temp folder (and the temp folder
276
- * itself) can't be renamed or removed, though what is inside them stays
277
- * writable as before. The checkout pools are denied first, and the build's own
278
- * checkout given back right after, so every later deny still holds inside it.
279
- */
280
- export declare function macosFenceProfile(fence: readonly string[], grants?: FenceGrants): string;
281
- /**
282
- * The network half of the macOS fence. Every outbound connection is denied, then loopback is given back as the launch
283
- * allows: every local port under `loopback: "any"`, else the run's proxy and broker alone. (Seatbelt admits a port an
284
- * allow names whatever a deny after it says, so a local port can't be refused once all are admitted: the choice is all
285
- * or none, plus the run's own.) Filters name the remote end only: a `local ip` filter would admit any outbound
286
- * connection.
287
- */
288
- export declare function macosNetworkRules(network: NetworkFence | undefined): string[];
289
- export declare const SANDBOX_EXEC = "/usr/bin/sandbox-exec";
290
- /** Where macOS's resolver (mDNSResponder) answers: every name lookup on a Mac is a message to this socket. */
291
- export declare const MACOS_DNS_SOCKET = "/private/var/run/mDNSResponder";
292
270
  /**
293
271
  * Whether this machine can run an agent inside the macOS fence (checked once). Not from inside another
294
272
  * sandbox: a Toolroll a fenced process starts (the end-to-end journeys a flow's check zone runs) can't nest
@@ -300,23 +278,24 @@ export declare function macosFenceAvailable(platform?: NodeJS.Platform, probe?:
300
278
  status: number | null;
301
279
  stderr: string;
302
280
  }): boolean;
303
- /** The spawn that runs `file args` inside the macOS fence (same process: sandbox-exec replaces itself with the agent). */
304
- export declare function macosFenced(file: string, args: readonly string[], fence: readonly string[], grants?: FenceGrants): {
305
- file: string;
306
- args: string[];
307
- };
308
281
  export declare function linuxFenceAvailable(platform?: NodeJS.Platform, probe?: () => boolean): boolean;
309
- /** The spawn that runs `file args` inside the Linux fence: the whole filesystem as it is, the checkout pools masked
310
- * and the build's own checkout bound back read-write over them, then every fenced path masked whole (an empty
311
- * folder, or /dev/null for a file), so what is made there after launch is masked too; then only the explicit grants
312
- * bound back read-only (bubblewrap binds from the original root, so a grant inside a mask still reaches its files). */
313
- export declare function linuxFenced(file: string, args: readonly string[], fence: readonly string[], grants?: FenceGrants): {
282
+ /** Thrown when no sandbox can be made for a launch: the spawn road refuses it. */
283
+ export declare class LinuxFenceUnavailable extends Error {
284
+ }
285
+ /**
286
+ * The runtime's config for a launch (sandbox-runtime.ts SrtTerms), from its fence: every fenced path denied to read and
287
+ * write (with the exact paths read again inside them), the pools' other checkouts write-denied (and, under v1, read-
288
+ * denied) around the build's own, the flows' scratch folders sealed, the launch's writes (its allowlist, or anywhere
289
+ * outside the denies), its mach services and its network.
290
+ */
291
+ export declare function srtTermsOf(fence: readonly string[], grants?: FenceGrants): SrtTerms;
292
+ /** The spawn that runs `file args` from `cwd` inside the sandbox runtime's sandbox (sandbox-runtime.ts srtSpawn), and the
293
+ * environment it needs beside the caller's. */
294
+ export declare function sandboxedSpawn(file: string, args: readonly string[], fence: readonly string[], grants?: FenceGrants, cwd?: string, temp?: string): {
314
295
  file: string;
315
296
  args: string[];
297
+ env: Record<string, string>;
316
298
  };
317
- /** The host's own sockets a process in a mounted filesystem could still talk to (D-Bus, the display, container
318
- * engines), wherever they exist here. */
319
- export declare function linuxHostSockets(uid?: number | undefined, home?: string, tmp?: string, followLinks?: boolean, env?: Readonly<Record<string, string | undefined>>): string[];
320
299
  /**
321
300
  * What each agent's fence gives it, by permission mode: the one table README's "What an agent can reach" shows
322
301
  * (fenceSummaryMarkdown; a test holds them equal).
@@ -328,3 +307,147 @@ export declare const FENCE_SUMMARY: readonly {
328
307
  }[];
329
308
  /** FENCE_SUMMARY as README's markdown table. */
330
309
  export declare function fenceSummaryMarkdown(): string;
310
+ /** What a `sign-ins` check reads and writes beyond its fence (check-access.ts): its exact sign-in files, its own Claude
311
+ * transcript folder (macOS) and Codex's session and log folders. */
312
+ export type SignInGrants = {
313
+ read: string[];
314
+ transcripts: string[];
315
+ writes: string[];
316
+ /** macOS services the sign-ins need: certificate checking (trustd), without which gh (Go) can't verify GitHub's TLS
317
+ * and calls its own token invalid. Measured with gh 2.x on macOS 26. */
318
+ mach: string[];
319
+ };
320
+ /**
321
+ * A `sign-ins` check's grants (sandbox parity C10, srt-style): each sign-in file by its name, read by the check itself
322
+ * through its sandbox. Nothing privileged reads or copies one: the sandbox resolves the name in its own view, so a link
323
+ * planted there into Toolroll's state leads to a denied path (Seatbelt checks the resolved file; bubblewrap resolves
324
+ * inside its mount namespace, where the state is masked). On a Mac, the checkout's own Claude transcript folder (made
325
+ * when absent, only as the real folder it is), whose session files alone are written; and Codex's session and log
326
+ * folders (when ~/.codex is there), never its config.
327
+ */
328
+ export declare function signInGrants(home: string, checkout: string, platform?: NodeJS.Platform): SignInGrants;
329
+ /** Whether a sign-in file is itself, now: every folder from the (real) home folder down a real folder, the file a
330
+ * regular file with one name (never a link, never a hard link to something else). Checked when granted and again right
331
+ * before the launch (invoke.ts); the sandbox resolves the name a third time. */
332
+ export declare function signInIsItself(base: string, path: string): boolean;
333
+ /**
334
+ * Which fence policy a launch runs under (docs/plans/sandbox-parity.md §1.5). v1: the fence as approvals sealed before
335
+ * 0.9.70 knew it (the home configuration, the repositories beside the project and its primary checkout denied to read).
336
+ * v2: those reads open, as in Anthropic's sandbox runtime, less what the project's sealed `readDeny` names. A widening,
337
+ * so only a scope sealed at v2 runs under it; every narrowing applies to both.
338
+ */
339
+ export type FencePolicy = 1 | 2;
340
+ export declare const FENCE_POLICY_CURRENT: FencePolicy;
341
+ /** Who a launch is (docs/plans/sandbox-parity.md §3): a build's agent, its setup and check commands, a saved result's
342
+ * checks, a flow's setup, script or trigger, or a tool server. Each gets its fence from `fenceFor`, and only from it. */
343
+ export type FenceKind = "build" | "command" | "check" | "flow" | "tool-server";
344
+ /**
345
+ * What `fenceFor` needs. The boundary and the trusted binding are required, as values or an explicit null (a launch
346
+ * with no checkout), so no caller forgets one; every path comes from Toolroll's own records, never from the checkout.
347
+ */
348
+ export type FenceContext = {
349
+ databaseFile: string | null;
350
+ worktree: string | null;
351
+ boundary: TaskBoundary | null;
352
+ binding: WorktreeBinding | null;
353
+ runFolders: RunFolders;
354
+ /** The checkout pools, from the plane's records (CheckoutPool). */
355
+ poolRoots: readonly (string | null)[];
356
+ home?: string;
357
+ /** Denied beside the fence (a provider's live session keys, say). */
358
+ extra?: readonly string[];
359
+ /** The shared dependencies this checkout links, as the plane recorded them. */
360
+ linked?: {
361
+ repo: string;
362
+ setupDigest: string | null;
363
+ } | null;
364
+ /** A workspace launch's write allowlist and what it adds read-only (writeAllowlist); absent: writes anywhere its
365
+ * fence allows. */
366
+ writes?: {
367
+ writes: WriteAllowlist;
368
+ readOnly: string[];
369
+ } | null;
370
+ /** The launch's network, when it has one of its own (a flow's: decided by its caller, never left unsaid). */
371
+ network?: NetworkFence;
372
+ /** What it may not read beside the fence, as absolute paths (project-sandbox.ts readDenyPath): denied whole. */
373
+ readDeny?: readonly string[];
374
+ /** macOS services it may also reach (project-sandbox.ts). */
375
+ machLookup?: readonly string[];
376
+ /** The fence policy it runs under: v2 reads other tasks' checkouts (never writes them). */
377
+ policy?: FencePolicy;
378
+ /** The agent the launch runs, whose own sign-in files stay its to write (PROVIDER_HOME). */
379
+ provider?: string;
380
+ /** Scratch folders (a flow's) that stay denied whole inside the pool, whatever the policy. */
381
+ scratchRoots?: readonly string[];
382
+ };
383
+ /** A launch's sealed sandbox terms (builder.ts launchSandboxOf): its policy version, read denies and mach services. A
384
+ * launch with none sealed is v1 with none of them. */
385
+ export type LaunchSandbox = {
386
+ policy: FencePolicy;
387
+ readDeny: readonly string[];
388
+ machLookup: readonly string[];
389
+ };
390
+ export declare const LEGACY_SANDBOX: LaunchSandbox;
391
+ /** A build's sandbox terms, exactly as its approval sealed them: an approval from before 0.9.70 is policy v1 (the home
392
+ * configuration denied) with nothing listed. */
393
+ export declare function launchSandboxOf(profile: ExecutionProfile): LaunchSandbox;
394
+ /** A launch's whole fence: its denied paths, what it gives back, and that it runs inside the OS fence or not at all.
395
+ * Spread into RunOptions as is. */
396
+ export type LaunchFence = {
397
+ kind: FenceKind;
398
+ fence: string[];
399
+ fenceGrants: FenceGrants;
400
+ confinement: "required";
401
+ };
402
+ /** The one place a launch's fence is made (docs/plans/sandbox-parity.md C1): its denies, its grants, and
403
+ * `confinement: "required"`, so the spawn road refuses it where no OS fence applies rather than running it bare. */
404
+ export declare function fenceFor(kind: FenceKind, ctx: FenceContext): LaunchFence;
405
+ /** The flows' scratch folders inside the checkout pools (`flow-checks` beside the checkouts) and any a caller names,
406
+ * each denied by name whether or not it is there yet, and where it is a link, where it leads too. */
407
+ export declare function sealedScratch(poolRoots: readonly (string | null)[], named: readonly string[]): string[];
408
+ /**
409
+ * Toolroll's own install as no launch may write it: this package's folder (its compiled code, the sandbox launch
410
+ * helper among it), the sandbox runtime's package wherever it was installed, and the Node running them with the
411
+ * folder it lives in (its npm and global packages beside it), as the real folders they are. Never a folder that holds
412
+ * the launch's own checkout or its repository (a build of Toolroll's own source, run from it: that repository's other
413
+ * files are the primary checkout's, write-denied already), the home folder or one above it, and nothing the user
414
+ * doesn't own (root's folders are out of a build's reach already).
415
+ */
416
+ export declare function toolrollInstall(worktree: string | null, home: string, repository?: string | null): string[];
417
+ /** Names that run code or reconfigure tools for whoever opens the project next: the sandbox runtime's DANGEROUS_FILES
418
+ * (@anthropic-ai/sandbox-runtime sandbox-utils), and the names its mandatory denies can't hold when they are made
419
+ * after a launch starts (`.envrc`, package-manager and Claude settings). */
420
+ export declare const PROTECTED_FILES: readonly string[];
421
+ /** Folders protected whole, wherever they are (the runtime's dangerous directories, with Git's hooks). */
422
+ export declare const PROTECTED_DIRS: readonly string[];
423
+ /**
424
+ * The handoff's backstop (builder.ts commit): the changed paths (relative, from Git) that are protected names at any
425
+ * depth, in any case. The sandbox runtime denies these when a launch starts; one made after it started (a new nested
426
+ * folder's `.envrc`, say) gets through the sandbox, and a result that holds it is refused.
427
+ */
428
+ export declare function protectedChanges(changed: readonly string[]): string[];
429
+ /**
430
+ * The handoff's backstop for links (builder.ts commit): a staged symbolic link (Git mode 120000) whose path is a
431
+ * protected name or a folder on the way to one (`pkg/.claude`, `.vscode`, `lib/.git`), at any depth, in any case. A
432
+ * link there would lead whoever opens the project to settings, hooks or commands kept anywhere.
433
+ */
434
+ export declare function protectedLinks(links: readonly string[]): string[];
435
+ /** The home folder's own persistence points beyond the protected names (login items, systemd user units and autostart,
436
+ * Git's, gh's, pip's and fish's configuration, the shells' other startup files, SSH and GnuPG, and the folders on a
437
+ * login PATH). Under `.config/` each also stands under $XDG_CONFIG_HOME. */
438
+ export declare const HOME_PERSISTENCE: readonly string[];
439
+ /**
440
+ * What no launch writes, whatever its writes, as static write denies for the sandbox runtime (whose own protected names
441
+ * are anchored at the launch's folder only) and Codex's profile: the home folder's protected names (shell profiles, Git
442
+ * and tool configuration), its persistence points and sign-ins (but the launch's own agent's exact sign-in files), and
443
+ * the bound repository's own Git config, hooks and submodules (a linked worktree reaches them through its `.git`
444
+ * file). Each by its name and by where it leads now (a linked ~/.config or LaunchAgents is denied at both ends). On
445
+ * Linux only those there at launch: the runtime holds an absent name with a stand-in nothing can read for the launch's
446
+ * length, and the person's own git and shells then fail on their ~/.gitconfig and ~/.bashrc (measured). Under a write
447
+ * allowlist an absent one is out of reach anyway; under `anywhere` it can be made (an accepted limit).
448
+ */
449
+ export declare function homeWriteDenies(home: string, binding: WorktreeBinding | null, provider: string | null, platform?: NodeJS.Platform, env?: Readonly<Record<string, string | undefined>>): string[];
450
+ /** A tool server's grants from its build's: never the agent's network or write allowlist (a server keeps its own state
451
+ * where it always has), and never the build's own checkout or launch folder read back (nothing a build wrote loads in a
452
+ * process that holds a tool's secrets). */
453
+ export declare function serverGrantsOf(grants: FenceGrants): FenceGrants;