@clipboard-health/groundcrew 4.48.0 → 4.48.2
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/clearance-allow-hosts +1 -0
- package/crew.config.example.ts +2 -2
- package/dist/commands/doctor.js +0 -25
- package/dist/commands/openWorkspace.d.ts.map +1 -1
- package/dist/commands/openWorkspace.js +14 -17
- package/dist/commands/resumeWorkspace.d.ts.map +1 -1
- package/dist/commands/resumeWorkspace.js +7 -15
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +16 -18
- package/dist/lib/agentLaunch.d.ts +13 -11
- package/dist/lib/agentLaunch.d.ts.map +1 -1
- package/dist/lib/agentLaunch.js +102 -52
- package/dist/lib/cmuxAgentHookInstall.d.ts +17 -0
- package/dist/lib/cmuxAgentHookInstall.d.ts.map +1 -0
- package/dist/lib/cmuxAgentHookInstall.js +42 -0
- package/dist/lib/codexConfigRelocation.d.ts +60 -0
- package/dist/lib/codexConfigRelocation.d.ts.map +1 -0
- package/dist/lib/codexConfigRelocation.js +70 -0
- package/dist/lib/commandRunner.d.ts +9 -0
- package/dist/lib/commandRunner.d.ts.map +1 -1
- package/dist/lib/commandRunner.js +1 -0
- package/dist/lib/config.d.ts +8 -12
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +1 -2
- package/dist/lib/gitCommonDir.d.ts +7 -0
- package/dist/lib/gitCommonDir.d.ts.map +1 -0
- package/dist/lib/gitCommonDir.js +15 -0
- package/dist/lib/host.d.ts +0 -14
- package/dist/lib/host.d.ts.map +1 -1
- package/dist/lib/host.js +1 -8
- package/dist/lib/launchCommand.d.ts +32 -52
- package/dist/lib/launchCommand.d.ts.map +1 -1
- package/dist/lib/launchCommand.js +96 -187
- package/dist/lib/localRunner.d.ts +4 -5
- package/dist/lib/localRunner.d.ts.map +1 -1
- package/dist/lib/localRunner.js +4 -23
- package/docs/configuration.md +3 -3
- package/docs/runners.md +7 -50
- package/docs/setup-hooks.md +1 -1
- package/docs/spec/e2e-scenario-catalog.md +1 -1
- package/docs/spec/v2-design.md +7 -7
- package/docs/task-sources.md +3 -3
- package/package.json +2 -5
- package/static/demo.tape +0 -2
- package/static/render-demo.sh +0 -1
- package/dist/lib/clearanceHosts.d.ts +0 -31
- package/dist/lib/clearanceHosts.d.ts.map +0 -1
- package/dist/lib/clearanceHosts.js +0 -96
- package/dist/lib/srtLaunch.d.ts +0 -61
- package/dist/lib/srtLaunch.d.ts.map +0 -1
- package/dist/lib/srtLaunch.js +0 -107
- package/dist/lib/srtPolicy.d.ts +0 -118
- package/dist/lib/srtPolicy.d.ts.map +0 -1
- package/dist/lib/srtPolicy.js +0 -371
package/dist/lib/srtPolicy.js
DELETED
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate the srt (Anthropic sandbox-runtime) settings object for a single
|
|
3
|
-
* agent launch. srt is only a sandbox engine — it has no groundcrew- or
|
|
4
|
-
* agent-aware policy of its own — so this module owns the policy ergonomics
|
|
5
|
-
* that safehouse previously supplied via its bundled `.sb` profiles.
|
|
6
|
-
*
|
|
7
|
-
* The shape, in one place:
|
|
8
|
-
*
|
|
9
|
-
* - **Reads** start broad and are clamped: `denyRead` masks the whole home
|
|
10
|
-
* region (`/Users` on macOS, `/home` + `/root` on Linux) so the agent cannot
|
|
11
|
-
* read `~/.ssh`, `~/.aws`, shell history, or unrelated repos; `allowRead`
|
|
12
|
-
* then re-opens exactly the worktree, the repo's git metadata, the language
|
|
13
|
-
* toolchains needed to *run* the agent, the agent's own config/credential
|
|
14
|
-
* dirs, and — on macOS, for keychain-authenticated agents (claude) — the user
|
|
15
|
-
* keychain dir (`~/Library/Keychains`) so the agent can authenticate under
|
|
16
|
-
* the home mask. srt skips non-existent allow/deny paths, so listing a path
|
|
17
|
-
* that isn't present (a Linux keychain dir, an uninstalled toolchain) is
|
|
18
|
-
* harmless.
|
|
19
|
-
* - **Writes** are allow-only in srt. Two STAFF-1305 structural fixes shape it:
|
|
20
|
-
* 1. **Agent state (work item 1).** The host-CLI persistence vector (planting
|
|
21
|
-
* hooks, `mcpServers`, commands, plugins, … that execute on the user's
|
|
22
|
-
* next host run) is closed per agent. **claude** runs with a writable
|
|
23
|
-
* `~/.claude` (its Bash tool creates `session-env`/scratch state there) but
|
|
24
|
-
* every fixed-path executable/instruction surface — including
|
|
25
|
-
* `~/.claude.json` (`mcpServers`) and the bundled `chrome` binary — is
|
|
26
|
-
* denied; claude tolerates those write denials. **codex** hard-fails with a
|
|
27
|
-
* read-only home, so it is pointed at a relocated, per-launch writable
|
|
28
|
-
* config dir (`CODEX_HOME`, see {@link agentConfigRelocation}) and its real
|
|
29
|
-
* `~/.codex` is never write-granted at all. **cursor-agent** keeps a
|
|
30
|
-
* writable `~/.cursor` (chats/projects/resume) but denies mcp.json,
|
|
31
|
-
* hooks, rules, skills, plugins, and commands — the host-RCE surfaces.
|
|
32
|
-
* 2. **Git (work item 2).** The git common dir is granted write as a **narrow
|
|
33
|
-
* allowlist** of exactly the paths `status/diff/add/commit/push/gc` write —
|
|
34
|
-
* never wholesale — so the per-worktree gitdir redirection files, sibling
|
|
35
|
-
* worktree gitdirs, and the repo `config`/`hooks` stay unwritable.
|
|
36
|
-
* `denyWrite` is belt-and-suspenders over global toolchain bins and the agent
|
|
37
|
-
* homes; it uses **literal paths only** because bubblewrap silently ignores
|
|
38
|
-
* globs on Linux.
|
|
39
|
-
* - **Network** is allow-only and sourced from the existing clearance
|
|
40
|
-
* allowlist (see {@link ./clearanceHosts.ts}); local binding and unix sockets
|
|
41
|
-
* stay off (the docker socket and the DNS-exfil vector in srt#88).
|
|
42
|
-
*
|
|
43
|
-
* `allowPty` is on because the agent runs interactively under tmux;
|
|
44
|
-
* `allowGitConfig` stays off so the agent cannot rewrite `~/.gitconfig` or
|
|
45
|
-
* `.git/config` (both readable, just not writable).
|
|
46
|
-
*/
|
|
47
|
-
import os from "node:os";
|
|
48
|
-
import path from "node:path";
|
|
49
|
-
import process from "node:process";
|
|
50
|
-
import { SandboxRuntimeConfigSchema, } from "@anthropic-ai/sandbox-runtime";
|
|
51
|
-
/**
|
|
52
|
-
* Return the config-relocation spec for an agent, or `undefined` when the agent
|
|
53
|
-
* runs with a writable real home + deny-list and does not relocate (claude,
|
|
54
|
-
* unknown agents).
|
|
55
|
-
*/
|
|
56
|
-
export function agentConfigRelocation(agent) {
|
|
57
|
-
return AGENT_CONFIG_RELOCATIONS[agent.toLowerCase()];
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Per-agent credential/config profiles. Deliberately narrow — no blanket
|
|
61
|
-
* `~/.config`, which would re-expose unrelated apps' secrets. An unknown agent
|
|
62
|
-
* gets no extra home access and must be granted paths explicitly.
|
|
63
|
-
*
|
|
64
|
-
* claude keeps a writable home (`writePaths`) with the executable surfaces
|
|
65
|
-
* re-closed (`denyPaths`) because its macOS keychain credential is bound to the
|
|
66
|
-
* default config dir, so it cannot be relocated. codex has no `writePaths`: it
|
|
67
|
-
* relocates (see `AGENT_CONFIG_RELOCATIONS`), so its real `~/.codex` is
|
|
68
|
-
* read-only and no per-surface deny-list is needed there.
|
|
69
|
-
*/
|
|
70
|
-
const AGENT_SRT_PROFILES = {
|
|
71
|
-
claude: {
|
|
72
|
-
readPaths: [".claude", ".claude.json"],
|
|
73
|
-
writePaths: [".claude"],
|
|
74
|
-
denyPaths: [
|
|
75
|
-
// The mcpServers config — claude spawns these commands on every startup,
|
|
76
|
-
// the sharpest host-RCE persistence vector. claude tolerates this being
|
|
77
|
-
// read-only (validated live; it does not write it during a task).
|
|
78
|
-
".claude.json",
|
|
79
|
-
".claude/settings.json",
|
|
80
|
-
".claude/settings.local.json",
|
|
81
|
-
".claude/commands",
|
|
82
|
-
".claude/agents",
|
|
83
|
-
".claude/plugins",
|
|
84
|
-
".claude/skills",
|
|
85
|
-
".claude/hooks",
|
|
86
|
-
".claude/statusline.sh",
|
|
87
|
-
".claude/CLAUDE.md",
|
|
88
|
-
// Bundled browser binary — replaceable with a malicious one that runs when
|
|
89
|
-
// claude next drives a browser on the host.
|
|
90
|
-
".claude/chrome",
|
|
91
|
-
// ~/.claude is itself a git repo; deny the executable surfaces within its
|
|
92
|
-
// gitdir (commits, if any, still write objects/refs).
|
|
93
|
-
".claude/.git/hooks",
|
|
94
|
-
".claude/.git/config",
|
|
95
|
-
],
|
|
96
|
-
usesMacosKeychain: true,
|
|
97
|
-
},
|
|
98
|
-
codex: {
|
|
99
|
-
// Read-only: codex relocates its writable home (CODEX_HOME), so the real
|
|
100
|
-
// ~/.codex never needs write and no per-surface deny-list is required.
|
|
101
|
-
readPaths: [".codex"],
|
|
102
|
-
writePaths: [],
|
|
103
|
-
denyPaths: [],
|
|
104
|
-
},
|
|
105
|
-
"cursor-agent": {
|
|
106
|
-
// Writable state for chats/projects/resume (`--continue`), matching the
|
|
107
|
-
// paths Safehouse's cursor-agent profile re-opens. Persistence surfaces
|
|
108
|
-
// that would execute on the user's next host run are carved back out.
|
|
109
|
-
readPaths: [".cursor", ".config/cursor", ".local/share/cursor-agent"],
|
|
110
|
-
writePaths: [
|
|
111
|
-
".cursor",
|
|
112
|
-
".config/cursor",
|
|
113
|
-
".cache/cursor-compile-cache",
|
|
114
|
-
"Library/Caches/cursor-compile-cache",
|
|
115
|
-
],
|
|
116
|
-
denyPaths: [
|
|
117
|
-
// mcpServers — the sharpest host-RCE vector (same class as claude's
|
|
118
|
-
// ~/.claude.json mcpServers).
|
|
119
|
-
".cursor/mcp.json",
|
|
120
|
-
// Lifecycle hooks run on the user's next Cursor launch.
|
|
121
|
-
".cursor/hooks.json",
|
|
122
|
-
".cursor/hooks",
|
|
123
|
-
// Global rules can inject instructions into future Cursor sessions.
|
|
124
|
-
".cursor/rules",
|
|
125
|
-
".cursor/skills-cursor",
|
|
126
|
-
".cursor/plugins",
|
|
127
|
-
".cursor/commands",
|
|
128
|
-
],
|
|
129
|
-
usesMacosKeychain: true,
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
const AGENT_CONFIG_RELOCATIONS = {
|
|
133
|
-
codex: {
|
|
134
|
-
configDirEnv: "CODEX_HOME",
|
|
135
|
-
sourceHomeRelativeDir: ".codex",
|
|
136
|
-
// auth.json carries the ChatGPT OAuth tokens (codex reads creds from a file,
|
|
137
|
-
// not the keychain); config.toml preserves the user's codex configuration.
|
|
138
|
-
seedFiles: ["auth.json", "config.toml"],
|
|
139
|
-
},
|
|
140
|
-
};
|
|
141
|
-
/** macOS user keychain dir, re-opened read-only for keychain-authenticated agents. */
|
|
142
|
-
const MACOS_KEYCHAIN_READ_PATH = "Library/Keychains";
|
|
143
|
-
/**
|
|
144
|
-
* Language toolchains and version managers re-opened read-only so the agent's
|
|
145
|
-
* runtime (and any installed CLIs) can execute even though they live under the
|
|
146
|
-
* home deny-read mask.
|
|
147
|
-
*
|
|
148
|
-
* srt's `allowRead` takes precedence over `denyRead`, so a credential carve-out
|
|
149
|
-
* is impossible once a parent is re-opened — the roots themselves must be
|
|
150
|
-
* narrow. Pure version-manager dirs (no credentials) are kept whole so version
|
|
151
|
-
* resolution (`nvm use`, etc.) works; multi-purpose homes are narrowed to their
|
|
152
|
-
* executable + dependency-cache subpaths so credential/config files (e.g.
|
|
153
|
-
* `~/.cargo/credentials.toml`) and unrelated app state (`~/.local/share`) stay
|
|
154
|
-
* masked. The node runtime itself is re-opened separately via `nodePrefix`.
|
|
155
|
-
* (Polyglot coverage is best-effort + user-extensible; validated in STAFF-1305.)
|
|
156
|
-
*/
|
|
157
|
-
const TOOLCHAIN_READ_ROOTS = [
|
|
158
|
-
".nvm",
|
|
159
|
-
".rustup",
|
|
160
|
-
".asdf",
|
|
161
|
-
".volta",
|
|
162
|
-
".pyenv",
|
|
163
|
-
".rbenv",
|
|
164
|
-
".npm", // npm cache; the ~/.npmrc credential file lives at $HOME and stays denied
|
|
165
|
-
".local/bin",
|
|
166
|
-
".local/lib",
|
|
167
|
-
".cargo/bin",
|
|
168
|
-
".cargo/registry",
|
|
169
|
-
".cargo/git",
|
|
170
|
-
".bun/bin",
|
|
171
|
-
".bun/install",
|
|
172
|
-
".deno/bin",
|
|
173
|
-
"go/bin",
|
|
174
|
-
"go/pkg",
|
|
175
|
-
];
|
|
176
|
-
/**
|
|
177
|
-
* The git common dir is granted write only at these relative subpaths — never
|
|
178
|
-
* wholesale — so the agent's `status/diff/add/commit/push` + `gc`/`pack-refs`
|
|
179
|
-
* work while the persistence/tamper surfaces stay unwritable by *omission*
|
|
180
|
-
* (macOS Seatbelt is deny-beats-allow, so a denied parent cannot be re-allowed
|
|
181
|
-
* for a child — the allowlist is the only correct shape). Closed by not being
|
|
182
|
-
* listed: `config`, `hooks`, `modules`, and **sibling** worktree gitdirs under
|
|
183
|
-
* `worktrees/<other>` (cross-task tamper). This worktree's own gitdir is
|
|
184
|
-
* granted separately and its redirection files carved back out (see
|
|
185
|
-
* `gitCommonWriteDenies`).
|
|
186
|
-
*
|
|
187
|
-
* Enumerated against a live run under srt (STAFF-1305): `objects` (loose +
|
|
188
|
-
* packs + commit-graph), `refs` + `logs` (branch + remote-tracking refs and
|
|
189
|
-
* their reflogs — granted whole because `gc` packs/deletes refs across *all*
|
|
190
|
-
* branches, so scoping to the current branch would break `gc`), `packed-refs`
|
|
191
|
-
* (+ `.lock`/`.new` temps), `info` (`update-server-info`), and the root-level
|
|
192
|
-
* `gc.pid`/`HEAD`/`ORIG_HEAD`/`FETCH_HEAD` (+ `.lock` temps) that `gc`'s
|
|
193
|
-
* repo-global reflog expiry touches. None are code-execution surfaces.
|
|
194
|
-
*/
|
|
195
|
-
const GIT_COMMON_WRITE_PATHS = [
|
|
196
|
-
"objects",
|
|
197
|
-
"refs",
|
|
198
|
-
"logs",
|
|
199
|
-
"info",
|
|
200
|
-
"packed-refs",
|
|
201
|
-
"packed-refs.lock",
|
|
202
|
-
"packed-refs.new",
|
|
203
|
-
"gc.pid",
|
|
204
|
-
"gc.pid.lock",
|
|
205
|
-
"HEAD",
|
|
206
|
-
"HEAD.lock",
|
|
207
|
-
"ORIG_HEAD",
|
|
208
|
-
"FETCH_HEAD",
|
|
209
|
-
];
|
|
210
|
-
/**
|
|
211
|
-
* Every agent credential/state home dir. A profile that does NOT own one of
|
|
212
|
-
* these (it isn't in the profile's `writePaths`) must deny writes to it — both
|
|
213
|
-
* as cross-agent defense (the codex profile shouldn't write `~/.claude`) and to
|
|
214
|
-
* override srt's hardcoded default write path `~/.claude/debug`, which
|
|
215
|
-
* `getDefaultWritePaths()` adds to every policy. Without this, that default
|
|
216
|
-
* re-opens `~/.claude/debug` (and, on Linux, makes it readable via the write
|
|
217
|
-
* bind) even under the profile-neutral prepare policy and the relocating codex
|
|
218
|
-
* profile. `denyWrite` wins over `allowWrite`, so denying the home dir overrides
|
|
219
|
-
* the default.
|
|
220
|
-
*/
|
|
221
|
-
const ALL_AGENT_HOME_DIRS = [".claude", ".codex", ".cursor"];
|
|
222
|
-
/** Git identity/config the agent reads (never writes — see `allowGitConfig`). */
|
|
223
|
-
const GIT_READ_PATHS = [".gitconfig", ".config/git"];
|
|
224
|
-
/**
|
|
225
|
-
* Global toolchain bin/module locations writes are denied to, to close the
|
|
226
|
-
* agent-safehouse#102 persistence vector (modifying a globally-installed CLI
|
|
227
|
-
* that the user later runs outside the sandbox). Home-relative literals.
|
|
228
|
-
*
|
|
229
|
-
* `.npm/_npx` is denied even though `~/.npm` is writable for the npm cache:
|
|
230
|
-
* `npx` stores downloaded tools there as ready-to-run binaries, so an agent
|
|
231
|
-
* that poisons that cache would get host execution the next time the user runs
|
|
232
|
-
* `npx <tool>` outside the sandbox — the same vector as the bin dirs above.
|
|
233
|
-
*/
|
|
234
|
-
const TOOLCHAIN_WRITE_DENY = [
|
|
235
|
-
".cargo/bin",
|
|
236
|
-
"go/bin",
|
|
237
|
-
".bun/install/global",
|
|
238
|
-
".deno/bin",
|
|
239
|
-
".local/bin",
|
|
240
|
-
".npm-global",
|
|
241
|
-
".npm/_npx",
|
|
242
|
-
".npmrc",
|
|
243
|
-
];
|
|
244
|
-
export function buildSrtSettings(input) {
|
|
245
|
-
const platform = input.platform ?? process.platform;
|
|
246
|
-
const homeDir = input.homeDir ?? os.homedir();
|
|
247
|
-
const nodeExecPath = input.nodeExecPath ?? process.execPath;
|
|
248
|
-
const profile = AGENT_SRT_PROFILES[input.agent.toLowerCase()] ?? {
|
|
249
|
-
readPaths: [],
|
|
250
|
-
writePaths: [],
|
|
251
|
-
denyPaths: [],
|
|
252
|
-
};
|
|
253
|
-
const underHome = (relativePath) => path.join(homeDir, relativePath);
|
|
254
|
-
const underGit = (relativePath) => path.join(input.gitCommonDir, relativePath);
|
|
255
|
-
const worktreeBasename = path.basename(input.worktreeDir);
|
|
256
|
-
// `<nodeBin>/../` is the node prefix; nvm/Volta-managed nodes keep their
|
|
257
|
-
// global modules at `<prefix>/lib/node_modules` and shims at `<prefix>/bin`.
|
|
258
|
-
const nodePrefix = path.dirname(path.dirname(nodeExecPath));
|
|
259
|
-
const nodeGlobalModules = path.join(nodePrefix, "lib", "node_modules");
|
|
260
|
-
const nodeBinDir = path.join(nodePrefix, "bin");
|
|
261
|
-
// `/mnt` masks WSL's Windows drive mounts (e.g. `/mnt/c/Users/<user>/.aws`,
|
|
262
|
-
// `.ssh`) — the Windows profile is readable from WSL and would otherwise
|
|
263
|
-
// bypass the home mask on a documented-supported platform. Harmless on native
|
|
264
|
-
// Linux (the worktree, if it lives under /mnt, is re-allowed below since
|
|
265
|
-
// allowRead wins over denyRead).
|
|
266
|
-
const denyRead = platform === "darwin" ? ["/Users"] : ["/home", "/root", "/mnt"];
|
|
267
|
-
// macOS keychain re-open for keychain-authenticated agents. Home-relative so
|
|
268
|
-
// it is a no-op on Linux (the path does not exist there; srt skips it), where
|
|
269
|
-
// these agents read credentials from a file under their (readable) home.
|
|
270
|
-
const keychainRead = platform === "darwin" && profile.usesMacosKeychain === true
|
|
271
|
-
? [underHome(MACOS_KEYCHAIN_READ_PATH)]
|
|
272
|
-
: [];
|
|
273
|
-
const allowRead = unique([
|
|
274
|
-
input.worktreeDir,
|
|
275
|
-
input.gitCommonDir,
|
|
276
|
-
nodePrefix,
|
|
277
|
-
...TOOLCHAIN_READ_ROOTS.map(underHome),
|
|
278
|
-
...GIT_READ_PATHS.map(underHome),
|
|
279
|
-
...profile.readPaths.map(underHome),
|
|
280
|
-
...(input.readOnlyDirs ?? []),
|
|
281
|
-
...keychainRead,
|
|
282
|
-
...(input.relocatedConfigDir === undefined ? [] : [input.relocatedConfigDir]),
|
|
283
|
-
...(input.taskSourceWritePaths ?? []),
|
|
284
|
-
]);
|
|
285
|
-
const allowWrite = unique([
|
|
286
|
-
input.worktreeDir,
|
|
287
|
-
underHome(".npm"),
|
|
288
|
-
// Narrow git allowlist — never the whole common dir (see GIT_COMMON_WRITE_PATHS).
|
|
289
|
-
...GIT_COMMON_WRITE_PATHS.map(underGit),
|
|
290
|
-
underGit(path.join("worktrees", worktreeBasename)),
|
|
291
|
-
...profile.writePaths.map(underHome),
|
|
292
|
-
// The agent's relocated, writable config home (codex). Absent for agents
|
|
293
|
-
// that write their real home behind a deny-list (claude).
|
|
294
|
-
...(input.relocatedConfigDir === undefined ? [] : [input.relocatedConfigDir]),
|
|
295
|
-
...(input.taskSourceWritePaths ?? []),
|
|
296
|
-
]);
|
|
297
|
-
const denyWrite = unique([
|
|
298
|
-
nodeGlobalModules,
|
|
299
|
-
nodeBinDir,
|
|
300
|
-
...TOOLCHAIN_WRITE_DENY.map(underHome),
|
|
301
|
-
// Carve the agent's executable/config surfaces back out of its writable
|
|
302
|
-
// state dir so a prompted agent can't plant a hook/command/plugin/mcpServer
|
|
303
|
-
// that runs on the user's next host invocation (denyWrite wins over
|
|
304
|
-
// allowWrite).
|
|
305
|
-
...profile.denyPaths.map(underHome),
|
|
306
|
-
// Deny agent home dirs this profile does not own — counters srt's default
|
|
307
|
-
// `~/.claude/debug` write path for the neutral prepare policy and the
|
|
308
|
-
// relocating codex profile, and keeps profiles from writing each other's
|
|
309
|
-
// credentials.
|
|
310
|
-
...ALL_AGENT_HOME_DIRS.filter((dir) => !profile.writePaths.includes(dir)).map(underHome),
|
|
311
|
-
// Carve the per-worktree git redirection + config files back out of the
|
|
312
|
-
// granted worktree gitdir: `commondir`/`gitdir` redirect git to a fake
|
|
313
|
-
// common dir with its own hooks/config, and `config.worktree` can set
|
|
314
|
-
// `core.*` hooks that run when git next operates here on the host. git
|
|
315
|
-
// writes these once at worktree creation, never during a task.
|
|
316
|
-
...gitCommonWriteDenies(input.gitCommonDir, worktreeBasename),
|
|
317
|
-
// The worktree's `.git` is a pointer *file* (`gitdir: …`). Deny writing it so
|
|
318
|
-
// the agent can't redirect the gitdir to a writable fake with its own
|
|
319
|
-
// config/hooks. git sets this pointer once at creation.
|
|
320
|
-
path.join(input.worktreeDir, ".git"),
|
|
321
|
-
]);
|
|
322
|
-
const settings = {
|
|
323
|
-
network: {
|
|
324
|
-
allowedDomains: [...input.allowedDomains],
|
|
325
|
-
deniedDomains: [],
|
|
326
|
-
allowLocalBinding: false,
|
|
327
|
-
allowUnixSockets: [],
|
|
328
|
-
allowAllUnixSockets: false,
|
|
329
|
-
},
|
|
330
|
-
filesystem: {
|
|
331
|
-
denyRead,
|
|
332
|
-
allowRead,
|
|
333
|
-
allowWrite,
|
|
334
|
-
denyWrite,
|
|
335
|
-
allowGitConfig: false,
|
|
336
|
-
},
|
|
337
|
-
allowPty: true,
|
|
338
|
-
};
|
|
339
|
-
// Fail closed: validate with srt's own schema before this is staged. srt's
|
|
340
|
-
// `loadConfig` `safeParse`s the settings file and, on ANY failure, returns
|
|
341
|
-
// null — at which point the CLI silently falls back to a default config with
|
|
342
|
-
// `denyRead: []`, disabling the home read mask for the launch. A single
|
|
343
|
-
// malformed `allowedDomains` entry (e.g. a URL or host:port that slipped
|
|
344
|
-
// through `collectAllowedDomains`) would otherwise trip that fail-open. Throw
|
|
345
|
-
// here instead so the launch aborts loudly rather than running unsandboxed.
|
|
346
|
-
const validation = SandboxRuntimeConfigSchema.safeParse(settings);
|
|
347
|
-
if (!validation.success) {
|
|
348
|
-
const detail = validation.error.issues
|
|
349
|
-
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
350
|
-
.join("; ");
|
|
351
|
-
throw new Error(`Generated srt settings failed validation (refusing to launch unsandboxed): ${detail}`);
|
|
352
|
-
}
|
|
353
|
-
return settings;
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Files inside this worktree's granted gitdir that must stay unwritable: the
|
|
357
|
-
* `commondir`/`gitdir` redirection pointers and the per-worktree
|
|
358
|
-
* `config.worktree`. Returned as denies so they override the gitdir's
|
|
359
|
-
* `allowWrite` grant.
|
|
360
|
-
*/
|
|
361
|
-
function gitCommonWriteDenies(gitCommonDir, worktreeBasename) {
|
|
362
|
-
const worktreeGitDir = path.join(gitCommonDir, "worktrees", worktreeBasename);
|
|
363
|
-
return [
|
|
364
|
-
path.join(worktreeGitDir, "commondir"),
|
|
365
|
-
path.join(worktreeGitDir, "gitdir"),
|
|
366
|
-
path.join(worktreeGitDir, "config.worktree"),
|
|
367
|
-
];
|
|
368
|
-
}
|
|
369
|
-
function unique(values) {
|
|
370
|
-
return [...new Set(values)];
|
|
371
|
-
}
|