@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 +30 -0
- package/README.md +51 -76
- package/package.json +8 -6
- package/src/config.ts +44 -14
- package/src/index.ts +96 -136
- package/src/pi-subagents-native.ts +250 -0
- package/src/external-network-policy.d.mts +0 -8
- package/src/external-network-policy.mjs +0 -27
- package/src/external-runs-view.ts +0 -173
- package/src/external-supervisor.ts +0 -189
- package/src/external-worker-launcher.mjs +0 -266
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.
|
|
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
|
|
95
|
-
|
|
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.
|
|
97
|
+
### `pi-subagents` 0.65.0 native-background tool boundary
|
|
100
98
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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 |
|
|
129
|
+
| Outer worker sandbox | Yes | No |
|
|
113
130
|
| Bash sandbox | Yes | Yes |
|
|
114
131
|
| Persistent follow-up | Yes | Yes |
|
|
115
|
-
| `workflowScript
|
|
116
|
-
|
|
|
117
|
-
|
|
|
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
|
-
|
|
120
|
-
|
|
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
|
|
181
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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`
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
58
|
-
"pi-subagents": "
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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", "
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
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.
|
|
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
|
-
|
|
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: [
|