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 <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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user