@stixxert/pi-docker-sandbox 0.1.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/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@stixxert/pi-docker-sandbox",
3
+ "version": "0.1.0",
4
+ "description": "pi extension: a private docker sandbox (sbx microVM with its own daemon) as the agent's deploy target — the host's docker is never exposed.",
5
+ "license": "Apache-2.0",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/stixxert/pi-docker-sandbox.git"
12
+ },
13
+ "files": ["index.ts", "README.md", "boundary.md", "security.md", "LICENSE"],
14
+ "keywords": ["pi-package"],
15
+ "type": "module",
16
+ "scripts": {
17
+ "typecheck": "tsc --noEmit",
18
+ "test": "node smoke-test.mjs",
19
+ "prepublishOnly": "npm run typecheck && npm test"
20
+ },
21
+ "pi": {
22
+ "extensions": ["./index.ts"]
23
+ },
24
+ "peerDependencies": {
25
+ "@earendil-works/pi-ai": "*",
26
+ "@earendil-works/pi-coding-agent": "*"
27
+ },
28
+ "devDependencies": {
29
+ "@earendil-works/pi-ai": "^0.84.1",
30
+ "@earendil-works/pi-coding-agent": "^0.84.1",
31
+ "@types/node": "^24.0.0",
32
+ "typescript": "^5.9.0"
33
+ }
34
+ }
package/security.md ADDED
@@ -0,0 +1,117 @@
1
+ # Isolation model & verification
2
+
3
+ This extension is designed so a pi agent's docker reach is **confined to its
4
+ own sandbox microVM** and the host's docker is never exposed to it.
5
+
6
+ ## Threat model
7
+
8
+ - **Agent is untrusted at the docker layer.** It may try to reach any docker
9
+ daemon it can find, read host config, or escape its sandbox.
10
+ - **Host user is trusted.** The host runs Docker Sandboxes (`sbx`), which
11
+ isolate each sandbox as a microVM with its own kernel.
12
+ - Out of scope: an agent that compromises the host OS itself (that would
13
+ bypass any docker-level control — the sandbox is not a host security
14
+ boundary, it is a docker-access boundary).
15
+
16
+ ## Design guarantees (by construction)
17
+
18
+ 1. **Transport: `sbx` only.** The extension imports only
19
+ `node:child_process/fs/path` and invokes `sbx exec <sandbox> -- docker ...`.
20
+ It never opens a docker socket, never speaks the Docker Engine HTTP API,
21
+ and never invokes a host `docker` binary. There is no code path that can
22
+ fall back to a host daemon.
23
+ 2. **Env scrubbing.** Before any `sbx exec`, all `DOCKER_*` and `COMPOSE_*`
24
+ variables are removed from the child environment. A leaked `DOCKER_HOST`
25
+ or `DOCKER_CONTEXT` on the host cannot redirect the inner docker CLI.
26
+ 3. **Daemon pinning.** Every inner docker invocation is pinned with
27
+ `-H unix:///var/run/docker.sock`, i.e. the sandbox's own daemon.
28
+ 4. **Per-session sandboxes.** Each pi session gets a uniquely named sandbox
29
+ (`pi-sbx-<pid>-<random>`, or an explicit `DOCKER_SANDBOX`), auto-provisioned
30
+ with only the session's workspace mounted. Two concurrent sessions cannot
31
+ share or observe each other's docker state.
32
+ 5. **Filesystem.** Only the session workspace (the dir mounted at `/workspace`
33
+ in the agent VM) is direct-mounted into the sandbox. Host `~/.docker`,
34
+ `~/.ssh`, `~/.agent`, and other host paths are not mounted.
35
+ 6. **Env confidentiality (secure by default).** `DOCKER_*`/`COMPOSE_*` are
36
+ always stripped from every child env. By default only a minimal safe set
37
+ (`HOME`, `PATH`, `USER`, `LOGNAME`, `TMPDIR`, `SHELL`, `LANG`, `TERM`) is
38
+ forwarded. Opt in precisely with `DOCKER_SANDBOX_ENV_ALLOWLIST="A,B"`
39
+ (adds exactly `A`,`B`), or opt out entirely with
40
+ `DOCKER_SANDBOX_ENV_PASSTHROUGH=1` (host env minus the docker vars).
41
+ Anything running inside the sandbox can read whatever reaches it — this
42
+ knob confines that surface.
43
+
44
+ ### Read-only workspace mode (`DOCKER_SANDBOX_WORKSPACE_RO=1`)
45
+
46
+ sbx requires the primary workspace to be rw, so in this mode the extension
47
+ creates a small scratch primary (`~/.sbx-prime-<sandbox>`) and mounts the
48
+ project as an **additional read-only** workspace. Consequences (all verified):
49
+
50
+ - Writes from inside the sandbox to the project fail (`Read-only file system`).
51
+ - `docker build` from the project context still works (the daemon only reads
52
+ the context; build cache lives in the sandbox VM).
53
+ - `docker compose up --build` works.
54
+ - Bind mounts sourced from the project are read-only in containers (the
55
+ extension appends `:ro` automatically); containers cannot modify project
56
+ files.
57
+ - All project writes must go through the agent's own tools (`/workspace` in
58
+ its VM) — the intended trust boundary. The sandbox becomes a pure
59
+ read-and-execute environment for the project.
60
+
61
+ ## Live verification: `docker_verify`
62
+
63
+ The `docker_verify` tool runs a runtime audit of the sandbox and reports
64
+ PASS/FAIL with evidence for each check:
65
+
66
+ | Check | What it proves |
67
+ |-------|----------------|
68
+ | transport | only `sbx exec` is used (design assertion) |
69
+ | env scrub | no `DOCKER_*`/`COMPOSE_*` vars exist inside the sandbox (a `DOCKER_HOST_SENTINEL` is injected into the host process env and asserted absent inside the sandbox — so this tests the scrubber itself, not just the VM base env) |
70
+ | env forwarding | the active mode (strict by default / allowlist / passthrough) is reported; unless passthrough is explicitly enabled, a non-allowlisted probe var is injected into the host env and asserted absent inside the sandbox; explicit passthrough is flagged with a warning |
71
+ | daemon pinning | docker calls reach the sandbox daemon (`-H unix:///var/run/docker.sock`) |
72
+ | mounts | host docker config / host home are NOT visible inside the sandbox |
73
+ | contexts | only the sandbox's own `default` docker context is reachable |
74
+ | host socket | the host Docker Desktop socket (`~/.docker/run/docker.sock`, macOS) does not exist inside the sandbox; on Linux the canonical `/var/run/docker.sock` path is also the sandbox's own pinned socket, so that check is scoped to macOS and Linux daemon identity rests on the daemon-pinning check |
75
+ | port binds | published ports bind to host `127.0.0.1` only (sbx forwarding) |
76
+
77
+ Run it before and after deploys. If any check FAILs, treat the sandbox as
78
+ compromised and remove it (`docker_sandbox_rm` / `sbx rm <name>`).
79
+
80
+ ## Lifecycle & teardown (isolation relevant)
81
+
82
+ Teardown is guaranteed by **two independent mechanisms**, so a sandbox cannot
83
+ outlive its session except by host reboot:
84
+
85
+ 1. **Watchdog (primary, crash-proof).** When a session's sandbox is created
86
+ (and at every `session_start`), a detached shell process is armed that
87
+ polls the pi process with `kill -0`. The moment pi exits — gracefully or
88
+ via `kill -9`/process-manager kill — the watchdog runs the teardown
89
+ (`sbx rm --force` or `sbx stop`, retried 5x to ride out sandboxd races).
90
+ It is spawned `detached` (own session), so process-group kills of pi do
91
+ not take it down.
92
+ 2. **`session_shutdown` hook (fast path).** On graceful exits and
93
+ `/new`/`/resume`/`/fork` it tears down immediately without waiting for the
94
+ watchdog poll.
95
+
96
+ Policy: `DOCKER_SANDBOX_TEARDOWN` = `remove` (default for session-scoped
97
+ sandboxes) \| `stop` \| `none` (default for pinned `DOCKER_SANDBOX` names).
98
+ Pinned/shared sandboxes are left alone unless the user opts in.
99
+
100
+ **Crash-path GC:** if the host reboots, both mechanisms die and the sandbox is
101
+ left stopped. The GC sweep (at session start and via `docker_gc`) reclaims
102
+ `pi-sbx-*` sandboxes that are stopped, not the current session's, and older
103
+ than `DOCKER_SANDBOX_GC_HOURS` (default 24h). Running sandboxes and non-pi
104
+ sandboxes are never touched.
105
+
106
+ **Manual:** `docker_sandbox_rm` removes the current session's sandbox;
107
+ `sbx rm --force <name>` from a host pane removes any sandbox.
108
+
109
+ ## Known boundaries (documented, not bugs)
110
+
111
+ - The sandbox can read/write the mounted workspace — that is the deploy
112
+ surface by design.
113
+ - A compromised sandbox could in principle attack the host through the
114
+ workspace mount (e.g. plant files the host user later executes). Same trust
115
+ level as any agent tooling writing to the workspace.
116
+ - `sbx` port forwarding binds `127.0.0.1` on the host; apps inside the sandbox
117
+ are not reachable from the LAN unless the host user forwards further.