Summary
On the Windows L2Bridge egress path, one job container can reach another job's dind (fake Docker API) listener and drive it — running containers, exec'ing, and bind-mounting inside the victim job's namespace. Cross-job confidentiality and integrity break.
Mechanism
- Each job's dind server binds a TCP listener on the host's LAN address with an ephemeral port (
pkg/dind/listen_windows.go), and hands the job DOCKER_HOST=tcp://<hostIP>:<port>.
- The VFP host
/32 allow (required so the container can reach the host at all) cannot be port-scoped — a port-scoped Switch ACL blackholes the entire endpoint — so port precision lives in the host firewall.
openHostPort (pkg/networking/firewall_windows.go) opens that port with remoteip=<ip_pool> — the entire container pool — rather than the owning container's /32.
- The fake Docker daemon performs no authentication (
pkg/dind/registry.go — credentials are accepted at face value).
So container A, a LAN peer on L2Bridge, can scan the host's LAN IP across the ephemeral range, find container B's dind endpoint, and issue Docker API calls against it. Bounded only by port discovery, which is cheap.
Impact
Cross-job escape of confidentiality and integrity on a shared Windows node: read another job's build context, execute in its containers, mount paths into them. Relevant whenever runner.max_concurrent > 1 (default on the Windows node) with dind.enabled.
Not a host escape — the Hyper-V boundary and the host-hardening denylist (135/139/445/3389/5985/5986/47001) still hold.
Fix
Scope the per-job host-port allow to the owning container's address (remoteip=<containerIP>/32) instead of the pool, and make the rule name per-container so concurrent jobs get distinct rules that can't delete each other's. Fail closed (no allow) rather than falling back to pool scope if the container IP isn't known.
Longer term, the fake Docker daemon should not rely solely on network reachability for authorization — a per-job token would make the network scoping defense-in-depth rather than the only control.
Provenance
Found in a pre-promotion security sweep of v0.2.0. Introduced with the dind-over-L2Bridge support in v0.2.0 (the host-port allow), not present before L2Bridge existed since dind was unreachable on the NAT path.
Summary
On the Windows L2Bridge egress path, one job container can reach another job's dind (fake Docker API) listener and drive it — running containers, exec'ing, and bind-mounting inside the victim job's namespace. Cross-job confidentiality and integrity break.
Mechanism
pkg/dind/listen_windows.go), and hands the jobDOCKER_HOST=tcp://<hostIP>:<port>./32allow (required so the container can reach the host at all) cannot be port-scoped — a port-scoped Switch ACL blackholes the entire endpoint — so port precision lives in the host firewall.openHostPort(pkg/networking/firewall_windows.go) opens that port withremoteip=<ip_pool>— the entire container pool — rather than the owning container's/32.pkg/dind/registry.go— credentials are accepted at face value).So container A, a LAN peer on L2Bridge, can scan the host's LAN IP across the ephemeral range, find container B's dind endpoint, and issue Docker API calls against it. Bounded only by port discovery, which is cheap.
Impact
Cross-job escape of confidentiality and integrity on a shared Windows node: read another job's build context, execute in its containers, mount paths into them. Relevant whenever
runner.max_concurrent > 1(default on the Windows node) withdind.enabled.Not a host escape — the Hyper-V boundary and the host-hardening denylist (135/139/445/3389/5985/5986/47001) still hold.
Fix
Scope the per-job host-port allow to the owning container's address (
remoteip=<containerIP>/32) instead of the pool, and make the rule name per-container so concurrent jobs get distinct rules that can't delete each other's. Fail closed (no allow) rather than falling back to pool scope if the container IP isn't known.Longer term, the fake Docker daemon should not rely solely on network reachability for authorization — a per-job token would make the network scoping defense-in-depth rather than the only control.
Provenance
Found in a pre-promotion security sweep of v0.2.0. Introduced with the dind-over-L2Bridge support in v0.2.0 (the host-port allow), not present before L2Bridge existed since dind was unreachable on the NAT path.