docs: honest framing of upstream smolvm 0.8.0 allowlist bug
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 40s

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 <smolmachine> --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:<port>/...` 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 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 16:37:56 -04:00
parent 2edc1abb9a
commit a919268d5e
3 changed files with 64 additions and 25 deletions
+20 -8
View File
@@ -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 <smolmachine> --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
@@ -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 <smolmachine> --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
+26 -12
View File
@@ -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-<slug>` + cross-reference with on-disk metadata
under `state/<slug>/`.
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 `<bundle-ip>/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::<port>`) 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
<smolmachine> --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