@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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { createRequire } from "node:module";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { BUILD_SECRET_NAMES, hasPreLaunchEnv, } from "./config.js";
|
|
@@ -38,51 +37,6 @@ export function resolveSafehouseClearancePath(baseUrl = import.meta.url) {
|
|
|
38
37
|
return path.resolve(path.dirname(clearancePackageJson), "safehouse", "safehouse-clearance");
|
|
39
38
|
}
|
|
40
39
|
const SAFEHOUSE_CLEARANCE_WRAPPER_PATH = resolveSafehouseClearancePath();
|
|
41
|
-
/**
|
|
42
|
-
* Resolve the `srt` CLI shipped by `@anthropic-ai/sandbox-runtime` (a pinned
|
|
43
|
-
* groundcrew dependency) via Node's module resolution, reading the package's
|
|
44
|
-
* `bin` field so the path survives version bumps that move the entry point.
|
|
45
|
-
* The resolved `dist/cli.js` carries a `#!/usr/bin/env node` shebang and npm
|
|
46
|
-
* marks it executable, so it is exec'd directly like the safehouse wrapper.
|
|
47
|
-
*
|
|
48
|
-
* @param baseUrl - **Test-only seam.** Production callers must omit this so the
|
|
49
|
-
* helper resolves from this module's URL. Tests pass an invalid value to
|
|
50
|
-
* exercise the catch branch.
|
|
51
|
-
*/
|
|
52
|
-
export function resolveSrtBinPath(baseUrl = import.meta.url) {
|
|
53
|
-
let packageJsonPath;
|
|
54
|
-
try {
|
|
55
|
-
packageJsonPath = createRequire(baseUrl).resolve("@anthropic-ai/sandbox-runtime/package.json");
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
throw new Error("@anthropic-ai/sandbox-runtime is required by @clipboard-health/groundcrew for the srt runner but could not be resolved. " +
|
|
59
|
-
"Reinstall groundcrew's dependencies (`npm install`), or set local.runner to 'safehouse' or 'sdx'.", { cause: error });
|
|
60
|
-
}
|
|
61
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- reading our pinned dependency's well-known `bin` field
|
|
62
|
-
const manifest = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
63
|
-
return path.resolve(path.dirname(packageJsonPath), srtBinEntry(manifest));
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Extract the `srt` entry from a package manifest's `bin` field, which npm
|
|
67
|
-
* allows as either a bare string (single-bin packages) or a name→path map.
|
|
68
|
-
*/
|
|
69
|
-
export function srtBinEntry(manifest) {
|
|
70
|
-
const binEntry = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.["srt"];
|
|
71
|
-
if (binEntry === undefined) {
|
|
72
|
-
throw new Error("@anthropic-ai/sandbox-runtime package.json is missing the `srt` bin entry.");
|
|
73
|
-
}
|
|
74
|
-
return binEntry;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Lazily resolved + memoized srt bin path. Unlike the safehouse wrapper (eager
|
|
78
|
-
* at module load), this defers the `package.json` read until an srt launch is
|
|
79
|
-
* actually built — srt is opt-in, so non-srt runs never pay for it.
|
|
80
|
-
*/
|
|
81
|
-
let srtBinPathCache;
|
|
82
|
-
function srtBinPath() {
|
|
83
|
-
srtBinPathCache ??= resolveSrtBinPath();
|
|
84
|
-
return srtBinPathCache;
|
|
85
|
-
}
|
|
86
40
|
function renderAgentCommand(arguments_) {
|
|
87
41
|
return arguments_.agentCmd
|
|
88
42
|
.replaceAll("{{worktree}}", shellSingleQuote(arguments_.worktreeDir))
|
|
@@ -137,20 +91,15 @@ function safehouseClearanceWrapperCommand() {
|
|
|
137
91
|
function safehouseWrapperCommand(networkEgress) {
|
|
138
92
|
return networkEgress === "allowlisted" ? safehouseClearanceWrapperCommand() : "safehouse";
|
|
139
93
|
}
|
|
140
|
-
function trapCleanupLine(promptDir) {
|
|
141
|
-
const cleanupCmd = `rm -rf ${shellSingleQuote(promptDir)}
|
|
94
|
+
function trapCleanupLine({ promptDir, additionalCleanupCommands = [], }) {
|
|
95
|
+
const cleanupCmd = [`rm -rf ${shellSingleQuote(promptDir)}`, ...additionalCleanupCommands].join("; ");
|
|
142
96
|
return `trap ${shellSingleQuote(cleanupCmd)} EXIT`;
|
|
143
97
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
* splice `preLaunch` between the `cd` and the secrets source — preLaunch must
|
|
150
|
-
* never see build-time secrets in env.
|
|
151
|
-
*/
|
|
152
|
-
function hostTrapAndCd(arguments_) {
|
|
153
|
-
return [trapCleanupLine(arguments_.promptDir), `cd ${shellSingleQuote(arguments_.workingDir)}`];
|
|
98
|
+
function hostTrapAndCd({ workingDir, promptDir, additionalCleanupCommands = [], }) {
|
|
99
|
+
return [
|
|
100
|
+
trapCleanupLine({ promptDir, additionalCleanupCommands }),
|
|
101
|
+
`cd ${shellSingleQuote(workingDir)}`,
|
|
102
|
+
];
|
|
154
103
|
}
|
|
155
104
|
/**
|
|
156
105
|
* Optional source-of-secrets line. Returns `[]` when no `secretsFile` is
|
|
@@ -173,9 +122,9 @@ function preLaunchPromptAndExec(arguments_) {
|
|
|
173
122
|
return lines;
|
|
174
123
|
}
|
|
175
124
|
/**
|
|
176
|
-
* Shared by the safehouse
|
|
125
|
+
* Shared by the safehouse and sdx builders: render the `exec <agent> "$@"`
|
|
177
126
|
* inner command and the optional status-reported prepareWorktree hook. The
|
|
178
|
-
* `{{sandbox}}` template is filled from `sandboxName` (empty for safehouse
|
|
127
|
+
* `{{sandbox}}` template is filled from `sandboxName` (empty for safehouse).
|
|
179
128
|
*/
|
|
180
129
|
function renderPrepareAndAgentCommand(arguments_) {
|
|
181
130
|
const agentCmd = renderAgentCommand({
|
|
@@ -193,7 +142,7 @@ function renderPrepareAndAgentCommand(arguments_) {
|
|
|
193
142
|
};
|
|
194
143
|
}
|
|
195
144
|
/**
|
|
196
|
-
* Shared host-shell prologue for the
|
|
145
|
+
* Shared host-shell prologue for the safehouse runner: when a
|
|
197
146
|
* `preLaunch` hook is present, scrub build-secret and `preLaunchEnv` names so it
|
|
198
147
|
* cannot see them; then source build secrets, read the staged prompt into `$_p`,
|
|
199
148
|
* and wipe the prompt dir. The caller arms the EXIT trap and `cd`s first.
|
|
@@ -254,7 +203,7 @@ export function isEnvironmentAssignment(token) {
|
|
|
254
203
|
/**
|
|
255
204
|
* Infer the agent's command basename from a agent `cmd` (skipping a leading
|
|
256
205
|
* `env`/`KEY=val` prefix). Safehouse uses it to pick the matching `.sb`
|
|
257
|
-
* profile
|
|
206
|
+
* profile.
|
|
258
207
|
*/
|
|
259
208
|
export function inferAgentCommandName(agentCmd) {
|
|
260
209
|
const tokens = tokenizeShellPrefix(agentCmd);
|
|
@@ -331,9 +280,6 @@ function promptPositional(omitPromptArgument) {
|
|
|
331
280
|
* prompt in hand.
|
|
332
281
|
*/
|
|
333
282
|
export function buildLaunchCommand(arguments_) {
|
|
334
|
-
if (arguments_.runner === "srt") {
|
|
335
|
-
return buildSrtLaunchCommand(arguments_);
|
|
336
|
-
}
|
|
337
283
|
if (arguments_.runner === "sdx") {
|
|
338
284
|
if (arguments_.definition.preLaunch !== undefined) {
|
|
339
285
|
throw new Error("preLaunch is not yet supported for runner='sdx'. Set local.runner to 'safehouse' or 'none', or open an issue for sdx support.");
|
|
@@ -342,7 +288,7 @@ export function buildLaunchCommand(arguments_) {
|
|
|
342
288
|
throw new Error("preLaunchEnv is not yet supported for runner='sdx'. Set local.runner to 'safehouse' or 'none', or open an issue for sdx support.");
|
|
343
289
|
}
|
|
344
290
|
if (arguments_.prepareWorktreeUnsandboxedCommand !== undefined) {
|
|
345
|
-
throw new Error("unsandboxedHooks.prepareWorktree is not supported for runner='sdx': the sdx container has no host to run it on. Remove unsandboxedHooks for this repo, or use runner 'safehouse'
|
|
291
|
+
throw new Error("unsandboxedHooks.prepareWorktree is not supported for runner='sdx': the sdx container has no host to run it on. Remove unsandboxedHooks for this repo, or use runner 'safehouse' or 'none'.");
|
|
346
292
|
}
|
|
347
293
|
return buildSdxLaunchCommand(arguments_);
|
|
348
294
|
}
|
|
@@ -465,10 +411,11 @@ function buildSafehouseLaunchCommand(arguments_) {
|
|
|
465
411
|
// separates it from the next argv token. See `resolveSafehouseAddDirs` for
|
|
466
412
|
// which paths these are and why.
|
|
467
413
|
const safehousePrepareAddDirs = arguments_.safehouseAddDirs ?? [];
|
|
468
|
-
const safehouseAgentAddDirs =
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
414
|
+
const safehouseAgentAddDirs = resolveSafehouseAgentAddDirs({
|
|
415
|
+
prepareAddDirs: safehousePrepareAddDirs,
|
|
416
|
+
agentAddDirs: arguments_.safehouseAgentAddDirs,
|
|
417
|
+
integrationAddDirs: safehouseAgentIntegration?.addDirs,
|
|
418
|
+
});
|
|
472
419
|
const safehouseAddDirsFlag = safehousePathListFlag("--add-dirs", safehousePrepareAddDirs);
|
|
473
420
|
const safehouseAgentAddDirsFlag = safehousePathListFlag("--add-dirs", safehouseAgentAddDirs);
|
|
474
421
|
const safehouseAgentAddDirsReadOnlyFlag = safehousePathListFlag("--add-dirs-ro", uniqueStrings([
|
|
@@ -479,13 +426,26 @@ function buildSafehouseLaunchCommand(arguments_) {
|
|
|
479
426
|
// profile only — the repo-controlled prepareWorktree hook never needs them.
|
|
480
427
|
const safehouseEnableFlag = safehouseEnableFeaturesFlag(arguments_.safehouseEnableFeatures ?? []);
|
|
481
428
|
const safehouseWrapper = safehouseWrapperCommand(arguments_.networkEgress);
|
|
429
|
+
// Extra paths an integration staged for the agent wrap (e.g. codex's
|
|
430
|
+
// relocated CODEX_HOME) and wants removed alongside the shim dir. Rendered
|
|
431
|
+
// once and reused by both the trap (failure path) and the explicit
|
|
432
|
+
// post-wrap cleanup (success path) below, so the two stay in lockstep.
|
|
433
|
+
const { teardownCleanupCmd, writeBackCmd } = safehouseIntegrationCleanupCommands(safehouseAgentIntegration);
|
|
482
434
|
// Defensive shim+promptDir trap: by the time we arm it, `rm -rf <promptDir>`
|
|
483
435
|
// has already run (line below) so the promptDir wipe is a no-op on the happy
|
|
484
436
|
// path. Keeps the failure-window between shim creation and the explicit
|
|
485
437
|
// post-wrap cleanup covered for both targets without an unarmed window.
|
|
486
|
-
const shimAndPromptCleanup =
|
|
438
|
+
const shimAndPromptCleanup = safehouseShimAndPromptCleanup({
|
|
439
|
+
promptDir,
|
|
440
|
+
writeBackCmd,
|
|
441
|
+
teardownCleanupCmd,
|
|
442
|
+
});
|
|
487
443
|
const shimAndPromptTrap = `trap ${shellSingleQuote(shimAndPromptCleanup)} EXIT`;
|
|
488
|
-
const lines = hostTrapAndCd({
|
|
444
|
+
const lines = hostTrapAndCd({
|
|
445
|
+
workingDir: arguments_.workingDir,
|
|
446
|
+
promptDir,
|
|
447
|
+
additionalCleanupCommands: teardownCleanupCmd === "" ? [] : [teardownCleanupCmd],
|
|
448
|
+
});
|
|
489
449
|
// Scrub inherited env before preLaunch (build secrets are copied out of
|
|
490
450
|
// `process.env`, which the launch shell inherits), then source secrets and
|
|
491
451
|
// read the staged prompt. See `hostPreLaunchSourceAndReadPrompt`.
|
|
@@ -510,130 +470,79 @@ function buildSafehouseLaunchCommand(arguments_) {
|
|
|
510
470
|
// Running the real launch chain as `sh -c` would make it see `sh`, so use
|
|
511
471
|
// an agent-named symlink to /bin/sh. This preserves per-agent profile
|
|
512
472
|
// selection without enabling every agent profile.
|
|
513
|
-
`{ ${safehouseWrapper} ${safehouseAgentAddDirsFlag}${safehouseAgentAddDirsReadOnlyFlag}${safehouseEnableFlag}${agentEnvPassFlag}"$_safehouse_shim" -c ${shellSingleQuote(agentCommand)} sh${promptPositional(arguments_.omitPromptArgument)}; _safehouse_status=$?; rm -rf "$_safehouse_shim_dir"
|
|
473
|
+
`{ ${safehouseWrapper} ${safehouseAgentAddDirsFlag}${safehouseAgentAddDirsReadOnlyFlag}${safehouseEnableFlag}${agentEnvPassFlag}"$_safehouse_shim" -c ${shellSingleQuote(agentCommand)} sh${promptPositional(arguments_.omitPromptArgument)}; _safehouse_status=$?; rm -rf "$_safehouse_shim_dir";${successPathCleanupSegment(writeBackCmd, teardownCleanupCmd)} trap - EXIT; exit "$_safehouse_status"; }`);
|
|
514
474
|
return lines.join(" && ");
|
|
515
475
|
}
|
|
516
|
-
|
|
517
|
-
|
|
476
|
+
/**
|
|
477
|
+
* Extra paths an integration staged for the agent wrap (e.g. codex's
|
|
478
|
+
* relocated `CODEX_HOME`) rendered as a single `rm -rf` command, or `""` when
|
|
479
|
+
* the integration staged nothing (Claude) — the empty string keeps every
|
|
480
|
+
* caller's string interpolation a no-op rather than branching itself.
|
|
481
|
+
*/
|
|
482
|
+
function safehouseTeardownCleanupCommand(teardownPaths) {
|
|
483
|
+
return teardownPaths.length === 0
|
|
484
|
+
? ""
|
|
485
|
+
: `rm -rf ${teardownPaths.map((teardownPath) => shellSingleQuote(teardownPath)).join(" ")}`;
|
|
518
486
|
}
|
|
519
|
-
function
|
|
520
|
-
return
|
|
487
|
+
function safehouseIntegrationCleanupCommands(integration) {
|
|
488
|
+
return {
|
|
489
|
+
teardownCleanupCmd: safehouseTeardownCleanupCommand(uniqueStrings(integration?.teardownPaths ?? [])),
|
|
490
|
+
writeBackCmd: safehouseWriteBackCommand(integration?.writeBackFiles ?? []),
|
|
491
|
+
};
|
|
521
492
|
}
|
|
522
|
-
function
|
|
523
|
-
return
|
|
493
|
+
function safehouseWriteBackCommand(writeBackFiles) {
|
|
494
|
+
return writeBackFiles
|
|
495
|
+
.map(({ baselinePath, stagedPath, sourcePath }) => {
|
|
496
|
+
const temporaryTemplate = shellSingleQuote(`${sourcePath}.groundcrew.XXXXXX`);
|
|
497
|
+
return [
|
|
498
|
+
`_groundcrew_write_back_tmp=$(/usr/bin/mktemp ${temporaryTemplate})`,
|
|
499
|
+
`if [ -n "$_groundcrew_write_back_tmp" ]; then if [ ! -L ${shellSingleQuote(stagedPath)} ] && [ -f ${shellSingleQuote(stagedPath)} ] && /bin/cp -P ${shellSingleQuote(stagedPath)} "$_groundcrew_write_back_tmp" && [ ! -L "$_groundcrew_write_back_tmp" ] && [ -f "$_groundcrew_write_back_tmp" ] && /bin/chmod 600 "$_groundcrew_write_back_tmp"; then if /usr/bin/cmp -s "$_groundcrew_write_back_tmp" ${shellSingleQuote(baselinePath)}; then :; elif ! /usr/bin/cmp -s ${shellSingleQuote(sourcePath)} ${shellSingleQuote(baselinePath)}; then echo 'groundcrew: skipped staged agent config write-back because the source changed concurrently' >&2; elif ! /bin/mv -f "$_groundcrew_write_back_tmp" ${shellSingleQuote(sourcePath)}; then echo 'groundcrew: failed to persist staged agent config' >&2; fi; else echo 'groundcrew: refused unsafe staged agent config write-back' >&2; fi; /bin/rm -f "$_groundcrew_write_back_tmp"; else echo 'groundcrew: failed to create staged agent config write-back file' >&2; fi`,
|
|
500
|
+
"unset _groundcrew_write_back_tmp",
|
|
501
|
+
].join("; ");
|
|
502
|
+
})
|
|
503
|
+
.join("; ");
|
|
524
504
|
}
|
|
525
505
|
/**
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
* the agent and bypass the filesystem read mask. A denylist can't enumerate
|
|
531
|
-
* every secret var, so we clear the env and re-add only these known-benign
|
|
532
|
-
* names plus per-wrap forwarded vars (build secrets / `preLaunchEnv`). Values
|
|
533
|
-
* are read from the host shell at runtime. Vars unset on the host become empty
|
|
534
|
-
* (harmless for these). srt injects proxy + TMPDIR vars into the child itself.
|
|
506
|
+
* The EXIT-trap command that wipes the profile-shim dir, the prompt dir, and
|
|
507
|
+
* any integration teardown paths — the failure-path cleanup. Shared with the
|
|
508
|
+
* explicit success-path cleanup below it (`successPathTeardownSegment`) so
|
|
509
|
+
* both paths remove the same set of staged paths.
|
|
535
510
|
*/
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
"
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
"
|
|
547
|
-
"
|
|
548
|
-
"PWD",
|
|
549
|
-
];
|
|
550
|
-
/** Render `env -i` forwarded assignments: ` NAME="$NAME"` per name (value taken from the host shell at runtime). */
|
|
551
|
-
function srtForwardedEnv(names) {
|
|
552
|
-
return names.map((name) => ` ${name}="$${name}"`).join("");
|
|
511
|
+
function safehouseShimAndPromptCleanup(input) {
|
|
512
|
+
return [
|
|
513
|
+
`rm -rf "$_safehouse_shim_dir"`,
|
|
514
|
+
`rm -rf ${shellSingleQuote(input.promptDir)}`,
|
|
515
|
+
...(input.writeBackCmd === "" ? [] : [input.writeBackCmd]),
|
|
516
|
+
...(input.teardownCleanupCmd === "" ? [] : [input.teardownCleanupCmd]),
|
|
517
|
+
].join("; ");
|
|
518
|
+
}
|
|
519
|
+
/** Success-path counterpart to the EXIT-trap integration cleanup. */
|
|
520
|
+
function successPathCleanupSegment(writeBackCmd, teardownCleanupCmd) {
|
|
521
|
+
const commands = [writeBackCmd, teardownCleanupCmd].filter((command) => command !== "");
|
|
522
|
+
return commands.length === 0 ? "" : ` ${commands.join("; ")};`;
|
|
553
523
|
}
|
|
554
524
|
/**
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
* Unlike safehouse there is no profile-by-basename selection, so no symlink
|
|
562
|
-
* shim is needed — srt takes the policy as an explicit `--settings` file. Each
|
|
563
|
-
* srt invocation runs under `env -i` (see `SRT_ENV_BASELINE`) so the agent gets
|
|
564
|
-
* a sanitized env instead of the inherited host env: the prepareWorktree wrap
|
|
565
|
-
* additionally forwards build secrets (for `npm ci` auth) and the agent wrap
|
|
566
|
-
* forwards the user's opt-in `preLaunchEnv` names — matching safehouse's
|
|
567
|
-
* sanitized baseline plus explicit pass-list posture. `preLaunch` and secret
|
|
568
|
-
* sourcing still run on the host shell (full env), exactly as under safehouse.
|
|
569
|
-
*
|
|
570
|
-
* The settings file lives in `srtSettingsDir` (a dedicated temp dir, never the
|
|
571
|
-
* prompt dir, which is wiped before the agent execs). The EXIT trap covers both
|
|
572
|
-
* the settings dir and the prompt dir for every failure window; the happy path
|
|
573
|
-
* removes the prompt dir before the agent wrap and the settings dir after it.
|
|
525
|
+
* Writable `--add-dirs` for the agent wrap: the shared prepare grants plus the
|
|
526
|
+
* task-source write paths plus any integration-staged dirs (e.g. codex's
|
|
527
|
+
* relocated `CODEX_HOME`), deduped. Kept out of `buildSafehouseLaunchCommand`
|
|
528
|
+
* so its per-source nullish handling doesn't count against that function's
|
|
529
|
+
* complexity budget.
|
|
574
530
|
*/
|
|
575
|
-
function
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
// capture the child's `sh -c '<cmd>'` as its OWN `-c` (dropping the trailing
|
|
591
|
-
// prompt positionals entirely), and an option-looking prompt/hook value could
|
|
592
|
-
// mutate srt's options (e.g. redirect `--settings`). `--` ends srt option
|
|
593
|
-
// parsing so everything after is the child argv.
|
|
594
|
-
const prepareTarget = `${srtBin} --settings ${shellSingleQuote(arguments_.srtPrepareSettingsFile)} --`;
|
|
595
|
-
const agentTarget = `${srtBin} --settings ${shellSingleQuote(arguments_.srtAgentSettingsFile)} --`;
|
|
596
|
-
// `env -i <baseline>` drops the inherited host env; each wrap re-adds only the
|
|
597
|
-
// benign baseline plus its forwarded names (`VAR="$VAR"` — value from the host
|
|
598
|
-
// shell at runtime; the names are safe identifiers, validated for
|
|
599
|
-
// preLaunchEnv). env -i isolates the agent wrap from build secrets, so no
|
|
600
|
-
// `unset` dance between wraps is needed.
|
|
601
|
-
const baseline = SRT_ENV_BASELINE.map((name) => `${name}="$${name}"`).join(" ");
|
|
602
|
-
const prepareForward = arguments_.secretsFile === undefined ? "" : srtForwardedEnv(BUILD_SECRET_NAMES);
|
|
603
|
-
const prepareWrap = `env -i ${baseline}${prepareForward} ${prepareTarget}`;
|
|
604
|
-
// The relocated config-home env (e.g. CODEX_HOME) is an explicit value, not a
|
|
605
|
-
// `VAR="$VAR"` host passthrough — groundcrew computed the staged path, it is
|
|
606
|
-
// not in the launch shell's env. The name is a fixed identifier; the value is
|
|
607
|
-
// single-quoted. Only the agent wrap gets it.
|
|
608
|
-
const agentConfigDirAssignment = arguments_.srtAgentConfigDirEnv === undefined
|
|
609
|
-
? ""
|
|
610
|
-
: ` ${arguments_.srtAgentConfigDirEnv.name}=${shellSingleQuote(arguments_.srtAgentConfigDirEnv.value)}`;
|
|
611
|
-
const agentWrap = `env -i ${baseline}${agentConfigDirAssignment}${srtForwardedEnv([
|
|
612
|
-
...(arguments_.definition.preLaunchEnv ?? []),
|
|
613
|
-
...workerEnvironmentNames(arguments_.workerEnvironment),
|
|
614
|
-
])} ${agentTarget}`;
|
|
615
|
-
// One EXIT trap wipes both the settings dir and the prompt dir, covering
|
|
616
|
-
// every failure window between here and the post-wrap cleanup.
|
|
617
|
-
const cleanup = `rm -rf ${shellSingleQuote(arguments_.srtSettingsDir)}; rm -rf ${shellSingleQuote(promptDir)}`;
|
|
618
|
-
const lines = [
|
|
619
|
-
`trap ${shellSingleQuote(cleanup)} EXIT`,
|
|
620
|
-
`cd ${shellSingleQuote(arguments_.workingDir)}`,
|
|
621
|
-
...hostPreLaunchSourceAndReadPrompt({
|
|
622
|
-
definition: arguments_.definition,
|
|
623
|
-
worktreeDir: arguments_.worktreeDir,
|
|
624
|
-
promptFile: arguments_.promptFile,
|
|
625
|
-
promptDir,
|
|
626
|
-
secretsFile: arguments_.secretsFile,
|
|
627
|
-
}),
|
|
628
|
-
];
|
|
629
|
-
if (arguments_.prepareWorktreeUnsandboxedCommand !== undefined) {
|
|
630
|
-
lines.push(hostPrepareWorktreeLine(arguments_.prepareWorktreeUnsandboxedCommand, arguments_.definition.preLaunchEnv ?? []));
|
|
631
|
-
}
|
|
632
|
-
if (prepareWorktreeCommand !== undefined) {
|
|
633
|
-
lines.push(`${prepareWrap} sh -c ${shellSingleQuote(prepareWorktreeCommand)}`);
|
|
634
|
-
}
|
|
635
|
-
lines.push(...workerEnvironmentExports(arguments_.workerEnvironment), `{ ${agentWrap} sh -c ${shellSingleQuote(agentCommand)} sh${promptPositional(arguments_.omitPromptArgument)}; _srt_status=$?; rm -rf ${shellSingleQuote(arguments_.srtSettingsDir)}; trap - EXIT; exit "$_srt_status"; }`);
|
|
636
|
-
return lines.join(" && ");
|
|
531
|
+
function resolveSafehouseAgentAddDirs(input) {
|
|
532
|
+
return uniqueStrings([
|
|
533
|
+
...input.prepareAddDirs,
|
|
534
|
+
...(input.agentAddDirs ?? []),
|
|
535
|
+
...(input.integrationAddDirs ?? []),
|
|
536
|
+
]);
|
|
537
|
+
}
|
|
538
|
+
function safehousePathListFlag(flagName, paths) {
|
|
539
|
+
return paths.length === 0 ? "" : `${flagName}=${shellSingleQuote(paths.join(":"))} `;
|
|
540
|
+
}
|
|
541
|
+
function safehouseEnableFeaturesFlag(features) {
|
|
542
|
+
return features.length === 0 ? "" : `--enable=${shellSingleQuote(features.join(","))} `;
|
|
543
|
+
}
|
|
544
|
+
function uniqueStrings(values) {
|
|
545
|
+
return [...new Set(values)];
|
|
637
546
|
}
|
|
638
547
|
function buildSdxLaunchCommand(arguments_) {
|
|
639
548
|
/* v8 ignore next 5 @preserve -- setupWorkspace passes sandboxName + sandbox config when picking sdx; missing fields are programmer errors */
|
|
@@ -659,7 +568,7 @@ function buildSdxLaunchCommand(arguments_) {
|
|
|
659
568
|
const sbxEnvironmentFlags = sbxEnvironmentNames.length === 0
|
|
660
569
|
? ""
|
|
661
570
|
: `${sbxEnvironmentNames.map((name) => `-e ${name}`).join(" ")} `;
|
|
662
|
-
const lines = [trapCleanupLine(promptDir)];
|
|
571
|
+
const lines = [trapCleanupLine({ promptDir })];
|
|
663
572
|
if (arguments_.secretsFile !== undefined) {
|
|
664
573
|
lines.push(sourceSecretsLine(arguments_.secretsFile));
|
|
665
574
|
}
|
|
@@ -3,11 +3,10 @@ import type { HostCapabilities } from "./host.ts";
|
|
|
3
3
|
/**
|
|
4
4
|
* Resolve `local.runner` from config + host capabilities into a concrete
|
|
5
5
|
* backend. `auto` defaults to safehouse on macOS and sdx on Linux — both
|
|
6
|
-
* are the deny-first paths for their platform. `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the dispatcher can test platform pivots without touching real hosts.
|
|
6
|
+
* are the deny-first paths for their platform. `none` and the explicit names
|
|
7
|
+
* pass through unchanged so users always get exactly what they asked for.
|
|
8
|
+
* Pure: takes everything it needs as arguments so the dispatcher can test
|
|
9
|
+
* platform pivots without touching real hosts.
|
|
11
10
|
*/
|
|
12
11
|
export declare function resolveLocalRunner(setting: LocalRunnerSetting, host: HostCapabilities): LocalRunner;
|
|
13
12
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localRunner.d.ts","sourceRoot":"","sources":["../../src/lib/localRunner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD
|
|
1
|
+
{"version":3,"file":"localRunner.d.ts","sourceRoot":"","sources":["../../src/lib/localRunner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,gBAAgB,GACrB,WAAW,CAQb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CA+B/F"}
|
package/dist/lib/localRunner.js
CHANGED
|
@@ -2,11 +2,10 @@ import { log, styleWarning } from "./util.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Resolve `local.runner` from config + host capabilities into a concrete
|
|
4
4
|
* backend. `auto` defaults to safehouse on macOS and sdx on Linux — both
|
|
5
|
-
* are the deny-first paths for their platform. `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the dispatcher can test platform pivots without touching real hosts.
|
|
5
|
+
* are the deny-first paths for their platform. `none` and the explicit names
|
|
6
|
+
* pass through unchanged so users always get exactly what they asked for.
|
|
7
|
+
* Pure: takes everything it needs as arguments so the dispatcher can test
|
|
8
|
+
* platform pivots without touching real hosts.
|
|
10
9
|
*/
|
|
11
10
|
export function resolveLocalRunner(setting, host) {
|
|
12
11
|
if (setting !== "auto") {
|
|
@@ -38,24 +37,6 @@ export function assertLocalRunnerRequirements(host, runner) {
|
|
|
38
37
|
}
|
|
39
38
|
return;
|
|
40
39
|
}
|
|
41
|
-
if (runner === "srt") {
|
|
42
|
-
if (!host.isSrtSupported) {
|
|
43
|
-
throw new Error("Local groundcrew runs with the srt runner require macOS or Linux/WSL. Set local.runner to 'auto' to pick the platform default.");
|
|
44
|
-
}
|
|
45
|
-
if (host.isLinux) {
|
|
46
|
-
const missing = [
|
|
47
|
-
host.hasBubblewrap ? undefined : "bubblewrap",
|
|
48
|
-
host.hasSocat ? undefined : "socat",
|
|
49
|
-
host.hasRipgrep ? undefined : "ripgrep (rg)",
|
|
50
|
-
].filter((name) => name !== undefined);
|
|
51
|
-
if (missing.length > 0) {
|
|
52
|
-
throw new Error(`Local groundcrew runs with the srt runner on Linux require ${missing.join(", ")} on PATH. ` +
|
|
53
|
-
"Install the missing dependencies (Debian/Ubuntu: `apt install bubblewrap socat ripgrep`). " +
|
|
54
|
-
"On Ubuntu 24.04+ you may also need `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0` to allow unprivileged user namespaces.");
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
40
|
if (runner === "sdx") {
|
|
60
41
|
if (!host.isSdxSupported) {
|
|
61
42
|
throw new Error("Local groundcrew runs with the sdx runner require macOS or Linux.");
|
package/docs/configuration.md
CHANGED
|
@@ -314,7 +314,7 @@ and hook contract.
|
|
|
314
314
|
| Key | Default | What it does |
|
|
315
315
|
| ----------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
316
316
|
| `sources` | `[]` | Additional pluggable task sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. Declare `{ kind: "linear", team: "ENG", statuses: { ... } }` to configure Linear task creation's default team and/or override Linear status names used for `in-progress` / `in-review` disambiguation. Disable the implicit Linear source with `{ kind: "linear", enabled: false }` (no API key required) — useful for shell-only setups. |
|
|
317
|
-
| `sources[].sandboxWritePaths` | optional | Shell sources only. Local directories that the `safehouse`
|
|
317
|
+
| `sources[].sandboxWritePaths` | optional | Shell sources only. Local directories that the `safehouse` sandbox runner opens for read and write while an agent works a task from that source. Use this when a shell-backed task store lives outside the worktree, such as `~/plans`. Paths may be absolute, relative to the agent working directory, or `~`-prefixed. Not supported by the `sdx` runner. |
|
|
318
318
|
| `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
|
|
319
319
|
| `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
|
|
320
320
|
| `git.branchPrefix` | OS username | Prefix groundcrew puts before the task id when naming a worktree branch (`<branchPrefix>-<task>`). Must be a slash-free slug of letters, digits, `.`, `_`, or `-`. Defaults to the OS account username. Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. Prefer a per-user config for personal prefixes — a committed `git.branchPrefix` gives every contributor the same branch prefix. |
|
|
@@ -340,6 +340,6 @@ and hook contract.
|
|
|
340
340
|
| `prompts.promptFile` | optional | Path to a UTF-8 file whose contents become `prompts.initial`, read at load time. Resolved relative to the config file's directory; `~` is expanded and absolute paths are used as-is. The JSON-friendly alternative to inlining a large prompt or `readFileSync`. Mutually exclusive with `prompts.initial`. |
|
|
341
341
|
| `workspaceKind` | `"auto"` | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"`, `"tmux"`, or `"zellij"` to fail loudly when the chosen backend is missing. |
|
|
342
342
|
| `local.runner` | `"auto"` | Local isolation backend. `"auto"` uses `safehouse` on macOS and `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly. |
|
|
343
|
-
| `local.networkEgress` | `"allowlisted"` | Network egress posture. With `local.runner: "safehouse"`, `"allowlisted"` wraps with Clearance and `"open"` keeps the Safehouse filesystem sandbox while opening network egress. Ignored by `
|
|
344
|
-
| `local.readOnlyDirs` | `~/.config/tfenv` | Host directories re-opened read-only inside the sandbox
|
|
343
|
+
| `local.networkEgress` | `"allowlisted"` | Network egress posture. With `local.runner: "safehouse"`, `"allowlisted"` wraps with Clearance and `"open"` keeps the Safehouse filesystem sandbox while opening network egress. Ignored by `sdx` and `none`. |
|
|
344
|
+
| `local.readOnlyDirs` | `~/.config/tfenv` | Host directories re-opened read-only inside the Safehouse sandbox, for toolchains the profile masks but does not re-open. `~` is expanded. Defaults to tfenv's config root so `terraform`/`tfenv` work in the sandbox; set your own list to add or replace entries. Ignored by `sdx` and `none`. |
|
|
345
345
|
| `logging.file` | XDG state path | Append-mode log file. `log()` / `logEvent()` tee here in addition to stdout. Defaults to `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log`. |
|
package/docs/runners.md
CHANGED
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
`local.runner` picks the local isolation backend. `auto` resolves per platform.
|
|
4
4
|
|
|
5
|
-
| Runner | Default on | Backend
|
|
6
|
-
| ----------- | ----------- |
|
|
7
|
-
| `safehouse` | macOS | [Safehouse](https://agent-safehouse.dev/) — fastest local; cannot safely give the agent Docker.
|
|
8
|
-
| `
|
|
9
|
-
| `
|
|
10
|
-
| `none` | — | Unsandboxed escape hatch. Never picked implicitly; doctor warns when configured. |
|
|
11
|
-
|
|
12
|
-
`auto` never resolves to `srt` — opt in explicitly with `local.runner: "srt"`.
|
|
5
|
+
| Runner | Default on | Backend |
|
|
6
|
+
| ----------- | ----------- | -------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| `safehouse` | macOS | [Safehouse](https://agent-safehouse.dev/) — fastest local; cannot safely give the agent Docker. |
|
|
8
|
+
| `sdx` | Linux / WSL | [Docker Sandboxes](https://docs.docker.com/sandboxes/) (`sbx`) — required when the agent needs `docker`. |
|
|
9
|
+
| `none` | — | Unsandboxed escape hatch. Never picked implicitly; doctor warns when configured. |
|
|
13
10
|
|
|
14
11
|
## Safehouse Clearance Allowlist
|
|
15
12
|
|
|
@@ -52,54 +49,14 @@ This is for the case where the allowlist is more friction than the egress restri
|
|
|
52
49
|
Scope and limits:
|
|
53
50
|
|
|
54
51
|
- **safehouse only.** It applies to both groundcrew-composed Safehouse wraps (the `prepareWorktree` wrap and the agent wrap).
|
|
55
|
-
- **Ignored by `
|
|
52
|
+
- **Ignored by `sdx` / `none`.** The other runners ignore it, so you can leave `networkEgress` set while switching `local.runner`.
|
|
56
53
|
- **No additional effect when `cmd` already starts with `safehouse`:** that command owns its own wrap, so groundcrew injects nothing. Groundcrew-managed setup/resume launches still reject cmd-owned Safehouse wraps because worker self-completion env cannot be injected.
|
|
57
54
|
|
|
58
|
-
## srt (Anthropic sandbox-runtime)
|
|
59
|
-
|
|
60
|
-
`srt` is the fast, non-Docker option that works on **both macOS and Linux/WSL** — the gap `safehouse` (macOS-only) and `sdx` (Docker) leave. It replaces both Safehouse and Clearance in one tool: a sandbox engine (`sandbox-exec` on macOS, `bubblewrap` on Linux) plus a built-in proxy network allowlist. It is opt-in while it stabilizes:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
crew run --watch # with local.runner: "srt" in crew.config.ts
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
> **Beta.** `@anthropic-ai/sandbox-runtime` is an Anthropic research preview; its config format may evolve. groundcrew pins the version it ships with, so upgrades are deliberate.
|
|
67
|
-
|
|
68
|
-
Groundcrew generates a per-launch policy itself (Safehouse's `.sb` profiles have no equivalent here):
|
|
69
|
-
|
|
70
|
-
- **Reads**: the home region (`/Users` on macOS, `/home`+`/root`+`/mnt` on Linux — `/mnt` covers WSL's Windows drive mounts) is denied, then the worktree, the repo's git metadata, the language toolchains needed to run the agent, and the agent's own config dirs (`~/.claude`, `~/.codex`, …) are re-opened. On macOS the user keychain dir (`~/Library/Keychains`) is also re-opened read-only so keychain-authenticated agents (claude) can sign in under the home mask. The agent cannot read `~/.ssh`, `~/.aws`, shell history, or unrelated repos.
|
|
71
|
-
- **Writes**: allow-only, and the host-CLI persistence vector (planting hooks, `mcpServers`, `commands/`, `plugins/`, … that run on the user's next host invocation) is closed per agent. **claude** keeps a writable `~/.claude` (its Bash tool needs scratch/session state there) but every fixed-path executable/instruction surface — `~/.claude.json` (`mcpServers`), `settings.json` and its hooks, `commands/`, `agents/`, `plugins/`, `skills/`, `statusline.sh`, `CLAUDE.md`, the bundled `chrome` binary, `.git/{hooks,config}` — is denied; claude tolerates those write denials. **codex** hard-fails with a read-only home, so it is pointed at a per-launch relocated config dir (`CODEX_HOME`) seeded with its credentials, leaving the real `~/.codex` entirely unwritten. The git common dir is granted as a **narrow allowlist** of only what `status/diff/add/commit/push/gc` write (`objects`, `refs`, `logs`, `packed-refs`, this worktree's gitdir, …) — never wholesale, so the repo `config`/`hooks`, the per-worktree gitdir redirection files, and **sibling worktree gitdirs** stay unwritable. Global toolchain bins (`~/.cargo/bin`, global `node_modules`, the npx cache, …) are never writable either.
|
|
72
|
-
- **Environment**: each `srt` invocation runs under a sanitized env (`env -i` + a benign baseline). Unlike safehouse and sdx, the `srt` CLI inherits the host env, so without this an ambient `AWS_*`, `GITHUB_TOKEN`, etc. would reach the agent and bypass the read mask. Credentials the agent legitimately needs from the environment must be forwarded explicitly via the agent's `preLaunchEnv` (the same opt-in pass-list safehouse uses).
|
|
73
|
-
- **Network**: allow-only, **reused from the same Clearance allowlist** (`CLEARANCE_ALLOW_HOSTS` / `CLEARANCE_ALLOW_HOSTS_FILES`, including the shipped `clearance-allow-hosts`) so there is one source of truth. Local binding and unix sockets stay off (never the Docker socket).
|
|
74
|
-
- **Built-in credential profiles**: the per-agent read/write/deny profiles above ship for `claude`, `codex`, and `cursor-agent` (the `cursor` / `cursor-grok` presets). Unknown agents get no extra home access.
|
|
75
|
-
|
|
76
|
-
### Linux / WSL prerequisites
|
|
77
|
-
|
|
78
|
-
Install the srt runtime dependencies:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
# Debian / Ubuntu
|
|
82
|
-
sudo apt install bubblewrap socat ripgrep
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
On **Ubuntu 24.04+**, unprivileged user namespaces are restricted by AppArmor; enable them once:
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
`crew doctor` checks these and prints what is missing. macOS needs no extra dependencies — the `srt` CLI ships with groundcrew.
|
|
92
|
-
|
|
93
|
-
### Debugging sandbox denials
|
|
94
|
-
|
|
95
|
-
- **macOS**: `log stream --predicate 'process == "sandbox-exec"' --style syslog`
|
|
96
|
-
- **Linux**: bubblewrap has no built-in violation log; trace the failing command with `strace -f -e trace=file <cmd>` to find the blocked path, then widen the policy.
|
|
97
|
-
|
|
98
55
|
## Docker Sandboxes Setup
|
|
99
56
|
|
|
100
57
|
`sdx` does not support `unsandboxedHooks`. The sdx container has no
|
|
101
58
|
host to run it on; configuring it for an `sdx`-runner repo is a launch-time
|
|
102
|
-
config error. Use `safehouse
|
|
59
|
+
config error. Use `safehouse` or `none` if you need host-side setup.
|
|
103
60
|
|
|
104
61
|
Each agent that runs under `sdx` needs a `sandbox: { agent: "<sbx-agent>" }` block in `crew.config.ts`. Groundcrew addresses the sandbox as `groundcrew-<agent>` and reuses one existing sandbox per agent across repos and tasks.
|
|
105
62
|
|
package/docs/setup-hooks.md
CHANGED
|
@@ -127,7 +127,7 @@ Grant it only to repositories you trust to run arbitrary code on the host.
|
|
|
127
127
|
(top-level or under `hooks`) is a hard config error.
|
|
128
128
|
- **Per-repository only.** There is no `defaults.unsandboxedHooks`;
|
|
129
129
|
host execution is never a fleet-wide default.
|
|
130
|
-
- **Runner support.** Runs on the host for `safehouse
|
|
130
|
+
- **Runner support.** Runs on the host for `safehouse` and `none`. The
|
|
131
131
|
`sdx` runner rejects it at launch — a container has no host to run it on.
|
|
132
132
|
- **Credentials.** Build secrets are available so `npm`/`bundle` can
|
|
133
133
|
authenticate; the agent's `preLaunchEnv` names are scrubbed so the command
|
|
@@ -58,7 +58,7 @@ With no working implementation to validate against (green-on-v1 dropped), the ha
|
|
|
58
58
|
### 1.6 Lanes
|
|
59
59
|
|
|
60
60
|
- **Core lane**: hermetic, no sandbox runner, runs anywhere including CI.
|
|
61
|
-
- **Sandbox lane**: real
|
|
61
|
+
- **Sandbox lane**: real platform runner, platform-gated; asserts denial behavior that can't be faked honestly. v2-only by definition — v1's posture keeps its existing unit-level coverage.
|
|
62
62
|
|
|
63
63
|
### 1.7 Suite packaging
|
|
64
64
|
|