feat(smolmachines): patch smolvm state DB to actually enforce per-bottle allowlist
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 44s

Earlier commit framed this PR as "infrastructure landed, TSI
enforcement blocked on upstream smolvm 0.8.0." Found a clean
workaround that lets us enforce now.

Smolvm persists each machine's config (including
`allowed_cidrs`) as a JSON BLOB in
`~/Library/Application Support/smolvm/server/smolvm.db`,
`vms.data`. `machine create --allow-cidr X/32` silently writes
`allowed_cidrs: null` to that row when combined with `--from`,
but smolvm reads the row at `machine start` — so patching the
row between create and start sets the allowlist for real.

New `loopback_alias.force_allowlist(machine_name, cidrs)` opens
the SQLite DB, JSON-decodes the row, sets `allowed_cidrs`, and
writes back as BLOB (Text type silently corrupts smolvm's
later reads). launch.py calls it immediately after
`machine_create` and before `machine_start`.

Verified end-to-end on macOS / Docker Desktop:

  VM allowlist after start: ["127.0.0.16/32"]
  VM → 127.0.0.1:3000      → BLOCKED (Permission denied)
  VM → 8.8.8.8:53          → BLOCKED (Permission denied)
  VM → 127.0.0.16:<bundle> → CONNECTED

The DB-patch hack is correct only because smolvm reads
`allowed_cidrs` from the row at start time (not derived in-
process). When upstream honors `--allow-cidr` with `--from`,
the call becomes redundant — drop the call and the workaround
is gone.

Tests: 4 new for `force_allowlist` (BLOB round-trip; Linux
no-op; missing DB; missing row). Total 593 unit tests pass.

README + PRD updated to reflect the fix landed (no longer
"infrastructure pending upstream"). gitea#75 can close.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 16:55:03 -04:00
parent a919268d5e
commit 7eda2a66ec
5 changed files with 219 additions and 52 deletions
+25 -15
View File
@@ -611,21 +611,31 @@ PRD 0024's bundle image is a prerequisite — this PRD assumes
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.
**Fix + smolvm 0.8.0 workaround.** Allocate each bottle a
unique loopback alias (`127.0.0.16` .. `127.0.0.31`), bind
bundle port-forwards to it, set TSI's allowlist to that
alias's /32. The agent can only reach its own bundle; other
bottles' ports, host loopback services, and the internet are
all denied.
Smolvm 0.8.0 silently drops `--allow-cidr` when combined
with `--from <smolmachine>` (verified empirically:
`agent.config.json` shows `allowed_cidrs:null` despite the
flag). The launcher patches smolvm's persistent state DB
(`~/Library/Application Support/smolvm/server/smolvm.db`,
`vms.data` BLOB) between `machine create` and `machine
start` to set the allowlist directly. Smolvm reads the DB
at start, so TSI enforces. Tested end-to-end: VM → `127.0.0.1`
= "Permission denied"; VM → `<alias>:<bundle-port>` =
connects.
Other paths tried that didn't work: `machine update
--allow-cidr` doesn't exist; stop-edit-`agent.config.json`-
restart fails (file removed on stop); `--smolfile` mutually
exclusive with `--from`; `--image localhost:<port>/...` fails
because smolvm's pull agent can't reach host loopback during
pull. When smolvm honors `--allow-cidr` with `--from`
upstream, the DB patch becomes redundant and can be removed.
## References