@erichll/pi-sandbox 0.15.2 → 0.16.0

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 CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.0 - 2026-09-05
4
+
5
+ - Depend on the coordinated `@erichll/pi-auto-review 0.16.0` release.
6
+ - Replace the obsolete `externalWorkerIsolation` integration with the required
7
+ `native-background-tools` protection mode for `pi-subagents 0.65.0`.
8
+ - Require a validated canonical native-agent whitelist, explicit asynchronous
9
+ launches, ambient child extensions, disabled nested delegation, and
10
+ `scheduledRuns.enabled=false`.
11
+ - Register the upstream capability ceiling with only `bash`, `read`, `grep`,
12
+ `find`, and `ls`; child writes therefore pass through sandboxed Bash while
13
+ `write`, `edit`, MCP, and extension tools are unavailable.
14
+ - Reject external runners, all workflow forms, schedules, resume, and agent or
15
+ workflow management mutations. Protected mode supports only explicit async
16
+ direct-agent launches because 0.65.0 workflow children disable ambient
17
+ extensions.
18
+ - Remove the obsolete external launcher, supervisor, network-policy transport,
19
+ and FleetView bridge. This mode is a tool boundary, not OS process isolation;
20
+ use the default `builtin` provider when whole-worker isolation is required.
21
+
22
+ ## 0.15.3 - 2026-09-03
23
+
24
+ - Reviewer decisions on sandbox network boundaries inherit the fenced-JSON
25
+ tolerance fix through the `@erichll/pi-auto-review` 0.15.3 dependency
26
+ (strict decision-schema validation unchanged).
27
+ - Move the tested `pi-subagents` baseline to 0.64.0.
28
+ - Verify watchdog launch blocking occurs before worker spawn and leaves no child
29
+ transcript in the model-backed gate.
30
+ - Exercise `watchdog_diff` against read-only Git worktree metadata through the
31
+ real outer-sandbox launcher.
32
+
3
33
  ## 0.15.2 - 2026-09-02
4
34
 
5
35
  - No user-facing changes. Compatibility-verification release only: the
