toolroll 0.9.69 → 0.9.71
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.
- package/CHANGELOG.md +67 -0
- package/README.md +43 -17
- package/THIRD_PARTY_NOTICES.md +21 -1
- package/dist/agent-fence.d.ts +184 -115
- package/dist/agent-fence.js +418 -535
- package/dist/agentconfig.js +6 -1
- package/dist/browser/THIRD_PARTY_NOTICES.txt +21 -0
- package/dist/builder.js +24 -4
- package/dist/check-access.d.ts +6 -9
- package/dist/check-access.js +8 -11
- package/dist/coding-provider.d.ts +14 -0
- package/dist/coding-provider.js +33 -1
- package/dist/coding-workspace.d.ts +15 -0
- package/dist/coding-workspace.js +9 -2
- package/dist/contracts/console-api.d.ts +1 -0
- package/dist/contracts/console-api.js +1 -0
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +4 -0
- package/dist/egress.d.ts +2 -0
- package/dist/egress.js +13 -4
- package/dist/exec.d.ts +17 -3
- package/dist/exec.js +54 -17
- package/dist/flow-actions.d.ts +1 -0
- package/dist/flow-actions.js +3 -2
- package/dist/flow-code.d.ts +31 -2
- package/dist/flow-code.js +57 -5
- package/dist/flow-steps.d.ts +4 -0
- package/dist/flow-steps.js +9 -5
- package/dist/flow-triggers.d.ts +4 -0
- package/dist/flow-triggers.js +8 -3
- package/dist/invoke.d.ts +25 -8
- package/dist/invoke.js +115 -115
- package/dist/operate.js +46 -0
- package/dist/project-delete-ui.d.ts +7 -6
- package/dist/project-delete-ui.js +37 -3
- package/dist/project-sandbox.d.ts +59 -0
- package/dist/project-sandbox.js +83 -0
- package/dist/project-tools.d.ts +29 -5
- package/dist/project-tools.js +43 -7
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -0
- package/dist/sandbox-launch.d.ts +1 -0
- package/dist/sandbox-launch.js +148 -0
- package/dist/sandbox-runtime.d.ts +109 -0
- package/dist/sandbox-runtime.js +281 -0
- package/dist/scope.d.ts +28 -0
- package/dist/scope.js +25 -5
- package/dist/scout-browser-lock.d.ts +69 -0
- package/dist/scout-browser-lock.js +69 -0
- package/dist/scout-browser.d.ts +60 -1
- package/dist/scout-browser.js +200 -4
- package/dist/scout.d.ts +9 -4
- package/dist/scout.js +14 -4
- package/dist/serve.js +3 -1
- package/dist/server/render-tasks.js +4 -1
- package/dist/server/route-table.js +1 -0
- package/dist/server/settings.js +27 -0
- package/dist/store.d.ts +31 -1
- package/dist/store.js +83 -4
- package/dist/subagent-tools.d.ts +1 -0
- package/dist/subagent-tools.js +7 -5
- package/dist/tool-broker.d.ts +2 -1
- package/dist/tool-broker.js +2 -2
- package/dist/tool-launcher.js +10 -16
- package/package.json +2 -1
- package/scripts/provider-canary.mjs +5 -6
- package/dist/egress-bridge.d.ts +0 -17
- package/dist/egress-bridge.js +0 -111
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.71 — 2026-10-25
|
|
4
|
+
|
|
5
|
+
- **Sandboxed launches can open a terminal of their own on macOS.** A check
|
|
6
|
+
or build that drives a pseudo-terminal (node-pty, `script`, a test that
|
|
7
|
+
runs a shell) now works inside the sandbox. Only terminals the sandboxed
|
|
8
|
+
process made itself open: Toolroll doesn't turn on the sandbox runtime's
|
|
9
|
+
`allowPty`, which also opens your other open terminals, and it now denies
|
|
10
|
+
reading those by name, which the runtime's baseline allowed. Nothing
|
|
11
|
+
sandboxed can type into a terminal, and every sandboxed launch starts in a
|
|
12
|
+
session of its own, so a console started from your terminal doesn't hand
|
|
13
|
+
it that terminal.
|
|
14
|
+
- **Only a build's agent reaches your keychain.** The sandbox runtime lets
|
|
15
|
+
every sandboxed process ask macOS's keychain service, so it could read
|
|
16
|
+
keychain items any program may read (anthropics/sandbox-runtime#755). A
|
|
17
|
+
build's setup and check commands, a project's checks, flows and tool
|
|
18
|
+
servers are now denied it. A build's agent keeps it (Claude Code signs in
|
|
19
|
+
from the keychain), and so does a check whose project gave it
|
|
20
|
+
**sign-ins**, while the instance operator who chose that still is one. The
|
|
21
|
+
sign-ins choice now says it includes Keychain access.
|
|
22
|
+
|
|
23
|
+
## 0.9.70 — 2026-10-24
|
|
24
|
+
|
|
25
|
+
- **Every sandbox is now Anthropic's sandbox-runtime.** Builds, their setup
|
|
26
|
+
and check commands, a saved result's checks, flows and project tool servers
|
|
27
|
+
all run in `@anthropic-ai/sandbox-runtime` 0.0.79, the same sandbox Claude
|
|
28
|
+
Code uses: its own code writes the macOS Seatbelt profile and the Linux
|
|
29
|
+
bubblewrap and seccomp command for each launch. Toolroll hands it only a
|
|
30
|
+
short list: its own folders and database are off limits, the build's
|
|
31
|
+
checkout is writable, other tasks' checkouts are read and never written,
|
|
32
|
+
your project's read-deny list, and the run's proxy as the only way out.
|
|
33
|
+
`toolroll doctor` and Settings → Project → Home folder name the engine and
|
|
34
|
+
its version. Codex builds keep Codex's own sandbox with the same rules.
|
|
35
|
+
- **Checks that start Chrome don't run sandboxed on macOS yet.** The sandbox
|
|
36
|
+
runtime has no setting for the service Chromium registers at startup, so a
|
|
37
|
+
Playwright or browser check fails inside it. Until a follow-up adds that one
|
|
38
|
+
rule, set such a project's check access to **Not sandboxed** in
|
|
39
|
+
Settings → Project → Check access.
|
|
40
|
+
- **Your home folder is readable to new scopes, as in the sandbox runtime.**
|
|
41
|
+
A project hides what it wants back with Settings → Project → **Home
|
|
42
|
+
folder**, or `toolroll project sandbox --repo <p> --read-deny "~/.ssh"`.
|
|
43
|
+
Each approved scope seals its list. Scopes approved before 0.9.70 keep
|
|
44
|
+
hiding your credentials and agent settings, as they were approved.
|
|
45
|
+
- **The macOS services a project's builds may also reach** (`--mach`, for Go
|
|
46
|
+
tools that verify TLS through trustd) seal only on a person's password.
|
|
47
|
+
Approval cards and the CLI's scope view say what a scope reads and which
|
|
48
|
+
services it adds.
|
|
49
|
+
- **Checks can use your Claude, Codex and GitHub sign-ins again.** The
|
|
50
|
+
"Sandboxed, with model and GitHub sign-ins" level is on: a check reads
|
|
51
|
+
exactly those files, by name, inside its sandbox, never through a link.
|
|
52
|
+
- Flows, tool servers started outside a build and interactive Codex coding
|
|
53
|
+
sessions now run in the same sandbox as builds. A machine that can't
|
|
54
|
+
sandbox runs none of them unless an instance operator allowed unconfined
|
|
55
|
+
agents.
|
|
56
|
+
- On Linux the sandbox needs bubblewrap, socat and ripgrep; without them
|
|
57
|
+
nothing starts (as before, unless unconfined agents are allowed).
|
|
58
|
+
- A result that adds or changes a Git hook or config, editor or Claude
|
|
59
|
+
settings, a shell profile, `.envrc`, `.npmrc` or `.mcp.json` is refused at
|
|
60
|
+
the handoff, with its work kept. Toolroll's own install and the sandbox
|
|
61
|
+
runtime can't be written from inside a sandbox.
|
|
62
|
+
- The scout's browser runs outside the sandbox, as an explicit exception: a
|
|
63
|
+
pinned Playwright release installed from a lockfile into Toolroll's own
|
|
64
|
+
folder, with exactly the arguments and environment it was approved with.
|
|
65
|
+
- Limits that come with the sandbox runtime: rules are fixed when a launch
|
|
66
|
+
starts, concurrent builds share its temp folder, and on macOS a build can
|
|
67
|
+
read another process's arguments and environment. Write unlocks for
|
|
68
|
+
protected files are gone (the runtime can't grant them).
|
|
69
|
+
|
|
3
70
|
## 0.9.69 — 2026-10-23
|
|
4
71
|
|
|
5
72
|
- **Choose how far a project's checks reach.** Settings → Project has a new
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
| **
|
|
585
|
-
| **
|
|
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
|
|
622
|
-
|
|
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
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -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.
|
package/dist/agent-fence.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { LoopbackMode } from "./egress.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/** Where a Linux sign-ins check's copies of its sign-in files live while it runs (check-access.ts): one folder per launch,
|
|
5
|
-
* `c-<pid>-…`, beside the database; fenced from every build by name, like the bridges. */
|
|
6
|
-
export declare const SIGN_IN_COPIES_DIR = "sign-in-copies";
|
|
2
|
+
import type { ExecutionProfile } from "./scope.js";
|
|
3
|
+
import { type SrtTerms } from "./sandbox-runtime.js";
|
|
7
4
|
/** How a launch was fenced, as recorded on the run and said on the task. */
|
|
8
5
|
export type FenceMethod = "codex-profile" | "macos-sandbox" | "linux-bubblewrap" | "claude-rules" | "none";
|
|
9
6
|
/** Only the plane's recorded repository may supply an own-Git exemption. */
|
|
@@ -58,8 +55,8 @@ export declare function taskBoundary(options: {
|
|
|
58
55
|
provider: string;
|
|
59
56
|
home?: string;
|
|
60
57
|
binding?: WorktreeBinding | null;
|
|
58
|
+
policy?: FencePolicy;
|
|
61
59
|
}): TaskBoundary;
|
|
62
|
-
/** Where launches keep their private folders (the system temp folder), and this launch's own tool folder. */
|
|
63
60
|
/** Where launches keep their private folders: `root` (the system temp folder) and, on a Mac, `also` the short shared one
|
|
64
61
|
* a check's launch folder is made in (/private/tmp, so its unix sockets fit); `own` is this launch's. */
|
|
65
62
|
export type RunFolders = {
|
|
@@ -67,27 +64,21 @@ export type RunFolders = {
|
|
|
67
64
|
own: string | null;
|
|
68
65
|
also?: string[];
|
|
69
66
|
};
|
|
70
|
-
/** The prefixes of every launch's private folders: its tools' config
|
|
71
|
-
* 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). */
|
|
72
68
|
export declare const RUN_FOLDER_PREFIXES: readonly ["so-tools-", "so-codex-", "so-net-", "so-cmd-"];
|
|
73
69
|
export declare function runFolders(own: string | null, root?: string): RunFolders;
|
|
74
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
|
|
75
|
-
* path (about 104 bytes)
|
|
71
|
+
* path (about 104 bytes). */
|
|
76
72
|
export declare const CHECK_LAUNCH_ROOT_MACOS = "/private/tmp";
|
|
77
|
-
/** The folder a check's launch folder is made in: on a Mac the short one, elsewhere the system temp folder
|
|
73
|
+
/** The folder a check's launch folder is made in: on a Mac the short one, elsewhere the system temp folder. */
|
|
78
74
|
export declare const checkLaunchRoot: () => string;
|
|
79
75
|
/** Every root launch folders are made under (the system temp folder, and on a Mac the short shared one). */
|
|
80
76
|
export declare const runRoots: (folders: RunFolders) => string[];
|
|
81
77
|
/**
|
|
82
|
-
* What a fence gives back beside its denied paths.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* listed or read (the deps root and the copy linked, other tasks' checkout
|
|
87
|
-
* roots); `runFolders` denies every launch folder in the temp folder but
|
|
88
|
-
* this launch's own (macOS: by pattern, so a later one is denied too);
|
|
89
|
-
* `pool` denies the plane's checkout pools whole and gives this build's own
|
|
90
|
-
* 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).
|
|
91
82
|
*/
|
|
92
83
|
export type FenceGrants = {
|
|
93
84
|
readOnly: string[];
|
|
@@ -97,11 +88,18 @@ export type FenceGrants = {
|
|
|
97
88
|
pool?: CheckoutPool;
|
|
98
89
|
network?: NetworkFence;
|
|
99
90
|
writes?: WriteAllowlist;
|
|
100
|
-
/**
|
|
91
|
+
/** Policy v1: the home configuration names denied by name, present or not (TaskBoundary.homeNames). */
|
|
101
92
|
homeNames?: string[];
|
|
102
|
-
/**
|
|
103
|
-
|
|
93
|
+
/** macOS: the project's sealed mach services beyond the runtime's baseline (its `allowMachLookup`). */
|
|
94
|
+
machLookup?: string[];
|
|
95
|
+
/** macOS: the keychain's services (securityd), denied to every launch without it (sandbox-runtime.ts
|
|
96
|
+
* SEATBELT_NO_KEYCHAIN). Only fenceFor gives it, to a build's agent; a check with sign-ins is given it at its launch
|
|
97
|
+
* (invoke.ts). Never a tool server's (serverGrantsOf). */
|
|
98
|
+
keychain?: true;
|
|
99
|
+
/** A `sign-ins` check's exact sign-in files, read again inside the home denies (check-access.ts, signInGrants). */
|
|
104
100
|
signIns?: SignInGrants;
|
|
101
|
+
/** Never written, wherever the launch writes (homeWriteDenies): what runs for the operator next or signs them in. */
|
|
102
|
+
writeDenies?: string[];
|
|
105
103
|
};
|
|
106
104
|
export { isWritesMode, WRITES_DEFAULT, type WritesMode } from "./writes-mode.js";
|
|
107
105
|
/**
|
|
@@ -157,13 +155,10 @@ export declare function buildCacheFolder(repo: string, home?: string, platform?:
|
|
|
157
155
|
/** The environment that points a build's package managers at its project's cache folder, not the user's own. */
|
|
158
156
|
export declare function buildCacheEnv(folder: string): Record<string, string>;
|
|
159
157
|
/**
|
|
160
|
-
* A launch's network confinement (egress.ts
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* `loopback: "any"
|
|
164
|
-
* under `socketRoots` (the run's own temp folder and checkout). On Linux the agent gets a network of its own and
|
|
165
|
-
* reaches the proxy and broker through `bridge` (a folder of unix sockets the plane serves, and the forwarder that
|
|
166
|
-
* carries them onto the namespace's own loopback).
|
|
158
|
+
* A launch's network confinement (egress.ts). `open`: the network as it always was. `allowlist`: nothing leaves the
|
|
159
|
+
* machine but through the run's own proxy (`proxyPort`, which admits only this run's `token`); the run's tool broker
|
|
160
|
+
* (`brokerPorts`, at most one) rides the sandbox runtime's second relay; this machine's other local services are
|
|
161
|
+
* reachable only under `loopback: "any"`; unix sockets only under `socketRoots` (macOS).
|
|
167
162
|
*/
|
|
168
163
|
export type NetworkFence = {
|
|
169
164
|
mode: "open";
|
|
@@ -173,21 +168,8 @@ export type NetworkFence = {
|
|
|
173
168
|
brokerPorts: number[];
|
|
174
169
|
loopback: LoopbackMode;
|
|
175
170
|
socketRoots: string[];
|
|
176
|
-
|
|
177
|
-
folder: string;
|
|
178
|
-
forwarder: string;
|
|
179
|
-
node: string;
|
|
180
|
-
};
|
|
181
|
-
/** Linux: the run's own folders a private /tmp gives back (its checkout, its launch folder), when they are there. */
|
|
182
|
-
keep?: string[];
|
|
171
|
+
token?: string;
|
|
183
172
|
};
|
|
184
|
-
/**
|
|
185
|
-
* Services a sandboxed process could ask to act outside the sandbox for it (macOS): open a URL in a browser (Launch
|
|
186
|
-
* Services), script another application (Apple Events), fetch in the background (nsurlsessiond), look a name up
|
|
187
|
-
* (the DNS service: a lookup is itself a message out), or read and write the clipboard. Denied when the network is
|
|
188
|
-
* confined; the run's proxy resolves names for it.
|
|
189
|
-
*/
|
|
190
|
-
export declare const DELEGATION_SERVICES: readonly string[];
|
|
191
173
|
export declare const NO_GRANTS: FenceGrants;
|
|
192
174
|
/**
|
|
193
175
|
* The plane's checkout pools, denied whole: every project's worktrees, and any made there after launch. `agentFence`
|
|
@@ -199,6 +181,11 @@ export type CheckoutPool = {
|
|
|
199
181
|
own: string | null;
|
|
200
182
|
lookup: string[];
|
|
201
183
|
gitPaths?: string[];
|
|
184
|
+
/** Policy v2 (owner Q-C): other tasks' checkouts are read, never written (agents can work together); v1 denies both. */
|
|
185
|
+
readable?: true;
|
|
186
|
+
/** Folders inside the pool that stay denied whole whatever the policy (flows' scratch folders hold their inputs and
|
|
187
|
+
* outputs), this launch's own folder given back. */
|
|
188
|
+
sealed?: string[];
|
|
202
189
|
};
|
|
203
190
|
/**
|
|
204
191
|
* The pool deny for one launch. `roots` come from the plane's own records (the pool beside the database, and the one
|
|
@@ -245,6 +232,13 @@ export declare function fencedGitEnv(base?: Readonly<Record<string, string | und
|
|
|
245
232
|
* `codexPool`, which denies them around the build's own checkout, written back.
|
|
246
233
|
*/
|
|
247
234
|
export declare function codexFenceArgv(fence: readonly string[], fullAccess: boolean, grants?: FenceGrants): string[];
|
|
235
|
+
/**
|
|
236
|
+
* The same permission profile as codexFenceArgv, as the config overrides a Codex app-server thread takes
|
|
237
|
+
* (`thread/start` and `thread/resume` `config`; checked against codex-cli 0.156: the thread reports
|
|
238
|
+
* `activePermissionProfile` standing-orders, and its commands are refused the denied paths). Approval policy is the
|
|
239
|
+
* caller's own (an interactive session asks).
|
|
240
|
+
*/
|
|
241
|
+
export declare function codexFenceConfig(fence: readonly string[], fullAccess: boolean, grants?: FenceGrants): Record<string, unknown>;
|
|
248
242
|
/**
|
|
249
243
|
* Codex cannot deny an ancestor and still traverse it to the own checkout. Deny existing
|
|
250
244
|
* siblings individually, from the trusted pool roots and a fresh listing, never through
|
|
@@ -277,39 +271,6 @@ export declare function flattened(fence: readonly string[], grants?: FenceGrants
|
|
|
277
271
|
/** Claude's own file tools refuse the fenced paths in every mode (its shell is fenced by the macOS sandbox), and
|
|
278
272
|
* never edit a read-only one. */
|
|
279
273
|
export declare function claudeFenceSettings(fence: readonly string[], readOnly?: readonly string[]): string;
|
|
280
|
-
/**
|
|
281
|
-
* The folders the user owns above each path (never `/`, nor the path itself), and each `roots` folder itself when the
|
|
282
|
-
* user owns it. A Seatbelt path rule follows the name, not the folder: renaming one of these would bring a fenced path
|
|
283
|
-
* back under a name no rule mentions. A folder the user doesn't own can't be renamed by a build anyway.
|
|
284
|
-
*/
|
|
285
|
-
export declare function ownedAncestors(paths: readonly string[], roots?: readonly string[], uid?: number | undefined): string[];
|
|
286
|
-
/**
|
|
287
|
-
* A Seatbelt profile that allows everything except reading or writing the
|
|
288
|
-
* fenced paths, less what `grants` gives back (a later Seatbelt rule
|
|
289
|
-
* outranks an earlier one). Whatever the paths, the process can't read
|
|
290
|
-
* another's arguments and environment (`kern.procargs`, which `ps -E` and
|
|
291
|
-
* KERN_PROCARGS2 use) or inspect a process outside its own sandbox:
|
|
292
|
-
* `process-info*` limited by target alone doesn't stop the sysctl. It can't
|
|
293
|
-
* take another process's task port either (`task_for_pid`, what lldb attaches
|
|
294
|
-
* with; a task name port too), so a tool server's or Toolroll's own memory
|
|
295
|
-
* and environment stay out of reach. Last, every folder of the user's above
|
|
296
|
-
* a fenced or read-only path and above the temp folder (and the temp folder
|
|
297
|
-
* itself) can't be renamed or removed, though what is inside them stays
|
|
298
|
-
* writable as before. The checkout pools are denied first, and the build's own
|
|
299
|
-
* checkout given back right after, so every later deny still holds inside it.
|
|
300
|
-
*/
|
|
301
|
-
export declare function macosFenceProfile(fence: readonly string[], grants?: FenceGrants): string;
|
|
302
|
-
/**
|
|
303
|
-
* The network half of the macOS fence. Every outbound connection is denied, then loopback is given back as the launch
|
|
304
|
-
* allows: every local port under `loopback: "any"`, else the run's proxy and broker alone. (Seatbelt admits a port an
|
|
305
|
-
* allow names whatever a deny after it says, so a local port can't be refused once all are admitted: the choice is all
|
|
306
|
-
* or none, plus the run's own.) Filters name the remote end only: a `local ip` filter would admit any outbound
|
|
307
|
-
* connection.
|
|
308
|
-
*/
|
|
309
|
-
export declare function macosNetworkRules(network: NetworkFence | undefined): string[];
|
|
310
|
-
export declare const SANDBOX_EXEC = "/usr/bin/sandbox-exec";
|
|
311
|
-
/** Where macOS's resolver (mDNSResponder) answers: every name lookup on a Mac is a message to this socket. */
|
|
312
|
-
export declare const MACOS_DNS_SOCKET = "/private/var/run/mDNSResponder";
|
|
313
274
|
/**
|
|
314
275
|
* Whether this machine can run an agent inside the macOS fence (checked once). Not from inside another
|
|
315
276
|
* sandbox: a Toolroll a fenced process starts (the end-to-end journeys a flow's check zone runs) can't nest
|
|
@@ -321,23 +282,24 @@ export declare function macosFenceAvailable(platform?: NodeJS.Platform, probe?:
|
|
|
321
282
|
status: number | null;
|
|
322
283
|
stderr: string;
|
|
323
284
|
}): boolean;
|
|
324
|
-
/** The spawn that runs `file args` inside the macOS fence (same process: sandbox-exec replaces itself with the agent). */
|
|
325
|
-
export declare function macosFenced(file: string, args: readonly string[], fence: readonly string[], grants?: FenceGrants): {
|
|
326
|
-
file: string;
|
|
327
|
-
args: string[];
|
|
328
|
-
};
|
|
329
285
|
export declare function linuxFenceAvailable(platform?: NodeJS.Platform, probe?: () => boolean): boolean;
|
|
330
|
-
/**
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
286
|
+
/** Thrown when no sandbox can be made for a launch: the spawn road refuses it. */
|
|
287
|
+
export declare class LinuxFenceUnavailable extends Error {
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* The runtime's config for a launch (sandbox-runtime.ts SrtTerms), from its fence: every fenced path denied to read and
|
|
291
|
+
* write (with the exact paths read again inside them), the pools' other checkouts write-denied (and, under v1, read-
|
|
292
|
+
* denied) around the build's own, the flows' scratch folders sealed, the launch's writes (its allowlist, or anywhere
|
|
293
|
+
* outside the denies), its mach services and its network.
|
|
294
|
+
*/
|
|
295
|
+
export declare function srtTermsOf(fence: readonly string[], grants?: FenceGrants): SrtTerms;
|
|
296
|
+
/** The spawn that runs `file args` from `cwd` inside the sandbox runtime's sandbox (sandbox-runtime.ts srtSpawn), and the
|
|
297
|
+
* environment it needs beside the caller's. */
|
|
298
|
+
export declare function sandboxedSpawn(file: string, args: readonly string[], fence: readonly string[], grants?: FenceGrants, cwd?: string, temp?: string): {
|
|
335
299
|
file: string;
|
|
336
300
|
args: string[];
|
|
301
|
+
env: Record<string, string>;
|
|
337
302
|
};
|
|
338
|
-
/** The host's own sockets a process in a mounted filesystem could still talk to (D-Bus, the display, container
|
|
339
|
-
* engines), wherever they exist here. */
|
|
340
|
-
export declare function linuxHostSockets(uid?: number | undefined, home?: string, tmp?: string, followLinks?: boolean, env?: Readonly<Record<string, string | undefined>>): string[];
|
|
341
303
|
/**
|
|
342
304
|
* What each agent's fence gives it, by permission mode: the one table README's "What an agent can reach" shows
|
|
343
305
|
* (fenceSummaryMarkdown; a test holds them equal).
|
|
@@ -349,40 +311,147 @@ export declare const FENCE_SUMMARY: readonly {
|
|
|
349
311
|
}[];
|
|
350
312
|
/** FENCE_SUMMARY as README's markdown table. */
|
|
351
313
|
export declare function fenceSummaryMarkdown(): string;
|
|
352
|
-
/** What a `sign-ins` check reads and writes beyond its fence (check-access.ts)
|
|
314
|
+
/** What a `sign-ins` check reads and writes beyond its fence (check-access.ts): its exact sign-in files, its own Claude
|
|
315
|
+
* transcript folder (macOS) and Codex's session and log folders. */
|
|
353
316
|
export type SignInGrants = {
|
|
354
317
|
read: string[];
|
|
355
318
|
transcripts: string[];
|
|
356
|
-
|
|
319
|
+
writes: string[];
|
|
320
|
+
/** macOS services the sign-ins need: certificate checking (trustd), without which gh (Go) can't verify GitHub's TLS
|
|
321
|
+
* and calls its own token invalid. Measured with gh 2.x on macOS 26. */
|
|
322
|
+
mach: string[];
|
|
357
323
|
};
|
|
358
324
|
/**
|
|
359
|
-
* A `sign-ins` check's grants: each sign-in file
|
|
360
|
-
* through
|
|
361
|
-
*
|
|
362
|
-
*
|
|
325
|
+
* A `sign-ins` check's grants (sandbox parity C10, srt-style): each sign-in file by its name, read by the check itself
|
|
326
|
+
* through its sandbox. Nothing privileged reads or copies one: the sandbox resolves the name in its own view, so a link
|
|
327
|
+
* planted there into Toolroll's state leads to a denied path (Seatbelt checks the resolved file; bubblewrap resolves
|
|
328
|
+
* inside its mount namespace, where the state is masked). On a Mac, the checkout's own Claude transcript folder (made
|
|
329
|
+
* when absent, only as the real folder it is), whose session files alone are written; and Codex's session and log
|
|
330
|
+
* folders (when ~/.codex is there), never its config.
|
|
363
331
|
*/
|
|
364
332
|
export declare function signInGrants(home: string, checkout: string, platform?: NodeJS.Platform): SignInGrants;
|
|
333
|
+
/** Whether a sign-in file is itself, now: every folder from the (real) home folder down a real folder, the file a
|
|
334
|
+
* regular file with one name (never a link, never a hard link to something else). Checked when granted and again right
|
|
335
|
+
* before the launch (invoke.ts); the sandbox resolves the name a third time. */
|
|
336
|
+
export declare function signInIsItself(base: string, path: string): boolean;
|
|
337
|
+
/**
|
|
338
|
+
* Which fence policy a launch runs under (docs/plans/sandbox-parity.md §1.5). v1: the fence as approvals sealed before
|
|
339
|
+
* 0.9.70 knew it (the home configuration, the repositories beside the project and its primary checkout denied to read).
|
|
340
|
+
* v2: those reads open, as in Anthropic's sandbox runtime, less what the project's sealed `readDeny` names. A widening,
|
|
341
|
+
* so only a scope sealed at v2 runs under it; every narrowing applies to both.
|
|
342
|
+
*/
|
|
343
|
+
export type FencePolicy = 1 | 2;
|
|
344
|
+
export declare const FENCE_POLICY_CURRENT: FencePolicy;
|
|
345
|
+
/** Who a launch is (docs/plans/sandbox-parity.md §3): a build's agent, its setup and check commands, a saved result's
|
|
346
|
+
* checks, a flow's setup, script or trigger, or a tool server. Each gets its fence from `fenceFor`, and only from it. */
|
|
347
|
+
export type FenceKind = "build" | "command" | "check" | "flow" | "tool-server";
|
|
348
|
+
/**
|
|
349
|
+
* What `fenceFor` needs. The boundary and the trusted binding are required, as values or an explicit null (a launch
|
|
350
|
+
* with no checkout), so no caller forgets one; every path comes from Toolroll's own records, never from the checkout.
|
|
351
|
+
*/
|
|
352
|
+
export type FenceContext = {
|
|
353
|
+
databaseFile: string | null;
|
|
354
|
+
worktree: string | null;
|
|
355
|
+
boundary: TaskBoundary | null;
|
|
356
|
+
binding: WorktreeBinding | null;
|
|
357
|
+
runFolders: RunFolders;
|
|
358
|
+
/** The checkout pools, from the plane's records (CheckoutPool). */
|
|
359
|
+
poolRoots: readonly (string | null)[];
|
|
360
|
+
home?: string;
|
|
361
|
+
/** Denied beside the fence (a provider's live session keys, say). */
|
|
362
|
+
extra?: readonly string[];
|
|
363
|
+
/** The shared dependencies this checkout links, as the plane recorded them. */
|
|
364
|
+
linked?: {
|
|
365
|
+
repo: string;
|
|
366
|
+
setupDigest: string | null;
|
|
367
|
+
} | null;
|
|
368
|
+
/** A workspace launch's write allowlist and what it adds read-only (writeAllowlist); absent: writes anywhere its
|
|
369
|
+
* fence allows. */
|
|
370
|
+
writes?: {
|
|
371
|
+
writes: WriteAllowlist;
|
|
372
|
+
readOnly: string[];
|
|
373
|
+
} | null;
|
|
374
|
+
/** The launch's network, when it has one of its own (a flow's: decided by its caller, never left unsaid). */
|
|
375
|
+
network?: NetworkFence;
|
|
376
|
+
/** What it may not read beside the fence, as absolute paths (project-sandbox.ts readDenyPath): denied whole. */
|
|
377
|
+
readDeny?: readonly string[];
|
|
378
|
+
/** macOS services it may also reach (project-sandbox.ts). */
|
|
379
|
+
machLookup?: readonly string[];
|
|
380
|
+
/** The fence policy it runs under: v2 reads other tasks' checkouts (never writes them). */
|
|
381
|
+
policy?: FencePolicy;
|
|
382
|
+
/** The agent the launch runs, whose own sign-in files stay its to write (PROVIDER_HOME). */
|
|
383
|
+
provider?: string;
|
|
384
|
+
/** Scratch folders (a flow's) that stay denied whole inside the pool, whatever the policy. */
|
|
385
|
+
scratchRoots?: readonly string[];
|
|
386
|
+
};
|
|
387
|
+
/** A launch's sealed sandbox terms (builder.ts launchSandboxOf): its policy version, read denies and mach services. A
|
|
388
|
+
* launch with none sealed is v1 with none of them. */
|
|
389
|
+
export type LaunchSandbox = {
|
|
390
|
+
policy: FencePolicy;
|
|
391
|
+
readDeny: readonly string[];
|
|
392
|
+
machLookup: readonly string[];
|
|
393
|
+
};
|
|
394
|
+
export declare const LEGACY_SANDBOX: LaunchSandbox;
|
|
395
|
+
/** A build's sandbox terms, exactly as its approval sealed them: an approval from before 0.9.70 is policy v1 (the home
|
|
396
|
+
* configuration denied) with nothing listed. */
|
|
397
|
+
export declare function launchSandboxOf(profile: ExecutionProfile): LaunchSandbox;
|
|
398
|
+
/** A launch's whole fence: its denied paths, what it gives back, and that it runs inside the OS fence or not at all.
|
|
399
|
+
* Spread into RunOptions as is. */
|
|
400
|
+
export type LaunchFence = {
|
|
401
|
+
kind: FenceKind;
|
|
402
|
+
fence: string[];
|
|
403
|
+
fenceGrants: FenceGrants;
|
|
404
|
+
confinement: "required";
|
|
405
|
+
};
|
|
406
|
+
/** The one place a launch's fence is made (docs/plans/sandbox-parity.md C1): its denies, its grants, and
|
|
407
|
+
* `confinement: "required"`, so the spawn road refuses it where no OS fence applies rather than running it bare. */
|
|
408
|
+
export declare function fenceFor(kind: FenceKind, ctx: FenceContext): LaunchFence;
|
|
409
|
+
/** The flows' scratch folders inside the checkout pools (`flow-checks` beside the checkouts) and any a caller names,
|
|
410
|
+
* each denied by name whether or not it is there yet, and where it is a link, where it leads too. */
|
|
411
|
+
export declare function sealedScratch(poolRoots: readonly (string | null)[], named: readonly string[]): string[];
|
|
412
|
+
/**
|
|
413
|
+
* Toolroll's own install as no launch may write it: this package's folder (its compiled code, the sandbox launch
|
|
414
|
+
* helper among it), the sandbox runtime's package wherever it was installed, and the Node running them with the
|
|
415
|
+
* folder it lives in (its npm and global packages beside it), as the real folders they are. Never a folder that holds
|
|
416
|
+
* the launch's own checkout or its repository (a build of Toolroll's own source, run from it: that repository's other
|
|
417
|
+
* files are the primary checkout's, write-denied already), the home folder or one above it, and nothing the user
|
|
418
|
+
* doesn't own (root's folders are out of a build's reach already).
|
|
419
|
+
*/
|
|
420
|
+
export declare function toolrollInstall(worktree: string | null, home: string, repository?: string | null): string[];
|
|
421
|
+
/** Names that run code or reconfigure tools for whoever opens the project next: the sandbox runtime's DANGEROUS_FILES
|
|
422
|
+
* (@anthropic-ai/sandbox-runtime sandbox-utils), and the names its mandatory denies can't hold when they are made
|
|
423
|
+
* after a launch starts (`.envrc`, package-manager and Claude settings). */
|
|
424
|
+
export declare const PROTECTED_FILES: readonly string[];
|
|
425
|
+
/** Folders protected whole, wherever they are (the runtime's dangerous directories, with Git's hooks). */
|
|
426
|
+
export declare const PROTECTED_DIRS: readonly string[];
|
|
427
|
+
/**
|
|
428
|
+
* The handoff's backstop (builder.ts commit): the changed paths (relative, from Git) that are protected names at any
|
|
429
|
+
* depth, in any case. The sandbox runtime denies these when a launch starts; one made after it started (a new nested
|
|
430
|
+
* folder's `.envrc`, say) gets through the sandbox, and a result that holds it is refused.
|
|
431
|
+
*/
|
|
432
|
+
export declare function protectedChanges(changed: readonly string[]): string[];
|
|
365
433
|
/**
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
* Its folders are still resolved by name (Node has no openat), so a folder above it swapped for a link between the
|
|
371
|
-
* checks and the open would be followed. That is detected after the fact: every folder from / down to the file's own
|
|
372
|
-
* is looked at (no link, the same device and inode) before the open and again after it, the file's real path is
|
|
373
|
-
* checked again, and the name must still be the very file that was opened. Any difference reads nothing.
|
|
374
|
-
*
|
|
375
|
-
* Residual risk: a swap made and undone entirely between the two looks isn't seen. On a Mac every build's and check's
|
|
376
|
-
* fence denies the home configuration names (~/.codex among them) by name, present or not, so no build or sandboxed
|
|
377
|
-
* check can make that swap; only a check the operator set to Not sandboxed, or the person themselves, still could.
|
|
378
|
-
* `afterOpen` is for tests: it runs between the open and the second look.
|
|
434
|
+
* The handoff's backstop for links (builder.ts commit): a staged symbolic link (Git mode 120000) whose path is a
|
|
435
|
+
* protected name or a folder on the way to one (`pkg/.claude`, `.vscode`, `lib/.git`), at any depth, in any case. A
|
|
436
|
+
* link there would lead whoever opens the project to settings, hooks or commands kept anywhere.
|
|
379
437
|
*/
|
|
380
|
-
export declare function
|
|
381
|
-
|
|
382
|
-
|
|
438
|
+
export declare function protectedLinks(links: readonly string[]): string[];
|
|
439
|
+
/** The home folder's own persistence points beyond the protected names (login items, systemd user units and autostart,
|
|
440
|
+
* Git's, gh's, pip's and fish's configuration, the shells' other startup files, SSH and GnuPG, and the folders on a
|
|
441
|
+
* login PATH). Under `.config/` each also stands under $XDG_CONFIG_HOME. */
|
|
442
|
+
export declare const HOME_PERSISTENCE: readonly string[];
|
|
383
443
|
/**
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
444
|
+
* What no launch writes, whatever its writes, as static write denies for the sandbox runtime (whose own protected names
|
|
445
|
+
* are anchored at the launch's folder only) and Codex's profile: the home folder's protected names (shell profiles, Git
|
|
446
|
+
* and tool configuration), its persistence points and sign-ins (but the launch's own agent's exact sign-in files), and
|
|
447
|
+
* the bound repository's own Git config, hooks and submodules (a linked worktree reaches them through its `.git`
|
|
448
|
+
* file). Each by its name and by where it leads now (a linked ~/.config or LaunchAgents is denied at both ends). On
|
|
449
|
+
* Linux only those there at launch: the runtime holds an absent name with a stand-in nothing can read for the launch's
|
|
450
|
+
* length, and the person's own git and shells then fail on their ~/.gitconfig and ~/.bashrc (measured). Under a write
|
|
451
|
+
* allowlist an absent one is out of reach anyway; under `anywhere` it can be made (an accepted limit).
|
|
387
452
|
*/
|
|
388
|
-
export declare function
|
|
453
|
+
export declare function homeWriteDenies(home: string, binding: WorktreeBinding | null, provider: string | null, platform?: NodeJS.Platform, env?: Readonly<Record<string, string | undefined>>): string[];
|
|
454
|
+
/** A tool server's grants from its build's: never the agent's network or write allowlist (a server keeps its own state
|
|
455
|
+
* where it always has), and never the build's own checkout or launch folder read back (nothing a build wrote loads in a
|
|
456
|
+
* process that holds a tool's secrets). */
|
|
457
|
+
export declare function serverGrantsOf(grants: FenceGrants): FenceGrants;
|