# CL-0005: Ports Bound to All Interfaces

**Severity:** HIGH

**References:**
- [OWASP Docker Security Rule #5a](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-5a-be-careful-when-mapping-container-ports-to-the-host-with-firewalls-like-ufw)
- CIS Docker Benchmark 5.14 — Ensure that incoming container traffic is bound to a specific host interface

## What it detects

Port mappings that don't specify a bind address or that bind to a wildcard, in both short syntax (`"8080:80"`, `"0.0.0.0:8080:80"`, `"[::]:8080:80"`) and long syntax (`published:` without `host_ip:`, or with `host_ip: "0.0.0.0"` / `host_ip: "::"`). A bare short-syntax port with no colon (`"80"`, `3001`, a `"3000-3005"` range) is also flagged: Docker still publishes it, assigning a random (ephemeral) host port bound to all interfaces (`docker compose config` normalizes `- "80"` to a published `target`, and `docker compose up` binds it on `0.0.0.0`). Bind it with `127.0.0.1::80` to keep the ephemeral port on localhost. Specific bind addresses including loopback (`127.0.0.1`, `[::1]`) are not flagged.

## Why it matters

Docker publishes ports by manipulating iptables directly, bypassing host firewalls like UFW and firewalld. A port published without a bind address is accessible on all network interfaces, potentially exposing services to the public internet even when host firewall rules would otherwise block the traffic.

## Fix

```yaml
# Instead of (binds to all interfaces, bypasses host firewall):
ports:
  - "8080:80"

# Bind to localhost only:
ports:
  - "127.0.0.1:8080:80"

# Long syntax:
ports:
  - target: 80
    published: 8080
    host_ip: "127.0.0.1"
```

If this service needs to be publicly accessible, place it behind a reverse proxy (Traefik, Caddy, nginx) that handles TLS termination, and bind only the reverse proxy's ports publicly.

For systemic mitigation, configure the `DOCKER-USER` iptables chain on the host so that Docker's bypass of UFW/firewalld becomes per-rule rather than per-service. This is documented in [Docker and iptables](https://docs.docker.com/engine/network/packet-filtering-firewalls/). Per-service binding is whack-a-mole; the chain-level rule is the durable fix.

### Swarm caveat

`host_ip` in the long syntax is not always honored under Swarm mode — Swarm uses an ingress mesh and may publish ports on all manager nodes regardless. If you're targeting Swarm, port publishing is a Swarm concern; this rule still flags the file but treat the fix as Swarm-specific.

## When to suppress

- Public-facing service on a VPS where the cloud provider's firewall is the security boundary and `0.0.0.0` is intended. Suppress with a `reason:` naming the firewall layer (e.g. `reason: "GCP firewall enforces ingress; UFW disabled by design"`).

## See also

- [CL-0008](CL-0008.md) — `network_mode: host` (port-publish controls don't apply at all)