package/README.md CHANGED
@@ -9,7 +9,7 @@ also run complete process-backed subagent trees inside independent sandboxes.
9
9
  - [Security model](#security-model)
10
10
  - [Linux requirements](#linux-requirements)
11
11
  - [Subagent provider](#subagent-provider)
12
- - [`pi-subagents` 0.63.0 capability boundary](#pi-subagents-0630-capability-boundary)
12
+ - [`pi-subagents` 0.65.0 native-background tool boundary](#pi-subagents-0650-native-background-tool-boundary)
13
13
  - [Network domain policy](#network-domain-policy)
14
14
  - [Optional host IPC fallback](#optional-host-ipc-fallback)
15
15
  - [Additional trusted read paths](#additional-trusted-read-paths)
@@ -81,8 +81,7 @@ new installs.
81
81
  ```json
82
82
  {
83
83
  "subagents": {
84
- "provider": "builtin",
85
- "externalWorkerIsolation": "off"
84
+ "provider": "builtin"
86
85
  }
87
86
  }
88
87
  ```
@@ -91,55 +90,57 @@ Supported modes:
91
90
 
92
91
  - `builtin` (default): register the process-backed `subagent` tool and sandbox
93
92
  each complete worker process tree.
94
- - `pi-subagents`: let the external extension own orchestration. Main-agent and
95
- inherited Bash execution remains sandboxed, but the external worker process
96
- itself is not wrapped while `externalWorkerIsolation` is `off`.
93
+ - `pi-subagents`: let `pi-subagents 0.65.0` own orchestration under the required
94
+ native-background tool boundary described below.
97
95
  - `off`: protect Bash only.
98
96
 
99
- ### `pi-subagents` 0.63.0 capability boundary
97
+ ### `pi-subagents` 0.65.0 native-background tool boundary
100
98
 
101
- `pi-sandbox` verifies the following combination in its test suite and release
102
- gate. This version boundary is pinned exactly (see the package
103
- `devDependencies`) and additionally exercised against the latest published
104
- `pi-subagents` on a nightly CI schedule. The `PI_SUBAGENT_PI_BINARY` seam
105
- (`externalWorkerIsolation: "enforce"`) is re-checked deterministically by the
106
- `gate:external-isolation` probe on the pinned and latest dependency. The
107
- published peer range remains `>=0.50.0`; this documents the current tested
108
- development baseline, not a runtime minimum.
99
+ This provider is intentionally locked to exactly `pi-subagents 0.65.0` and
100
+ fails closed if its public ceiling API or internal discovery layout drifts.
101
+ Configure both the protection mode and a non-empty canonical whitelist:
109
102
 
110
- | Capability | `builtin` | `pi-subagents` 0.63.0 |
103
+ ```json
104
+ {
105
+ "subagents": {
106
+ "provider": "pi-subagents",
107
+ "protection": "native-background-tools",
108
+ "allowedNativeAgents": ["worker", "reviewer", "scout"]
109
+ }
110
+ }
111
+ ```
112
+
113
+ The `pi-subagents` config at
114
+ `~/.pi/agent/extensions/subagent/config.json` must also contain
115
+ `"scheduledRuns": { "enabled": false }`. Every launch must explicitly set
116
+ `async: true`. Only direct single-agent launches are supported. In 0.65.0,
117
+ public `workflowScript` / `workflowScriptPath` children disable ambient
118
+ extensions, so both are rejected along with named workflows, schedules,
119
+ configuration mutations, resume, nested subagents, and external runners.
120
+
121
+ Every child is constrained by the upstream capability ceiling to `bash`,
122
+ `read`, `grep`, `find`, and `ls`. The child loads ambient extensions and must
123
+ acknowledge `@erichll:pi-sandbox`; writes can therefore occur only through the
124
+ sandboxed Bash tool. `write`, `edit`, MCP, and other extension tools are not
125
+ available.
126
+
127
+ | Capability | `builtin` | `pi-subagents` 0.65.0 protected mode |
111
128
  | --- | --- | --- |
112
- | Outer worker sandbox | Yes | Opt-in (`externalWorkerIsolation: "enforce"`) |
129
+ | Outer worker sandbox | Yes | No |
113
130
  | Bash sandbox | Yes | Yes |
114
131
  | Persistent follow-up | Yes | Yes |
115
- | `workflowScript`, missions, schedules | No | Yes |
116
- | Named workflow resources | No | Yes |
117
- | permission-system parent forwarding | Not required | Verified by compatibility gate |
132
+ | Public async `workflowScript` | No | No (ambient extensions disabled upstream) |
133
+ | Schedules / named workflow resources | No | No |
134
+ | Child tools | Configured parent tools | Fixed five-tool ceiling |
118
135
 
119
- Moving the tested baseline requires re-running `npm run gate:external-isolation`
120
- and, before a release, the model-backed `npm run gate:pi-subagents`. See
136
+ Before a release, run the deterministic and model-backed portions of
137
+ `npm run gate:pi-subagents`. See
121
138
  `docs/compat-notes.md` for the recorded compatibility seams and how each is
122
139
  verified on upgrade.
123
140
 
124
- The 0.63.0 baseline also exercises tasks passed as an `@task.md` argument and
125
- standard Git worktree metadata (`.git` pointer plus `commondir`) through the
126
- real outer-sandbox launcher. Worktrunk itself is not a dependency; provider
127
- selection remains upstream behavior.
128
-
129
- For the external provider, `off` leaves the worker process outside the outer
130
- Sandbox Runtime boundary. Do not treat inherited Bash sandboxing as complete
131
- worker isolation unless the trusted global configuration explicitly enables
132
- `enforce`.
133
-
134
- Note on the external provider's public execution surface: pi-subagents 0.43.0
135
- removed the old static multi-agent controls; `workflowScript` remains the
136
- public multi-agent orchestration surface. Current releases also accept a direct
137
- single-child `{ agent, task }` launch. The compatibility gate deliberately uses
138
- `return runs.run('main', { agent, task })` so the single-child probe continues
139
- to exercise the workflow executor and its isolation seam.
140
-
141
141
  The configuration parser rejects malformed JSON, unknown fields, and unknown
142
- providers instead of silently weakening isolation.
142
+ providers instead of silently weakening isolation. All legacy
143
+ `externalWorkerIsolation` configurations produce a migration error.
143
144
 
144
145
  ## Network domain policy
145
146
 
@@ -177,9 +178,8 @@ The precedence is deterministic:
177
178
 
178
179
  Both arrays default to empty. That preserves the prior behavior: no persistent
179
180
  network authorization, with every eligible public connection reviewed once.
180
- The same trusted arrays are copied into main-agent Bash, built-in subagents,
181
- and external `pi-subagents` workers when outer isolation is enforced. Missing
182
- or malformed external-worker policy transport hard-denies network access.
181
+ The same trusted arrays govern main-agent Bash, built-in subagents, and the
182
+ sandboxed Bash tool loaded by protected native `pi-subagents` children.
183
183
 
184
184
  Domain allowlists are not a complete data-loss-prevention boundary. A
185
185
  multi-tenant or user-uploadable destination such as `github.com` can itself be
@@ -188,28 +188,9 @@ Real package-manager traffic may require additional hosts—for example, PyPI
188
188
  downloads commonly use `files.pythonhosted.org`. `pi-sandbox` never infers or
189
189
  silently adds those domains.
190
190
 
191
- To opt in to outer worker isolation for the external provider, use:
192
-
193
- ```json
194
- {
195
- "subagents": {
196
- "provider": "pi-subagents",
197
- "externalWorkerIsolation": "enforce"
198
- }
199
- }
200
- ```
201
-
202
- `enforce` installs a session-scoped `PI_SUBAGENT_PI_BINARY` wrapper which
203
- starts the real Pi worker under a dedicated Sandbox Runtime broker. Bootstrap
204
- failure is terminal for that worker; it never falls back to host execution.
205
- The wrapper is preserved for nested child launches. This is opt-in while the
206
- permission-forwarding supervisor and managed-worktree gate continue to mature.
207
-
208
- Managed worktrees receive their own writable checkout plus the smallest known
209
- Git metadata paths as read-only access so `git status` works. The main
210
- repository `.git` is never made writable by this policy; Git mutation from an
211
- outer-sandboxed external worker is therefore intentionally unsupported for
212
- now.
191
+ Native background runner initialization itself is outside the OS sandbox.
192
+ Only the child's Bash modification channel is sandboxed. Select `builtin` when
193
+ the entire worker process tree must be contained by Sandbox Runtime.
213
194
 
214
195
  ## Optional host IPC fallback
215
196
 
@@ -295,17 +276,11 @@ npm test
295
276
  npm run gate:pi-subagents
296
277
  ```
297
278
 
298
- `gate:pi-subagents` is a release/manual integration check, not a default CI
299
- test. Set `PI_SUBAGENTS_GATE_MODEL` to a configured test model and provide its
300
- normal Pi credential environment. The command creates a dedicated temporary
301
- agent directory, never reads or updates your production Pi config, and prints
302
- `SKIP` when those prerequisites are absent.
303
-
304
- It allows 10 minutes for the direct-child phase and 15 minutes for the
305
- multi-stage workflow, and exits only after the parent reports returned child
306
- results. Override those bounds with positive millisecond values in
307
- `PI_SUBAGENTS_GATE_DIRECT_TIMEOUT_MS` and
308
- `PI_SUBAGENTS_GATE_WORKFLOW_TIMEOUT_MS` when needed for a dedicated provider.
279
+ `gate:pi-subagents` verifies the pinned package layout, discovery behavior,
280
+ capability ceiling, and protected launch policy in an isolated temporary agent
281
+ directory. It never reads or updates production Pi configuration. Model-backed
282
+ acceptance requires `PI_SUBAGENTS_GATE_MODEL` and an already-exported matching
283
+ credential; missing prerequisites are reported as `SKIP`.
309
284
 
310
285
  The test suite covers real Linux Sandbox Runtime enforcement when its native
311
286
  dependencies are installed, plus deterministic broker, network approval,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erichll/pi-sandbox",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "description": "Linux (bubblewrap) and macOS (Seatbelt) process sandbox for Pi backed by Anthropic's @anthropic-ai/sandbox-runtime. Fail-closed static filesystem policy plus per-connection network approval from pi-auto-review; sandboxes main-agent Bash and built-in subagent process trees.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -51,11 +51,12 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@anthropic-ai/sandbox-runtime": "0.0.75",
54
- "@erichll/pi-auto-review": "0.15.2"
54
+ "@erichll/pi-auto-review": "0.16.0",
55
+ "jiti": "2.7.0"
55
56
  },
56
57
  "peerDependencies": {
57
- "@earendil-works/pi-coding-agent": "^0.84.1",
58
- "pi-subagents": ">=0.50.0",
58
+ "@earendil-works/pi-coding-agent": "^0.85.0",
59
+ "pi-subagents": "0.65.0",
59
60
  "typebox": ">=1.0.0"
60
61
  },
61
62
  "peerDependenciesMeta": {
@@ -64,9 +65,10 @@
64
65
  }
65
66
  },
66
67
  "devDependencies": {
67
- "@earendil-works/pi-coding-agent": "^0.84.1",
68
+ "@earendil-works/pi-coding-agent": "^0.85.0",
69
+ "@earendil-works/pi-server": "^0.85.0",
68
70
  "@types/node": "^26.2.0",
69
- "pi-subagents": "0.63.0",
71
+ "pi-subagents": "0.65.0",
70
72
  "typescript": "^6.0.3"
71
73
  }
72
74
  }
package/src/config.ts CHANGED
@@ -28,7 +28,8 @@ export type NetworkConfig = {
28
28
  export type PiSandboxConfig = {
29
29
  subagents: {
30
30
  provider: SubagentProvider;
31
- externalWorkerIsolation: "off" | "enforce";
31
+ protection?: "native-background-tools";
32
+ allowedNativeAgents?: readonly string[];
32
33
  };
33
34
  filesystem: {
34
35
  additionalAllowRead: readonly string[];
@@ -47,7 +48,6 @@ export const DEFAULT_PI_SANDBOX_CONFIG: Readonly<PiSandboxConfig> = Object.freez
47
48
  {
48
49
  subagents: Object.freeze({
49
50
  provider: "builtin",
50
- externalWorkerIsolation: "off",
51
51
  }),
52
52
  filesystem: Object.freeze({
53
53
  additionalAllowRead: Object.freeze([]),
@@ -138,9 +138,14 @@ export function parsePiSandboxConfig(value: unknown): PiSandboxConfig {
138
138
  );
139
139
  }
140
140
  const subagents = value.subagents ?? {};
141
+ if (Object.hasOwn(subagents, "externalWorkerIsolation")) {
142
+ throw new Error(
143
+ "invalid pi-sandbox configuration: subagents.externalWorkerIsolation was removed in 0.16.0; migrate pi-subagents to protection 'native-background-tools' with allowedNativeAgents, or use provider 'builtin' for OS-level worker isolation",
144
+ );
145
+ }
141
146
  rejectUnknownKeys(
142
147
  subagents,
143
- ["provider", "externalWorkerIsolation"],
148
+ ["provider", "protection", "allowedNativeAgents"],
144
149
  "subagents",
145
150
  );
146
151
 
@@ -154,17 +159,39 @@ export function parsePiSandboxConfig(value: unknown): PiSandboxConfig {
154
159
  `invalid pi-sandbox configuration: subagents.provider must be one of ${SUBAGENT_PROVIDERS.join(", ")}`,
155
160
  );
156
161
  }
157
- const externalWorkerIsolation =
158
- subagents.externalWorkerIsolation ??
159
- DEFAULT_PI_SANDBOX_CONFIG.subagents.externalWorkerIsolation;
160
- if (
161
- externalWorkerIsolation !== "off" &&
162
- externalWorkerIsolation !== "enforce"
163
- ) {
162
+ const protection = subagents.protection;
163
+ const allowedNativeAgents = subagents.allowedNativeAgents;
164
+ if (provider === "pi-subagents" && protection !== "native-background-tools") {
164
165
  throw new Error(
165
- "invalid pi-sandbox configuration: subagents.externalWorkerIsolation must be off or enforce",
166
+ "invalid pi-sandbox configuration: provider 'pi-subagents' requires subagents.protection 'native-background-tools'; migrate legacy configurations explicitly",
166
167
  );
167
168
  }
169
+ if (provider !== "pi-subagents" && (protection !== undefined || allowedNativeAgents !== undefined)) {
170
+ throw new Error(
171
+ "invalid pi-sandbox configuration: subagents.protection and allowedNativeAgents are only valid with provider 'pi-subagents'",
172
+ );
173
+ }
174
+ if (provider === "pi-subagents") {
175
+ if (!Array.isArray(allowedNativeAgents) || allowedNativeAgents.length === 0) {
176
+ throw new Error(
177
+ "invalid pi-sandbox configuration: subagents.allowedNativeAgents must be a non-empty array of canonical agent names",
178
+ );
179
+ }
180
+ if (allowedNativeAgents.some((name) =>
181
+ typeof name !== "string" ||
182
+ name !== name.trim() ||
183
+ !/^[A-Za-z0-9_.:-]+$/u.test(name)
184
+ )) {
185
+ throw new Error(
186
+ "invalid pi-sandbox configuration: subagents.allowedNativeAgents must contain only canonical agent names",
187
+ );
188
+ }
189
+ if (new Set(allowedNativeAgents).size !== allowedNativeAgents.length) {
190
+ throw new Error(
191
+ "invalid pi-sandbox configuration: subagents.allowedNativeAgents must not contain duplicates",
192
+ );
193
+ }
194
+ }
168
195
 
169
196
  if (value.filesystem !== undefined && !isRecord(value.filesystem)) {
170
197
  throw new Error(
@@ -291,7 +318,12 @@ export function parsePiSandboxConfig(value: unknown): PiSandboxConfig {
291
318
  return {
292
319
  subagents: {
293
320
  provider: provider as SubagentProvider,
294
- externalWorkerIsolation,
321
+ ...(provider === "pi-subagents"
322
+ ? {
323
+ protection: "native-background-tools" as const,
324
+ allowedNativeAgents: [...(allowedNativeAgents as string[])],
325
+ }
326
+ : {}),
295
327
  },
296
328
  filesystem: {
297
329
  additionalAllowRead: [...new Set(additionalAllowRead)],
@@ -314,8 +346,6 @@ function defaultPiSandboxConfig(): PiSandboxConfig {
314
346
  return {
315
347
  subagents: {
316
348
  provider: DEFAULT_PI_SANDBOX_CONFIG.subagents.provider,
317
- externalWorkerIsolation:
318
- DEFAULT_PI_SANDBOX_CONFIG.subagents.externalWorkerIsolation,
319
349
  },
320
350
  filesystem: {
321
351
  additionalAllowRead: [