You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Confirm the container has a single NIC, one default route, and no proxy configuration:
container run --rm --network isolated-test alpine/curl \
sh -c 'ip -o addr show | grep -v " lo "; ip route; env | grep -i proxy || echo "(no proxy vars)"'
2: eth0 inet 192.168.250.2/24 scope global eth0
default via 192.168.250.1 dev eth0
192.168.250.0/24 dev eth0 scope link src 192.168.250.2
(no proxy vars)
Reach the public internet over TCP, addressing hosts by IP:
Confirm the traffic is NAT'd out of the host, not blocked:
# from the isolated network
container run --rm --network isolated-test alpine/curl \
curl -sS --noproxy '*' https://1.1.1.1/cdn-cgi/trace | grep ^ip=
# ip=[REDACTED — host's public IP]# from the default NAT network, for comparison
container run --rm alpine/curl \
curl -sS --noproxy '*' https://1.1.1.1/cdn-cgi/trace | grep ^ip=
# ip=[REDACTED — identical]
Both networks egress with the same public source address.
Scope of the leak
Outbound TCP is fully open. UDP and ICMP are not forwarded.
Test from the hostOnly network
Result
https://140.82.121.4 (GitHub, -k)
HTTP 200
https://151.101.1.140 (Fastly, -k)
HTTP 421 (real server response)
https://1.1.1.1, 1.0.0.1, 8.8.8.8, 9.9.9.9
301 / 301 / 302 / 404
nslookup example.com 8.8.8.8 (UDP/53)
timeout
ping 8.8.8.8 (ICMP)
100% loss
Problem description
--internal is documented and implemented as an isolation boundary, but it does not block egress. A container on a hostOnly network can open TCP connections to any internet host, NAT'd behind the host's public IP.
The intended behavior is stated consistently in four places:
NetworkMode.swift — hostOnly: "Containers can talk with each other in the same subnet only."
Isolated network #1079, which introduced the flag — "Containers under the same subnet can connect each other (and the host), but not the external network."
container network create --help — "Restrict to host-only network."
The integration test that covers this asserts isolation by resolving a hostname:
// Tests/IntegrationTests/Network/TestCLINetwork.swift
// External connection should be blocked — the isolated network has no gateway.
letexternalResult=try f.run(["run","--rm","--network", net, curlImage,"curl","--connect-timeout","5","http://google.com",])lethostOnlyBlockedCodes:Set<Int32>=[6,7,28]
#expect(hostOnlyBlockedCodes.contains(externalResult.status),...)
DNS is known not to work on host-only networks (noted by @jglogan in discussion #1170), so this test terminates on curl exit 6 — "couldn't resolve host" — which is the UDP path failing, not the TCP path being blocked. On the same network, in the same moment:
curl http://google.com → exit 6 ← scored as "blocked"
curl https://140.82.121.4/ → HTTP 200 ← actual egress
The test would pass unchanged with TCP egress wide open. #1552 later widened the assertion from == 6 to {6, 7, 28} in response to flakes, which further decoupled it from what it is meant to verify.
The missing UDP/ICMP translation and the "DNS doesn't work on host-only networks" limitation are the same underlying gap — and that gap is precisely what conceals the open TCP path.
The test comment also states "the isolated network has no gateway", but a gateway is configured unconditionally (gateway = ipv4Subnet.lower + 1), and the guest receives it as its default route.
Root cause
The only isolation mechanism is the vmnet operating mode:
There is no packet filter rule, no route suppression, and no other enforcement anywhere in the vmnet plugin. The guarantee rests entirely on VMNET_HOST_MODE suppressing forwarding, which does not hold for TCP on this configuration. The host has net.inet.ip.forwarding: 1 and holds 192.168.250.1 on bridge100.
Impact
--internal combined with a dual-homed proxy is the isolation pattern recommended to users sandboxing autonomous coding agents (discussion #1170, and #1320 / #719). Under that pattern the sandboxed workload can bypass the proxy entirely by connecting to any destination by IP address. Users following the documented guidance are getting materially less containment than the flag advertises.
Suggested fix
Enforcement — apply an explicit egress block for hostOnly subnets rather than relying on VMNET_HOST_MODE alone, or plumb through a vmnet configuration that reliably suppresses forwarding.
Test — assert against a raw IP so the check exercises the TCP path rather than DNS:
// Reaching a literal IP proves egress is open regardless of DNS state.
letexternalResult=try f.run(["run","--rm","--network", net, curlImage,"curl","-sSk","--connect-timeout","5","https://1.1.1.1/",])
#expect(externalResult.status !=0,"hostOnly network must not reach external IPs")
Retaining a hostname-based check as well is fine, but it cannot be the only signal.
Environment
OS: macOS 26.5 (25F71), Apple silicon (arm64)
Xcode: n/a — Command Line Tools only (/Library/Developer/CommandLineTools)
Container: Container CLI version 1.2.0 (build: release, commit: 6e65319)
I have done the following
(Reproduced on the 1.2.0 signed release. The relevant code paths on
mainappear unchanged — see "Root cause" below.)Steps to reproduce
Create a host-only network:
Confirm the mode:
Confirm the container has a single NIC, one default route, and no proxy configuration:
container run --rm --network isolated-test alpine/curl \ sh -c 'ip -o addr show | grep -v " lo "; ip route; env | grep -i proxy || echo "(no proxy vars)"'Reach the public internet over TCP, addressing hosts by IP:
Confirm the traffic is NAT'd out of the host, not blocked:
Both networks egress with the same public source address.
Scope of the leak
Outbound TCP is fully open. UDP and ICMP are not forwarded.
https://140.82.121.4(GitHub,-k)HTTP 200https://151.101.1.140(Fastly,-k)HTTP 421(real server response)https://1.1.1.1,1.0.0.1,8.8.8.8,9.9.9.9301/301/302/404nslookup example.com 8.8.8.8(UDP/53)ping 8.8.8.8(ICMP)Problem description
--internalis documented and implemented as an isolation boundary, but it does not block egress. A container on a hostOnly network can open TCP connections to any internet host, NAT'd behind the host's public IP.The intended behavior is stated consistently in four places:
NetworkMode.swift—hostOnly: "Containers can talk with each other in the same subnet only."container network create --help— "Restrict to host-only network."Why this has gone undetected
The integration test that covers this asserts isolation by resolving a hostname:
DNS is known not to work on host-only networks (noted by @jglogan in discussion #1170), so this test terminates on curl exit 6 — "couldn't resolve host" — which is the UDP path failing, not the TCP path being blocked. On the same network, in the same moment:
The test would pass unchanged with TCP egress wide open. #1552 later widened the assertion from
== 6to{6, 7, 28}in response to flakes, which further decoupled it from what it is meant to verify.The missing UDP/ICMP translation and the "DNS doesn't work on host-only networks" limitation are the same underlying gap — and that gap is precisely what conceals the open TCP path.
The test comment also states "the isolated network has no gateway", but a gateway is configured unconditionally (
gateway = ipv4Subnet.lower + 1), and the guest receives it as its default route.Root cause
The only isolation mechanism is the vmnet operating mode:
There is no packet filter rule, no route suppression, and no other enforcement anywhere in the vmnet plugin. The guarantee rests entirely on
VMNET_HOST_MODEsuppressing forwarding, which does not hold for TCP on this configuration. The host hasnet.inet.ip.forwarding: 1and holds192.168.250.1onbridge100.Impact
--internalcombined with a dual-homed proxy is the isolation pattern recommended to users sandboxing autonomous coding agents (discussion #1170, and #1320 / #719). Under that pattern the sandboxed workload can bypass the proxy entirely by connecting to any destination by IP address. Users following the documented guidance are getting materially less containment than the flag advertises.Suggested fix
Enforcement — apply an explicit egress block for hostOnly subnets rather than relying on
VMNET_HOST_MODEalone, or plumb through a vmnet configuration that reliably suppresses forwarding.Test — assert against a raw IP so the check exercises the TCP path rather than DNS:
Retaining a hostname-based check as well is fine, but it cannot be the only signal.
Environment
/Library/Developer/CommandLineTools)Code of Conduct