Container runner: wildcard-bound host services reachable from containers via host.containers.internal #216

Open
opened 2026-07-23 13:50:56 +02:00 by dominik.polakovics · 0 comments

Summary

Found while validating PR #214 (containerized runner, ADR-0052). ADR-0052 states pasta gives "full egress with no route back to the host's loopback services ... never a host port." That is true only for services bound to the host loopback (127.0.0.1). A host service bound to a wildcard/LAN address (0.0.0.0) remains reachable from inside a container via host.containers.internal, which podman's --network=pasta maps by default.

This is not a break of the loopback isolation the ADR relies on, and not a merge blocker — filing so we investigate and decide on hardening later.

Why it matters here

lab's own HTTP server runs with --addr :8080 (a wildcard bind — confirmed on the dev host). So a containerized run can reach lab's operator HTTP port at host.containers.internal:8080, and likewise any other 0.0.0.0-bound service on the host (a database, metrics endpoint, another daemon).

Mitigating factors (why it's not urgent):

  • lab's agent API (/agent/v1) is served on the unix socket only, never on :8080.
  • The operator API on :8080 requires a session cookie; a container has no cookie and the run token (LAB_TOKEN) only authenticates the agent socket. So reaching :8080 is low-value.

But the surface is wider than the ADR claims, and it depends on what else the operator runs on 0.0.0.0.

Evidence (reproduced with podman's faithful default pasta args)

Faithful arg set: --config-net --dns-forward 169.254.1.1 -t none -u none -T none -U none --no-map-gw --map-guest-addr 169.254.1.2. From inside the netns, against host services:

Probe Bound to Result
127.0.0.1:PORT (guest loopback) host 127.0.0.1 no answer
<gateway>:PORT host 127.0.0.1 no answer (--no-map-gw)
host.containers.internal:PORT host 127.0.0.1 no answer
host.containers.internal:PORT host 0.0.0.0 REACHED

Mechanism: podman always appends --map-guest-addr 169.254.1.2 for host.containers.internal; per the pasta(1) man page it maps to the host's global address (not loopback). --no-map-gw (podman default) is what blocks the loopback path.

Possible mitigations to evaluate

  1. Add --map-guest-addr none to the pane argv (internal/podmanx/podmanx.go RunArgv) to drop the host.containers.internal mapping entirely — the container has no need for it (it reaches lab via the mounted unix socket).
  2. Bind lab to 127.0.0.1 on hosts where the container runner is enabled (and document that co-located services should too), relying on the reverse proxy for external access.
  3. Document the residual reachability explicitly in ADR-0052 / ops.md (partially done in the PR's ADR caveat) and treat wildcard-bound host services as in-scope for the container threat model.

Notes

  • Verified against podman main/v5.8.x vendored libnetwork/pasta and the pasta(1) man page; reproduced with nixpkgs passt on the dev host (rootless podman is not yet installed there).
  • Related: PR #214 / ADR-0052 §pasta bullet (now carries a short caveat).
## Summary Found while validating PR #214 (containerized runner, ADR-0052). ADR-0052 states pasta gives *"full egress with no route back to the host's loopback services ... never a host port."* That is **true only for services bound to the host loopback (`127.0.0.1`)**. A host service bound to a **wildcard/LAN address (`0.0.0.0`)** remains reachable from inside a container via `host.containers.internal`, which podman's `--network=pasta` maps by default. This is **not** a break of the loopback isolation the ADR relies on, and **not a merge blocker** — filing so we investigate and decide on hardening later. ## Why it matters here lab's own HTTP server runs with `--addr :8080` (a wildcard bind — confirmed on the dev host). So a containerized run **can** reach lab's operator HTTP port at `host.containers.internal:8080`, and likewise any *other* `0.0.0.0`-bound service on the host (a database, metrics endpoint, another daemon). Mitigating factors (why it's not urgent): - lab's agent API (`/agent/v1`) is served on the unix socket only, never on `:8080`. - The operator API on `:8080` requires a session cookie; a container has no cookie and the run token (`LAB_TOKEN`) only authenticates the agent socket. So reaching `:8080` is low-value. But the surface is wider than the ADR claims, and it depends on what else the operator runs on `0.0.0.0`. ## Evidence (reproduced with podman's faithful default pasta args) Faithful arg set: `--config-net --dns-forward 169.254.1.1 -t none -u none -T none -U none --no-map-gw --map-guest-addr 169.254.1.2`. From inside the netns, against host services: | Probe | Bound to | Result | |---|---|---| | `127.0.0.1:PORT` (guest loopback) | host `127.0.0.1` | no answer | | `<gateway>:PORT` | host `127.0.0.1` | no answer (`--no-map-gw`) | | `host.containers.internal:PORT` | host `127.0.0.1` | no answer | | `host.containers.internal:PORT` | host `0.0.0.0` | **REACHED** | Mechanism: podman always appends `--map-guest-addr 169.254.1.2` for `host.containers.internal`; per the pasta(1) man page it maps to the host's *global* address (not loopback). `--no-map-gw` (podman default) is what blocks the loopback path. ## Possible mitigations to evaluate 1. Add `--map-guest-addr none` to the pane argv (`internal/podmanx/podmanx.go` `RunArgv`) to drop the `host.containers.internal` mapping entirely — the container has no need for it (it reaches lab via the mounted unix socket). 2. Bind lab to `127.0.0.1` on hosts where the container runner is enabled (and document that co-located services should too), relying on the reverse proxy for external access. 3. Document the residual reachability explicitly in ADR-0052 / ops.md (partially done in the PR's ADR caveat) and treat wildcard-bound host services as in-scope for the container threat model. ## Notes - Verified against podman `main`/v5.8.x vendored `libnetwork/pasta` and the pasta(1) man page; reproduced with nixpkgs `passt` on the dev host (rootless podman is not yet installed there). - Related: PR #214 / ADR-0052 §pasta bullet (now carries a short caveat).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#216
No description provided.