From a919268d5e619768bd4674fa711139e027bce942 Mon Sep 17 00:00:00 2001 From: claude Date: Wed, 27 May 2026 16:37:56 -0400 Subject: [PATCH] docs: honest framing of upstream smolvm 0.8.0 allowlist bug PR #76 originally claimed the per-bottle alias scoping closed gitea#75 ("agent can reach host loopback"). Verified empirically that's not actually true: `smolvm 0.8.0 machine create --from --net --allow-cidr X/32` silently drops the allowlist (`agent.config.json` shows `allowed_cidrs: null`, and the running VM reaches all of `127.0.0.0/8` regardless). So the alias-allocation + alias-bind infrastructure is correct pre-work, but the actual TSI enforcement is blocked on an upstream smolvm bug. README + PRD 0023 + the module docstring get reworded to say so plainly. gitea#75 stays open. Workarounds tried (all dead-ends): - `machine update --allow-cidr` doesn't exist - stop-edit-`agent.config.json`-restart fails (smolvm removes the file on stop) - `--smolfile` is mutually exclusive with `--from` - `--image localhost:/...` fails because smolvm's agent process can't reach host loopback during pull When upstream lands a fix, our existing code (alias allocation, port-bind, --allow-cidr in launch) will scope correctly without further changes. Co-Authored-By: Claude Opus 4.7 --- README.md | 28 ++++++++++---- .../backend/smolmachines/loopback_alias.py | 23 ++++++++--- docs/prds/0023-smolmachines-backend.md | 38 +++++++++++++------ 3 files changed, 64 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index a20da9e..5031c68 100644 --- a/README.md +++ b/README.md @@ -200,14 +200,26 @@ sidecar bundle still in Docker. Selected via The integration tests run against whichever backend the env var selects and skip cleanly when its prerequisites are missing. -**One-time sudo on first launch (macOS):** smolmachines needs -per-bottle loopback aliases (`127.0.0.16` .. `127.0.0.31`) on `lo0` -so each bottle's TSI allowlist scopes to its own /32. The first -`./cli.py start` after each reboot prompts for sudo to add the pool -via `ifconfig lo0 alias`. Aliases persist until reboot; subsequent -launches don't prompt. Without this, every bottle would share -`127.0.0.1` and be able to reach unrelated host services on the -loopback. +**One-time sudo on first launch (macOS):** smolmachines bottles +each reserve a loopback alias from a pool (`127.0.0.16` .. +`127.0.0.31`) and bind their bundle's port-forwards to it; the +first `./cli.py start` after each reboot prompts for sudo to add +missing aliases via `ifconfig lo0 alias`. Aliases persist until +reboot; subsequent launches don't prompt. + +**Known v1 limitation — agent can reach the whole host +loopback:** the alias-allocation infrastructure exists, but TSI +allowlist enforcement is blocked on a smolvm 0.8.0 upstream bug: +`smolvm machine create --from --net --allow-cidr +X/32` silently drops the allowlist (the persisted +`agent.config.json` shows `allowed_cidrs: null`, and the running +VM reaches `127.0.0.0/8` regardless). So while a smolmachines +bottle is running, host-local dev services (postgres on 5432, +dev servers, etc.) are reachable from inside the agent even +though the launcher's `--allow-cidr` says otherwise. The docker +backend keeps the bottle on a `--internal` docker network and +doesn't have this issue. Tracked in gitea issue #75; will +auto-resolve once smolvm honors the flag. ## Manifest diff --git a/claude_bottle/backend/smolmachines/loopback_alias.py b/claude_bottle/backend/smolmachines/loopback_alias.py index e4f4922..65a1a0d 100644 --- a/claude_bottle/backend/smolmachines/loopback_alias.py +++ b/claude_bottle/backend/smolmachines/loopback_alias.py @@ -7,11 +7,24 @@ reach **any** service bound to macOS's loopback, not just the bundle's published ports. That's a real downgrade from the docker backend's `--internal` network isolation. -This module narrows the allowlist by allocating each bottle a -unique loopback alias (`127.0.0.16` .. `127.0.0.31` by default). -The bundle's port-forwards bind to that alias, TSI's allowlist is -the alias /32, and other host loopback services stay invisible to -the bottle. +This module is the host-side half of the eventual fix: allocate +each bottle a unique loopback alias (`127.0.0.16` .. `127.0.0.31` +by default), bind the bundle's port-forwards to that alias, and +pass the alias's /32 as smolvm's `--allow-cidr`. If TSI enforced +the allowlist, the agent could only reach its own bundle. + +**Upstream block, smolvm 0.8.0:** verified empirically that +`smolvm machine create --from --net --allow-cidr +X/32` silently drops the allowlist. The persisted +`agent.config.json` shows `allowed_cidrs: null`, and the running +VM can reach any host loopback service regardless of the +flag. `machine update --allow-cidr` doesn't exist; stop-edit- +start of `agent.config.json` doesn't work (the file is removed +on stop); `--smolfile` is mutually exclusive with `--from`. So +the alias scoping infrastructure lives here, ready, but the +TSI enforcement is blocked on a smolvm upstream fix. Until that +lands, the agent can still reach the whole `127.0.0.0/8`. The +README + gitea issue #75 spell this out. macOS only configures `127.0.0.1` on `lo0` by default; the additional aliases require `sudo ifconfig lo0 alias`. We lazily diff --git a/docs/prds/0023-smolmachines-backend.md b/docs/prds/0023-smolmachines-backend.md index 7813a8e..f326081 100644 --- a/docs/prds/0023-smolmachines-backend.md +++ b/docs/prds/0023-smolmachines-backend.md @@ -600,18 +600,32 @@ PRD 0024's bundle image is a prerequisite — this PRD assumes the plan is to filter on a deterministic name prefix `claude-bottle-` + cross-reference with on-disk metadata under `state//`. -8. **~~Loopback scoping (Docker Desktop pivot).~~ Resolved.** - Each bottle now allocates a per-bottle loopback alias from a - pool of `127.0.0.16` .. `127.0.0.31`, binds the bundle's - port-forwards to that alias, and sets TSI's allowlist to the - alias's /32. So a smolmachines bottle can only reach its own - bundle's published ports — not other bottles' ports, and not - unrelated host services on `127.0.0.1`. macOS loopback - aliases need `sudo ifconfig lo0 alias`; the launcher lazily - adds missing pool entries on first launch per reboot (sudo - prompts once, aliases persist until reboot). Linux native - daemons share the host's network namespace and skip the - alias dance. +8. **Loopback scoping (Docker Desktop pivot).** The original + design pinned the bundle at a docker bridge IP and set TSI's + allowlist to `/32`. On Docker Desktop / macOS the + daemon runs inside its own Linux VM, so bridge IPs aren't + reachable from macOS networking — TSI's syscall impersonation + can't reach them. Resolution: publish each agent-facing bundle + port on host loopback (`-p 127.0.0.1::`) and set TSI to + `127.0.0.1/32`. **This widens the TSI allowlist to anything + bound to macOS's loopback** — postgres, dev servers, other + bottles' published ports, mDNSResponder, etc. + + **Attempted fix + upstream block (`smolmachines-loopback- + alias-scoping` branch).** Allocate each bottle a unique + loopback alias (`127.0.0.16` .. `127.0.0.31`), bind bundle + port-forwards to it, set TSI's `--allow-cidr` to that /32. + Verified empirically that `smolvm 0.8.0 machine create --from + --net --allow-cidr X/32` **silently drops the + allowlist** — `agent.config.json` shows `allowed_cidrs:null` + and the VM reaches all of `127.0.0.0/8` regardless of the + flag. Workarounds tried: `machine update --allow-cidr` + doesn't exist; stop-edit-`agent.config.json`-restart fails + (file is removed on stop); `--smolfile` is mutually exclusive + with `--from`. Alias-allocation infrastructure is in place + so the day smolvm honors `--allow-cidr` with `--from`, the + scoping starts working. Until then the agent can reach the + whole host loopback. Tracked in gitea issue #75. ## References