Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions src/services/sandbox/egress_firewall.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

This module installs iptables OUTPUT rules that match on the sandbox uid:
- ALLOW the sandbox uid → 127.0.0.1:<proxy_port> (so pip etc. work)
- DROP everything else from the sandbox uid
- ALLOW the sandbox uid → 127.0.0.53:53 (DNS via systemd-resolved)
- REJECT everything else from the sandbox uid
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IPv6 not covered (defense-in-depth gap). This REJECT — and all rules here — are iptables (IPv4) only. ip6tables OUTPUT policy is ACCEPT with no rules, so over IPv6 the sandbox uid is unrestricted. Not exploitable in the default deploy (Docker bridge IPv6 off, API binds 0.0.0.0/IPv4-only, internal services in separate netns, seccomp blocks bind for non-bash) — but the invariant is absent for v6. Consider mirroring these rules via ip6tables (or asserting IPv6 is disabled) so it holds if an operator ever enables Docker IPv6.


The API process itself runs as root (uid 0), so the proxy's own outbound
traffic to PyPI/npm/etc. is unaffected by these rules.
Expand Down Expand Up @@ -113,19 +114,41 @@ def install_sandbox_egress_rules(sandbox_uid: int, proxy_port: int) -> bool:
"-j",
"ACCEPT",
],
# Allow loopback traffic generally (DNS to systemd-resolved on 127.0.0.53,
# localhost-only services, etc.). The proxy enforces hostname allowlist
# for actual outbound; this just keeps the sandbox uid able to talk
# to itself if it ever needs to.
# Allow DNS to systemd-resolved on loopback (some tools resolve
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is inaccurate for the Docker runtime: systemd-resolved does not run inside the container (no systemd; CMD is python3 -m src.main), so nothing listens on 127.0.0.53. 127.0.0.53 is the host's stub resolver — see the # ExtServers: [host(127.0.0.53)] line in the container's /etc/resolv.conf. The actual in-container resolver is 127.0.0.11.

# before handing the CONNECT to the proxy).
[
"-A",
"OUTPUT",
"-m",
"owner",
"--uid-owner",
str(sandbox_uid),
"-o",
"lo",
"-d",
"127.0.0.53",
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong resolver address. Verified on the live container: /etc/resolv.conf is nameserver 127.0.0.11 (Docker embedded DNS); 127.0.0.53 has no listener inside the container. Two consequences:

  1. This ACCEPT is effectively dead — it never matches real DNS traffic.
  2. The sandbox inherits the container's /etc/resolv.conf (chroot to /, the unshare --mount wrapper never remounts /etc), so any tool doing direct getaddrinfo() queries 127.0.0.11:53 → caught by the catch-all REJECT. So direct sandbox-side DNS that worked before this PR now fails.

Not a security regression (fails closed) and the main path is fine (the proxy resolves DNS as root for HTTPS_PROXY tools; the old -o lo rule matched 0 packets). Recommendation: drop both DNS rules entirely and update the docstring — the proxy already covers DNS. If you'd rather keep direct resolution, change 127.0.0.53127.0.0.11 in both rules instead.

"-p",
"udp",
"--dport",
"53",
"-m",
"comment",
"--comment",
_RULE_COMMENT,
"-j",
"ACCEPT",
],
[
"-A",
"OUTPUT",
"-m",
"owner",
"--uid-owner",
str(sandbox_uid),
"-d",
"127.0.0.53",
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same wrong-address issue as the UDP rule above (127.0.0.53 → should be 127.0.0.11, or drop both DNS rules).

"-p",
"tcp",
"--dport",
"53",
"-m",
"comment",
"--comment",
Expand Down
Loading