What this check reads
It runs one docker inspect across every running container id with a Go template that walks .NetworkSettings.Networks and prints the container's name whenever one of its network keys is exactly bridge, the default bridge. It then reads com.docker.network.bridge.enable_icc from docker network inspect bridge. A count of zero containers on the default bridge is a PASS, and so is any count when that icc option reads false. Anything else is the WARN, which names up to five of the containers sharing the bridge.
When it applies
Needs a reachable daemon and at least one running container; otherwise SKIP. It looks at exactly one network - the default bridge - and asks two questions: are any running containers attached to it, and is com.docker.network.bridge.enable_icc set to false. User-defined networks are not examined, which is why "nothing on the default bridge" is an immediate PASS: containers on a user-defined network can still all reach each other, but that is considered the properly scoped arrangement. Compose creates user-defined networks by default, so most Compose hosts pass trivially.
What each result means
| Result | When | What it means |
|---|---|---|
| SKIP | Daemon unreachable / no running containers | Nothing was evaluated. No daemon answered, or nothing was running, so the default bridge was never inspected at all. |
| PASS | Nothing on the default bridge | No running container sits on the default bridge. Containers sharing a user-defined network can still reach each other, and this check never looks at those. |
| PASS | Default bridge with `icc=false` | Containers are on the default bridge, but the daemon blocks traffic between them, so the bridge is not a shared free-for-all. |
| WARN | Containers share the default bridge with icc on | Every container named can reach every other one's ports, so compromising any single service hands an attacker a route to the rest. |
Why it matters
On the default bridge every container can reach every other container's ports (icc), and there is no DNS-based service discovery, so people use --link or hard-coded IPs. User-defined bridge networks give automatic DNS, and containers on different networks cannot talk at all, which is the segmentation you want between, say, a web tier and a database. CIS Docker 2.2 "Ensure network traffic is restricted between containers on the default bridge" and 5.29 "Ensure that Docker's default bridge 'docker0' is not used"; OWASP Docker Cheat Sheet Rule #5; NIST SP 800-190 4.3.
Why it fails, and when it is wrong
docker runwithout--networklands on the default bridge. Compose projects create their own network and PASS automatically.--icc=false(daemon.json"icc": false) disables default-bridge chatter but also breaks legitimate communication that relies on it; user-defined networks are the usual fix instead.- Containers on
hostnetwork are not on any bridge and are not evaluated here (they have no isolation at all; see Published Container Ports and Capabilities).
How to fix it
Find the containers on the default bridge
docker inspect -f '{{.Name}} {{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' $(docker ps -q) | grep -w bridgeCreate a network per group that must talk
docker network create backend
docker network create frontendContainers on different user-defined networks cannot reach each other at all, and each network gives DNS by container name.
Attach the containers and recreate them
docker run -d --network backend --name db postgres:16
docker run -d --network backend --name app <image>
docker network connect frontend appCompose does this for you with its own project network; after changing networks there, run docker compose up -d --force-recreate.
Or switch off icc on the default bridge
{ "icc": false }Set it in /etc/docker/daemon.json and restart Docker; it cannot be changed on a running daemon. It also breaks traffic that relied on the bridge, including legacy --link setups, which is why user-defined networks are the usual answer.
Verify the fix
# Which containers are on the default bridge:
docker inspect -f '{{.Name}} {{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' $(docker ps -q)
# expected: no container listing "bridge"
docker network inspect bridge --format '{{index .Options "com.docker.network.bridge.enable_icc"}}'
# expected: false, if anything is still on the default bridge
# Prove isolation between two containers that should not talk:
docker exec <container-a> sh -c 'ping -c1 -W1 <container-b-ip> || wget -qO- --timeout=2 http://<container-b-ip>:80'
# expected: no route / timeoutDebugging
Two different messages. 'Docker daemon not reachable' means the audit account could not talk to the daemon: check systemctl is-active docker, then docker ps as the audit user and sudo docker ps, and run the audit as root or with sudo if only the second works. 'No running containers to evaluate' means there was genuinely nothing on any network to inspect. The Docker Inventory check covers the unreachable case in more detail.
Move them onto user-defined networks, which give per-network scoping and DNS by container name. In Compose this is automatic; with docker run, create a network (docker network create app) and use --network app.
The expected limit of this check. Containers sharing a user-defined network have full connectivity between them, and this check does not look at those at all. Segment by putting unrelated services on separate networks and verify with the docker exec probe above.
Requires {"icc": false} in /etc/docker/daemon.json and a daemon restart; it cannot be changed on the fly. It also breaks any container-to-container traffic that relied on it, including legacy --link setups.
Network membership is set at creation. docker inspect shows the truth; docker compose up -d --force-recreate after changing the network configuration.
Sources
- Docker: Bridge network driver (default vs user-defined, icc)
- Docker: Networking overview (user-defined networks, DNS)
- Docker: dockerd
--icc - OWASP Docker Security Cheat Sheet (Rule #5)
- NIST SP 800-190 (network segmentation)
How the script reads this
Next
Re-run the Network & Supply Chain audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 5 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.
All 5 Network & Supply Chain fixes