@ashlr/hub 2.2.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +95 -8
- package/dist/api/plugin.d.ts +1 -0
- package/dist/cli/backlog.js +2 -0
- package/dist/cli/backlog.js.map +1 -1
- package/dist/cli/digest.d.ts +6 -0
- package/dist/cli/digest.js +127 -2
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/eval-fixtures.d.ts +18 -0
- package/dist/cli/eval-fixtures.js +38 -0
- package/dist/cli/eval-fixtures.js.map +1 -0
- package/dist/cli/eval.d.ts +56 -0
- package/dist/cli/eval.js +283 -0
- package/dist/cli/eval.js.map +1 -0
- package/dist/cli/fleet.d.ts +27 -0
- package/dist/cli/fleet.js +222 -0
- package/dist/cli/fleet.js.map +1 -0
- package/dist/cli/goal.d.ts +15 -0
- package/dist/cli/goal.js +91 -0
- package/dist/cli/goal.js.map +1 -0
- package/dist/cli/help.js +10 -3
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/inbox.d.ts +6 -0
- package/dist/cli/inbox.js +183 -8
- package/dist/cli/inbox.js.map +1 -1
- package/dist/cli/index.js +50 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/loop.d.ts +16 -0
- package/dist/cli/loop.js +66 -0
- package/dist/cli/loop.js.map +1 -0
- package/dist/cli/mcp.d.ts +12 -0
- package/dist/cli/mcp.js +148 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/onboard.d.ts +16 -0
- package/dist/cli/onboard.js +71 -0
- package/dist/cli/onboard.js.map +1 -1
- package/dist/cli/plugins.js +9 -8
- package/dist/cli/plugins.js.map +1 -1
- package/dist/cli/pulse.d.ts +10 -0
- package/dist/cli/pulse.js +261 -0
- package/dist/cli/pulse.js.map +1 -1
- package/dist/cli/run.js +21 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/stack.d.ts +34 -0
- package/dist/cli/stack.js +221 -0
- package/dist/cli/stack.js.map +1 -0
- package/dist/cli/update.js +12 -5
- package/dist/cli/update.js.map +1 -1
- package/dist/core/daemon/loop.d.ts +10 -5
- package/dist/core/daemon/loop.js +167 -37
- package/dist/core/daemon/loop.js.map +1 -1
- package/dist/core/env-bridge.js +3 -2
- package/dist/core/env-bridge.js.map +1 -1
- package/dist/core/fleet/automerge-pass.d.ts +33 -0
- package/dist/core/fleet/automerge-pass.js +61 -0
- package/dist/core/fleet/automerge-pass.js.map +1 -0
- package/dist/core/fleet/quota.d.ts +70 -0
- package/dist/core/fleet/quota.js +198 -0
- package/dist/core/fleet/quota.js.map +1 -0
- package/dist/core/fleet/router.d.ts +46 -0
- package/dist/core/fleet/router.js +121 -0
- package/dist/core/fleet/router.js.map +1 -0
- package/dist/core/fleet/self.d.ts +58 -0
- package/dist/core/fleet/self.js +187 -0
- package/dist/core/fleet/self.js.map +1 -0
- package/dist/core/fleet/status.d.ts +54 -0
- package/dist/core/fleet/status.js +157 -0
- package/dist/core/fleet/status.js.map +1 -0
- package/dist/core/foundry/provenance.d.ts +78 -0
- package/dist/core/foundry/provenance.js +172 -0
- package/dist/core/foundry/provenance.js.map +1 -0
- package/dist/core/git.d.ts +13 -0
- package/dist/core/git.js +26 -0
- package/dist/core/git.js.map +1 -1
- package/dist/core/inbox/merge.d.ts +151 -0
- package/dist/core/inbox/merge.js +858 -0
- package/dist/core/inbox/merge.js.map +1 -0
- package/dist/core/inbox/store.js +10 -1
- package/dist/core/inbox/store.js.map +1 -1
- package/dist/core/integrations/markdown.d.ts +66 -0
- package/dist/core/integrations/markdown.js +132 -0
- package/dist/core/integrations/markdown.js.map +1 -0
- package/dist/core/integrations/secrets.d.ts +37 -0
- package/dist/core/integrations/secrets.js +67 -0
- package/dist/core/integrations/secrets.js.map +1 -0
- package/dist/core/integrations/stack.d.ts +47 -0
- package/dist/core/integrations/stack.js +107 -0
- package/dist/core/integrations/stack.js.map +1 -0
- package/dist/core/mcp-native-engineer.d.ts +123 -0
- package/dist/core/mcp-native-engineer.js +936 -0
- package/dist/core/mcp-native-engineer.js.map +1 -0
- package/dist/core/mcp-native.js +20 -42
- package/dist/core/mcp-native.js.map +1 -1
- package/dist/core/mcp-registry.js +2 -0
- package/dist/core/mcp-registry.js.map +1 -1
- package/dist/core/observability/codex-source.d.ts +53 -0
- package/dist/core/observability/codex-source.js +390 -0
- package/dist/core/observability/codex-source.js.map +1 -0
- package/dist/core/observability/estimate.d.ts +12 -0
- package/dist/core/observability/estimate.js +26 -0
- package/dist/core/observability/estimate.js.map +1 -1
- package/dist/core/observability/limits.d.ts +52 -0
- package/dist/core/observability/limits.js +316 -0
- package/dist/core/observability/limits.js.map +1 -0
- package/dist/core/observability/usage-source.js +3 -1
- package/dist/core/observability/usage-source.js.map +1 -1
- package/dist/core/portfolio/scanners.d.ts +1 -0
- package/dist/core/portfolio/scanners.js +40 -1
- package/dist/core/portfolio/scanners.js.map +1 -1
- package/dist/core/providers.js +4 -0
- package/dist/core/providers.js.map +1 -1
- package/dist/core/quality/health.js +1 -0
- package/dist/core/quality/health.js.map +1 -1
- package/dist/core/run/agent-loop.d.ts +8 -0
- package/dist/core/run/agent-loop.js +24 -14
- package/dist/core/run/agent-loop.js.map +1 -1
- package/dist/core/run/engine-registry.d.ts +48 -0
- package/dist/core/run/engine-registry.js +190 -0
- package/dist/core/run/engine-registry.js.map +1 -0
- package/dist/core/run/engines.d.ts +17 -3
- package/dist/core/run/engines.js +63 -44
- package/dist/core/run/engines.js.map +1 -1
- package/dist/core/run/learned-router.d.ts +121 -0
- package/dist/core/run/learned-router.js +374 -0
- package/dist/core/run/learned-router.js.map +1 -0
- package/dist/core/run/model-manager.js +3 -3
- package/dist/core/run/model-manager.js.map +1 -1
- package/dist/core/run/model-profile.d.ts +67 -0
- package/dist/core/run/model-profile.js +132 -0
- package/dist/core/run/model-profile.js.map +1 -0
- package/dist/core/run/orchestrator.d.ts +1 -1
- package/dist/core/run/orchestrator.js +642 -510
- package/dist/core/run/orchestrator.js.map +1 -1
- package/dist/core/run/prompts/budget.d.ts +25 -0
- package/dist/core/run/prompts/budget.js +51 -0
- package/dist/core/run/prompts/budget.js.map +1 -0
- package/dist/core/run/prompts/index.d.ts +14 -0
- package/dist/core/run/prompts/index.js +59 -0
- package/dist/core/run/prompts/index.js.map +1 -0
- package/dist/core/run/prompts/layers.d.ts +26 -0
- package/dist/core/run/prompts/layers.js +73 -0
- package/dist/core/run/prompts/layers.js.map +1 -0
- package/dist/core/run/prompts/roles.d.ts +23 -0
- package/dist/core/run/prompts/roles.js +48 -0
- package/dist/core/run/prompts/roles.js.map +1 -0
- package/dist/core/run/prompts/types.d.ts +38 -0
- package/dist/core/run/prompts/types.js +5 -0
- package/dist/core/run/prompts/types.js.map +1 -0
- package/dist/core/run/provider-client.d.ts +6 -24
- package/dist/core/run/provider-client.js +125 -61
- package/dist/core/run/provider-client.js.map +1 -1
- package/dist/core/run/router.js +8 -0
- package/dist/core/run/router.js.map +1 -1
- package/dist/core/run/sandboxed-engine.d.ts +66 -0
- package/dist/core/run/sandboxed-engine.js +262 -0
- package/dist/core/run/sandboxed-engine.js.map +1 -0
- package/dist/core/run/verify-commands.d.ts +83 -0
- package/dist/core/run/verify-commands.js +234 -0
- package/dist/core/run/verify-commands.js.map +1 -0
- package/dist/core/run/verify.d.ts +31 -1
- package/dist/core/run/verify.js +48 -0
- package/dist/core/run/verify.js.map +1 -1
- package/dist/core/sandbox/audit.d.ts +27 -0
- package/dist/core/sandbox/audit.js +34 -0
- package/dist/core/sandbox/audit.js.map +1 -1
- package/dist/core/sandbox/confine.d.ts +134 -0
- package/dist/core/sandbox/confine.js +370 -0
- package/dist/core/sandbox/confine.js.map +1 -0
- package/dist/core/types.d.ts +227 -5
- package/dist/core/web/api.js +77 -28
- package/dist/core/web/api.js.map +1 -1
- package/dist/core/web/control.d.ts +116 -0
- package/dist/core/web/control.js +307 -0
- package/dist/core/web/control.js.map +1 -0
- package/dist/core/web/public/app.js +512 -6
- package/dist/core/web/public/styles.css +468 -0
- package/package.json +3 -2
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* confine.ts — M52: OS-level confinement (closes v4 read-residual).
|
|
3
|
+
*
|
|
4
|
+
* Wraps the contained engine spawn with a platform-native read-jail +
|
|
5
|
+
* network-egress gate. ENTIRELY OPT-IN — absent cfg.foundry.confinement the
|
|
6
|
+
* launcher is null and spawnEngine behaves byte-identically to v4.
|
|
7
|
+
*
|
|
8
|
+
* SUPPORTED PLATFORMS:
|
|
9
|
+
* macOS (darwin) — sandbox-exec(1) with a custom SBPL profile.
|
|
10
|
+
* Linux — bwrap (bubblewrap) when on PATH; else null.
|
|
11
|
+
* Other — null (env-only v4 behavior).
|
|
12
|
+
*
|
|
13
|
+
* THREAT MODEL (macOS sandbox-exec):
|
|
14
|
+
* Goal: prevent the contained CLI from reading arbitrary paths outside its
|
|
15
|
+
* worktree — specifically other source trees (e.g. sibling repos) and
|
|
16
|
+
* secrets dirs (e.g. ~/.ssh, ~/.gnupg, ~/.aws).
|
|
17
|
+
*
|
|
18
|
+
* Strategy: (allow default) keeps the process functional (dynamic linker,
|
|
19
|
+
* frameworks, sockets for IPC, etc.). We then layer targeted denials:
|
|
20
|
+
*
|
|
21
|
+
* 1. (deny file-read*) — blocks all file reads by default.
|
|
22
|
+
* 2. (allow file-read* (subpath "<worktree>")) — re-allow the worktree.
|
|
23
|
+
* 3. (allow file-read* (subpath "<$HOME/.claude>")) etc. — re-allow each
|
|
24
|
+
* vendor config home the agent needs for its own auth (read-only).
|
|
25
|
+
* 4. (allow file-read* (subpath "<readAllowed[i]>")) — caller extras.
|
|
26
|
+
* 5. (allow file-read* (subpath "/usr") (subpath "/lib") ...)
|
|
27
|
+
* — allow system paths needed for the OS and CLIs to function.
|
|
28
|
+
* 6. (deny network-outbound*) unless networkEgress:true.
|
|
29
|
+
* 7. (allow file-write* (subpath "<worktree>") (subpath "<TMPDIR>")
|
|
30
|
+
* (subpath "<home>/.claude") ...)
|
|
31
|
+
* — write inside worktree + tmp + vendor config dirs (HOME_CONFIG_SUBDIRS
|
|
32
|
+
* + VENDOR_HOME_ENVS values) so confined agents can write session state.
|
|
33
|
+
*
|
|
34
|
+
* What this DOES NOT protect:
|
|
35
|
+
* - Reads of /proc, /dev, and similar pseudo-filesystems that have no
|
|
36
|
+
* file-read* equivalent in SBPL. These are generally safe (no user data).
|
|
37
|
+
* - Writes to paths outside worktree/TMPDIR that are NOT covered by the
|
|
38
|
+
* allow-file-write* list. Those writes ARE blocked (allow default does
|
|
39
|
+
* not include file-write*).
|
|
40
|
+
* - Against a process that can exploit the macOS sandbox itself. This is
|
|
41
|
+
* an OS-level trust boundary, not a VM boundary.
|
|
42
|
+
*
|
|
43
|
+
* IMPORTANT LIMITATION: sandbox-exec's SBPL `(deny file-read*)` interacts
|
|
44
|
+
* with macOS's TCC/SIP stack. On some system paths (Frameworks, dylibs) the
|
|
45
|
+
* deny fires before dyld can load, causing the wrapped binary to crash. The
|
|
46
|
+
* system subpath allow-list below is empirically derived to keep `cat`,
|
|
47
|
+
* `sh`, and major CLIs (claude, codex) functional. For production hardening,
|
|
48
|
+
* run the profile under `sandbox-exec -D/tmp/sbpl.profile.tmp` and check
|
|
49
|
+
* sandbox-violation lines in the system log.
|
|
50
|
+
*
|
|
51
|
+
* INJECTION SAFETY:
|
|
52
|
+
* All caller-supplied paths are escaped via escapeSbplPath() before
|
|
53
|
+
* embedding in the SBPL profile string. The escaper replaces `"` and `\`
|
|
54
|
+
* with their escaped forms so a malicious path cannot break out of the
|
|
55
|
+
* (subpath "...") SBPL string literal.
|
|
56
|
+
*/
|
|
57
|
+
import { execFileSync } from 'node:child_process';
|
|
58
|
+
import { tmpdir } from 'node:os';
|
|
59
|
+
import { realpathSync, existsSync } from 'node:fs';
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a path to its canonical realpath. macOS `sandbox-exec` resolves
|
|
62
|
+
* symlinks before matching (subpath ...) clauses, so e.g. a worktree under
|
|
63
|
+
* `/var/folders/...` (a symlink to `/private/var/folders/...`) must be emitted
|
|
64
|
+
* as its resolved target or the re-allow clause never matches and the agent is
|
|
65
|
+
* denied its own workspace. Falls back to the input when the path does not yet
|
|
66
|
+
* exist or cannot be resolved.
|
|
67
|
+
*/
|
|
68
|
+
function resolveReal(p) {
|
|
69
|
+
try {
|
|
70
|
+
return existsSync(p) ? realpathSync(p) : p;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return p;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
import { audit } from './audit.js';
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// SBPL path escaping — injection safety
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
/**
|
|
81
|
+
* Escape a path for safe embedding in a SBPL (sandbox profile language) string
|
|
82
|
+
* literal. SBPL string literals are delimited by `"`. Only `"` and `\` need
|
|
83
|
+
* escaping — forward slashes and parens in paths are literal and valid.
|
|
84
|
+
*
|
|
85
|
+
* A path containing `)` is safe because it appears inside `"..."` — the
|
|
86
|
+
* parser reads the closing `"` to end the literal before looking for `)`.
|
|
87
|
+
*
|
|
88
|
+
* PROOF: sandbox-exec's parser (Scheme-like) reads `(subpath "<escaped>")`.
|
|
89
|
+
* The literal ends at the first unescaped `"`. After escaping, `"` → `\"` and
|
|
90
|
+
* `\` → `\\`, so no injected character can terminate the string early.
|
|
91
|
+
*/
|
|
92
|
+
export function escapeSbplPath(p) {
|
|
93
|
+
return p.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
94
|
+
}
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// macOS SBPL profile builder
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
/**
|
|
99
|
+
* System paths the agent binary needs to read on macOS. Empirically derived
|
|
100
|
+
* to keep sandbox-exec-wrapped CLIs functional (dyld, shells, frameworks).
|
|
101
|
+
*/
|
|
102
|
+
// (M52 revision) The macOS profile no longer enumerates system read paths: an
|
|
103
|
+
// allow-list of dyld/framework paths is brittle across macOS versions and aborts
|
|
104
|
+
// the agent process. The profile instead starts from (allow default) and denies
|
|
105
|
+
// reads under $HOME — see buildMacosSbplProfile's threat-model note.
|
|
106
|
+
/**
|
|
107
|
+
* Vendor config-home env vars whose values (absolute paths) the agent CLI
|
|
108
|
+
* may need to read for its own auth/subscription. We allow reading these
|
|
109
|
+
* paths but NOT writing to them (except worktree + TMPDIR).
|
|
110
|
+
*/
|
|
111
|
+
const VENDOR_HOME_ENVS = [
|
|
112
|
+
'CODEX_HOME',
|
|
113
|
+
'CLAUDE_CONFIG_DIR',
|
|
114
|
+
'XDG_CONFIG_HOME',
|
|
115
|
+
'XDG_DATA_HOME',
|
|
116
|
+
];
|
|
117
|
+
/**
|
|
118
|
+
* Subdirectories under HOME that are standard vendor config homes when the
|
|
119
|
+
* dedicated env vars are absent. We allow these so agent CLIs can read their
|
|
120
|
+
* own on-disk auth without needing an explicit env var.
|
|
121
|
+
*/
|
|
122
|
+
const HOME_CONFIG_SUBDIRS = [
|
|
123
|
+
'.claude',
|
|
124
|
+
'.config',
|
|
125
|
+
'.local/share',
|
|
126
|
+
'.hermes',
|
|
127
|
+
'.opencode',
|
|
128
|
+
'.codex',
|
|
129
|
+
'.npm', // node_modules cache needed by some CLIs
|
|
130
|
+
'.node_repl_history',
|
|
131
|
+
];
|
|
132
|
+
/**
|
|
133
|
+
* Build a macOS SBPL profile string for sandbox-exec.
|
|
134
|
+
*
|
|
135
|
+
* The profile:
|
|
136
|
+
* (version 1)
|
|
137
|
+
* (allow default) — keep process functional
|
|
138
|
+
* (deny file-read*) — block all file reads by default
|
|
139
|
+
* (allow file-read* ...) — re-allow: worktree, vendor homes, system
|
|
140
|
+
* (deny network-outbound*) — block egress (omitted when networkEgress)
|
|
141
|
+
* (allow file-write* ...) — write only under worktree + TMPDIR
|
|
142
|
+
*/
|
|
143
|
+
export function buildMacosSbplProfile(profile, ctx) {
|
|
144
|
+
const env = ctx.env ?? process.env;
|
|
145
|
+
const home = ctx.home ?? env.HOME ?? env.USERPROFILE ?? '';
|
|
146
|
+
const tmp = env.TMPDIR ?? tmpdir();
|
|
147
|
+
const sub = (p) => `(subpath "${escapeSbplPath(resolveReal(p))}")`;
|
|
148
|
+
// Subtrees the agent may READ even under HOME: its worktree, its vendor config
|
|
149
|
+
// homes (subscription auth/session), and any caller-supplied extras.
|
|
150
|
+
const reallowRead = [ctx.worktree];
|
|
151
|
+
for (const key of VENDOR_HOME_ENVS) {
|
|
152
|
+
const val = env[key];
|
|
153
|
+
if (val)
|
|
154
|
+
reallowRead.push(val);
|
|
155
|
+
}
|
|
156
|
+
if (home) {
|
|
157
|
+
for (const s of HOME_CONFIG_SUBDIRS)
|
|
158
|
+
reallowRead.push(`${home}/${s}`);
|
|
159
|
+
}
|
|
160
|
+
if (profile.readAllowed) {
|
|
161
|
+
for (const p of profile.readAllowed)
|
|
162
|
+
if (p)
|
|
163
|
+
reallowRead.push(p);
|
|
164
|
+
}
|
|
165
|
+
// Subtrees the agent may WRITE: its worktree, vendor config homes (session
|
|
166
|
+
// state, logs, auth), and tmp. HOME_CONFIG_SUBDIRS are included so that when
|
|
167
|
+
// CLAUDE_CONFIG_DIR/CODEX_HOME/etc. are NOT set in the env (the common case —
|
|
168
|
+
// e.g. claude writes session state to ~/.claude by default), the confined
|
|
169
|
+
// agent can still write its own config dirs. The VENDOR_HOME_ENVS values cover
|
|
170
|
+
// the explicit-env-var override case. Other source trees and secrets dirs that
|
|
171
|
+
// are NOT in HOME_CONFIG_SUBDIRS or VENDOR_HOME_ENVS remain WRITE-denied
|
|
172
|
+
// (as they are READ-denied); the confinement residual is unchanged.
|
|
173
|
+
const reallowWrite = [ctx.worktree, tmp, '/tmp', '/private/tmp'];
|
|
174
|
+
for (const key of VENDOR_HOME_ENVS) {
|
|
175
|
+
const val = env[key];
|
|
176
|
+
if (val)
|
|
177
|
+
reallowWrite.push(val);
|
|
178
|
+
}
|
|
179
|
+
if (home) {
|
|
180
|
+
for (const s of HOME_CONFIG_SUBDIRS)
|
|
181
|
+
reallowWrite.push(`${home}/${s}`);
|
|
182
|
+
}
|
|
183
|
+
const reallowReadClauses = reallowRead.map(sub).join('\n ');
|
|
184
|
+
const reallowWriteClauses = reallowWrite.map(sub).join('\n ');
|
|
185
|
+
const networkClause = profile.networkEgress
|
|
186
|
+
? '(comment "network egress allowed by profile")'
|
|
187
|
+
: '(deny network*)';
|
|
188
|
+
// THREAT MODEL — the documented v4 residual is that a contained CLI can READ
|
|
189
|
+
// arbitrary paths outside its worktree, chiefly the user's OTHER source trees
|
|
190
|
+
// and secrets, which all live under $HOME (e.g. ~/Desktop/github, ~/.ssh,
|
|
191
|
+
// ~/.aws). We start from (allow default) so system libraries load and the agent
|
|
192
|
+
// CLI can actually run — an exhaustive allow-list of dyld/system paths is
|
|
193
|
+
// brittle across macOS versions and aborts the process (SIGABRT) — then DENY
|
|
194
|
+
// all reads/writes under $HOME and re-allow ONLY the worktree, the agent's own
|
|
195
|
+
// vendor config homes (HOME_CONFIG_SUBDIRS + VENDOR_HOME_ENVS), and caller
|
|
196
|
+
// extras. Vendor config dirs are BOTH read- and write-allowed so an agent can
|
|
197
|
+
// write session state/logs even when CLAUDE_CONFIG_DIR/CODEX_HOME are unset.
|
|
198
|
+
// Network egress is denied unless opted in.
|
|
199
|
+
// Residual: non-$HOME system paths stay readable (acceptable — no user
|
|
200
|
+
// repos/secrets live there); other $HOME paths (source trees, ~/.ssh, ~/.aws,
|
|
201
|
+
// etc.) remain denied for BOTH read and write; full VM isolation is future work.
|
|
202
|
+
const lines = [
|
|
203
|
+
'(version 1)',
|
|
204
|
+
'',
|
|
205
|
+
'; Allow default (process exec, dyld, IPC, mach) so the agent CLI can run.',
|
|
206
|
+
'(allow default)',
|
|
207
|
+
'',
|
|
208
|
+
'; Network egress gate.',
|
|
209
|
+
networkClause,
|
|
210
|
+
];
|
|
211
|
+
if (home) {
|
|
212
|
+
lines.push('', '; Deny reading the user HOME (other source trees + secrets) ...', `(deny file-read* ${sub(home)})`, '; ... but re-allow the worktree, vendor config homes, and caller extras.', '(allow file-read*', ` ${reallowReadClauses}`, ')', '', '; Restrict writes under HOME to the worktree + vendor homes (+ tmp).', `(deny file-write* ${sub(home)})`, '(allow file-write*', ` ${reallowWriteClauses}`, ')');
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
// No resolvable HOME: the HOME read-jail is inexpressible; degrade to the
|
|
216
|
+
// network gate only — still never weaker than v4's env-only containment.
|
|
217
|
+
lines.push('', '; No resolvable HOME — read-jail not expressible; network gate only.');
|
|
218
|
+
}
|
|
219
|
+
return lines.join('\n');
|
|
220
|
+
}
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Linux bwrap launcher builder
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
/** Check whether a binary is on PATH. Returns the binary name or null. */
|
|
225
|
+
function findBinary(name) {
|
|
226
|
+
try {
|
|
227
|
+
execFileSync('which', [name], { stdio: 'ignore', timeout: 3_000 });
|
|
228
|
+
return name;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Build a Linux bwrap (bubblewrap) launcher for the contained engine.
|
|
236
|
+
*
|
|
237
|
+
* Strategy: bind the root filesystem read-only, bind the worktree read-write,
|
|
238
|
+
* and optionally unshare the network namespace.
|
|
239
|
+
*/
|
|
240
|
+
function buildBwrapLauncher(profile, ctx) {
|
|
241
|
+
const bwrap = findBinary('bwrap');
|
|
242
|
+
if (!bwrap)
|
|
243
|
+
return null;
|
|
244
|
+
const env = ctx.env ?? process.env;
|
|
245
|
+
const tmp = env.TMPDIR ?? tmpdir();
|
|
246
|
+
const args = [
|
|
247
|
+
// Bind root read-only (gives access to system paths).
|
|
248
|
+
'--ro-bind', '/', '/',
|
|
249
|
+
// Bind worktree read-write (agent's workspace).
|
|
250
|
+
'--bind', ctx.worktree, ctx.worktree,
|
|
251
|
+
// Bind TMPDIR read-write.
|
|
252
|
+
'--bind', tmp, tmp,
|
|
253
|
+
// New temp filesystem.
|
|
254
|
+
'--tmpfs', '/tmp',
|
|
255
|
+
// Proc/dev for process functionality.
|
|
256
|
+
'--proc', '/proc',
|
|
257
|
+
'--dev', '/dev',
|
|
258
|
+
// Die with parent.
|
|
259
|
+
'--die-with-parent',
|
|
260
|
+
];
|
|
261
|
+
if (!profile.networkEgress) {
|
|
262
|
+
args.push('--unshare-net');
|
|
263
|
+
}
|
|
264
|
+
// Extra read-allowed paths: bind them read-only.
|
|
265
|
+
if (profile.readAllowed) {
|
|
266
|
+
for (const p of profile.readAllowed) {
|
|
267
|
+
if (p) {
|
|
268
|
+
args.push('--ro-bind', p, p);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Separator: everything after '--' is the command to run.
|
|
273
|
+
args.push('--');
|
|
274
|
+
return { bin: bwrap, prefixArgs: args };
|
|
275
|
+
}
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Primary public API
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
/**
|
|
280
|
+
* Build the OS sandbox launcher for the given confinement profile and context.
|
|
281
|
+
*
|
|
282
|
+
* Returns `{ bin, prefixArgs }` that callers prepend to the engine spawn, or
|
|
283
|
+
* `null` when confinement is off or not supported on this platform.
|
|
284
|
+
*
|
|
285
|
+
* This function is PURE on the happy path (macOS profile string is built from
|
|
286
|
+
* inputs only). The only side-effect is the bwrap PATH probe on Linux and the
|
|
287
|
+
* audit call on fallback/unsupported.
|
|
288
|
+
*
|
|
289
|
+
* Invariant: when `profile.mode === 'off'` (or undefined), always returns null
|
|
290
|
+
* with no side effects — byte-identical v4 behavior.
|
|
291
|
+
*/
|
|
292
|
+
export function buildSandboxLauncher(profile, ctx) {
|
|
293
|
+
// Flag-off: v4 env-only behavior.
|
|
294
|
+
if (!profile.mode || profile.mode === 'off') {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
const platform = process.platform;
|
|
298
|
+
const onUnsupported = profile.onUnsupported ?? 'fallback';
|
|
299
|
+
if (platform === 'darwin') {
|
|
300
|
+
const sbplProfile = buildMacosSbplProfile(profile, ctx);
|
|
301
|
+
return {
|
|
302
|
+
bin: 'sandbox-exec',
|
|
303
|
+
prefixArgs: ['-p', sbplProfile],
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (platform === 'linux') {
|
|
307
|
+
const launcher = buildBwrapLauncher(profile, ctx);
|
|
308
|
+
if (launcher)
|
|
309
|
+
return launcher;
|
|
310
|
+
// bwrap absent — fall through to unsupported handling.
|
|
311
|
+
}
|
|
312
|
+
// Platform unsupported or bwrap absent.
|
|
313
|
+
return _handleUnsupported(profile, ctx, onUnsupported, platform);
|
|
314
|
+
}
|
|
315
|
+
function _handleUnsupported(_profile, ctx, onUnsupported, platform) {
|
|
316
|
+
const reason = platform === 'linux'
|
|
317
|
+
? 'bwrap not found on PATH'
|
|
318
|
+
: `unsupported platform: ${platform}`;
|
|
319
|
+
if (onUnsupported === 'fail') {
|
|
320
|
+
throw new ConfinementUnsupportedError(`M52 confinement required but unavailable: ${reason}. ` +
|
|
321
|
+
`Set onUnsupported:'fallback' to allow env-only v4 behavior.`);
|
|
322
|
+
}
|
|
323
|
+
// 'fallback': audit the downgrade and return null (env-only v4).
|
|
324
|
+
audit({
|
|
325
|
+
action: 'confinement.fallback',
|
|
326
|
+
repo: ctx.worktree,
|
|
327
|
+
sandboxId: null,
|
|
328
|
+
summary: `OS confinement not available (${reason}); running with env-only containment (v4 behavior).`,
|
|
329
|
+
result: 'ok',
|
|
330
|
+
});
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
// confinementProfileFor — resolve effective profile from cfg
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
/**
|
|
337
|
+
* Resolve the effective ConfinementProfile for a specific engine from
|
|
338
|
+
* `cfg.foundry.confinement`. Per-engine key overrides the `*` (fleet-wide)
|
|
339
|
+
* default; absent → mode:'off' (v4 env-only, no side effects).
|
|
340
|
+
*
|
|
341
|
+
* A `*` key in cfg.foundry.confinement sets the fleet-wide default.
|
|
342
|
+
*/
|
|
343
|
+
export function confinementProfileFor(engine, cfg) {
|
|
344
|
+
const confinement = cfg.foundry?.confinement;
|
|
345
|
+
if (!confinement)
|
|
346
|
+
return { mode: 'off' };
|
|
347
|
+
// Per-engine key overrides the fleet-wide `*` default.
|
|
348
|
+
const perEngine = confinement[engine];
|
|
349
|
+
const fleetDefault = confinement['*'];
|
|
350
|
+
const merged = {
|
|
351
|
+
mode: 'off',
|
|
352
|
+
...fleetDefault,
|
|
353
|
+
...perEngine,
|
|
354
|
+
};
|
|
355
|
+
return merged;
|
|
356
|
+
}
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
// Error type
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
/**
|
|
361
|
+
* Terminal error thrown when OS confinement is required (mode:'os',
|
|
362
|
+
* onUnsupported:'fail') but the platform has no supported jail binary.
|
|
363
|
+
*/
|
|
364
|
+
export class ConfinementUnsupportedError extends Error {
|
|
365
|
+
constructor(message) {
|
|
366
|
+
super(message);
|
|
367
|
+
this.name = 'ConfinementUnsupportedError';
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
//# sourceMappingURL=confine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confine.js","sourceRoot":"","sources":["../../../src/core/sandbox/confine.ts"],"names":[],"mappings":"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"}
|