feat(smolmachines): per-bottle loopback alias scopes TSI to single /32
PR #74's Docker-Desktop fix routed the agent through `127.0.0.1:<random>` loopback forwards, but TSI filters by IP only — so the allowlist `127.0.0.1/32` let the agent VM reach **any** host service on macOS loopback (postgres, dev servers, other bottles' published ports, mDNSResponder, ...). Real downgrade vs the docker backend's `--internal` network. Resolution: per-bottle loopback alias. - New `loopback_alias` module manages a pool of `127.0.0.16` .. `127.0.0.31` on `lo0`. macOS only routes `127.0.0.1` by default; the extras need `sudo ifconfig lo0 alias`. `ensure_pool()` lazily adds the missing entries via one sudo prompt on first launch per reboot — aliases persist on `lo0` until reboot, so subsequent launches skip the prompt entirely. - `allocate(slug)` picks the lowest-numbered unused alias by inspecting running bundle containers' port-binding HostIps. No on-disk reservation — docker is the source of truth. - Bundle bringup binds published ports to the allocated alias (`docker run -p <alias>::<port>`) instead of `127.0.0.1`. - TSI allowlist becomes the alias's /32 — narrows reachability to this bottle's bundle only. - Linux native daemons share the host's network namespace; `127.0.0.0/8` works without aliases, so the module no-ops on non-Darwin and returns `127.0.0.1` from `allocate`. Tracking issue closed: gitea/issues/75. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -600,22 +600,18 @@ 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).** 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. The agent can't
|
||||
reach them by intent, but TSI can't filter by port. Follow-up
|
||||
to scope back: bind each bottle's bundle ports on a per-bottle
|
||||
loopback alias (e.g. `127.0.0.2` for bottle A, `127.0.0.3` for
|
||||
B) added via `ifconfig lo0 alias`, set TSI to that single /32.
|
||||
Needs sudo for alias setup; a small daemon-or-script we ship
|
||||
alongside the launcher could handle it.
|
||||
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.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
Reference in New Issue
Block a user