Skip to content

Comments

fix: add iptables bypass for container self-traffic#977

Merged
Mossaka merged 2 commits intomainfrom
copilot/fix-squid-proxy-access-errors
Feb 19, 2026
Merged

fix: add iptables bypass for container self-traffic#977
Mossaka merged 2 commits intomainfrom
copilot/fix-squid-proxy-access-errors

Conversation

Copy link
Contributor

Copilot AI commented Feb 19, 2026

Tests that bind a server to 0.0.0.0 and connect via the container's non-loopback IP (172.30.0.20) get DNAT'd to Squid and denied with 403. Loopback (127.0.0.0/8) is bypassed but the container's own eth0 IP is not.

Adds a NAT RETURN + filter ACCEPT rule for the agent's own IP, detected at runtime:

AGENT_IP=$(ip -4 addr show eth0 2>/dev/null | awk '/inet / { split($2,a,"/"); print a[1]; exit }')
if [ -n "$AGENT_IP" ] && is_valid_ipv4 "$AGENT_IP"; then
  iptables -t nat -A OUTPUT -d "$AGENT_IP" -j RETURN
  iptables -A OUTPUT -p tcp -d "$AGENT_IP" -j ACCEPT
fi
  • NAT RETURN — skips DNAT to Squid for self-directed traffic
  • Filter ACCEPT — prevents the default-deny DROP from blocking it
  • Runtime detection over hardcoded 172.30.0.20 for robustness if subnet config changes
  • Placed immediately after the existing loopback bypass, before DNS/DNAT rules
  • Same pattern as the existing host gateway bypass
Original prompt

This section details on the original issue you should resolve

<issue_title>Container self-traffic routed through Squid proxy causing test failures (go/echo, cpp/cpr, bun/hono, deno/hono)</issue_title>
<issue_description>## Problem

Tests that start a local HTTP server and connect to it via the container's own non-loopback IP fail with HTTP 403 Access Denied from Squid.

Affected test suites (from the v4 build-test experiment):

  • go/echo — 4 test failures
  • cpp/cpr — 1 test failure
  • bun/hono — 4 test failures
  • deno/hono — 4 test failures

Typical test pattern:

// Test server binds to all interfaces
e := echo.New()
e.GET("/hello", func(c echo.Context) error {
    return c.String(200, "Hello")
})
go e.Start(":8080")

// Test client connects using the container's own IP
resp, err := http.Get("http://172.30.0.20:8080/hello")
// → HTTP 403 Access Denied (from Squid!)

The same happens when port 80/443 is used and the server listens on 0.0.0.0.

Root Cause Analysis

The iptables rules in containers/agent/setup-iptables.sh correctly bypass the proxy for loopback traffic:

# Allow localhost traffic (for stdio MCP servers)
iptables -t nat -A OUTPUT -o lo -j RETURN
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN

However, there is no bypass for the container's own non-loopback IP (172.30.0.20).

When a test server binds to 0.0.0.0 and the test client connects using the container's own IP (172.30.0.20), the traffic flows like this:

test client → 172.30.0.20:PORT
   ↓ iptables nat OUTPUT chain
   No match: -d 127.0.0.0/8        (not loopback)
   No match: -d 172.30.0.10        (not Squid IP)
   MATCH:    -p tcp --dport PORT → DNAT to 172.30.0.10:3128  ← BUG
   ↓
Squid receives CONNECT/GET to 172.30.0.20:PORT
   → Not an allowed domain → 403 Access Denied

The same rules that redirect user-specified ports (AWF_ALLOW_HOST_PORTS) also catch self-directed traffic on those ports. Even if no extra ports are configured, ports 80 and 443 are always redirected.

The existing exemptions in place are:

Destination Rule Bypass reason
127.0.0.0/8 -d 127.0.0.0/8 -j RETURN Loopback / stdio MCP servers
172.30.0.10 (Squid) -d $SQUID_IP -j RETURN Prevent redirect loop
$AWF_API_PROXY_IP -d $AWF_API_PROXY_IP -j RETURN Direct sidecar access
host.docker.internal -d $HOST_GATEWAY_IP -j RETURN MCP gateway traffic
172.30.0.20 (self) MISSING ← This is the gap

Proposed Fix

Add a bypass rule for the container's own IP in the NAT OUTPUT chain, placed alongside the existing loopback bypass. The IP should be detected at runtime (not hardcoded) to be robust across configurations:

# Bypass Squid for traffic to the container's own IP
# Test frameworks often bind servers to 0.0.0.0 and connect via the non-loopback IP.
# Without this rule, the DNAT redirect rules catch self-directed traffic and
# route it through Squid, which denies it with 403 (not an allowed domain).
AGENT_IP=$(ip -4 addr show eth0 2>/dev/null | awk '/inet / { split($2,a,"/"); print a[1]; exit }')
if [ -n "$AGENT_IP" ] && is_valid_ipv4 "$AGENT_IP"; then
  echo "[iptables] Bypass Squid for self-directed traffic (agent IP: ${AGENT_IP})..."
  iptables -t nat -A OUTPUT -d "$AGENT_IP" -j RETURN
  # Also add a filter ACCEPT rule so the traffic isn't dropped by the default-deny chain
  iptables -A OUTPUT -p tcp -d "$AGENT_IP" -j ACCEPT
fi

This should be inserted immediately after the existing loopback bypass block (around line 66), before the DNS and Squid bypass rules, and before the port DNAT rules (lines 224–225).

Why runtime detection vs. hardcoding 172.30.0.20?

docker-manager.ts today hardcodes agentIp: '172.30.0.20', so 172.30.0.20 would work. However, runtime detection via ip addr is more defensive — it remains correct if the network config ever changes or if --build-local is used with a different subnet.

Security Considerations

This change is safe because:

  1. Self-traffic only — The bypass applies only to traffic whose destination is the container's own IP (172.30.0.20). External IPs are unaffected.

  2. No new exfiltration path — Any data sent to 172.30.0.20:PORT stays within the container's loopback equivalent; it cannot reach the internet.

  3. Analogy to loopback — This is functionally equivalent to the existing 127.0.0.0/8 bypass. Both cases represent in-container communication that should never be proxied.

  4. Filter chain still enforces DROP — Adding a NAT RETURN alone would have left the traffic subject to the iptables -A OUTPUT -p tcp -j DROP rule. The accompanying filter ACCEPT rule is required, scoped to just the agent's own IP.

  5. No impact on Squid domain enforcement — External HTTP/HTTPS traffic continues to flow through Squid and is subject to the domain allowlist as before.

  6. Comparison to existing exemptions — ...


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix container self-traffic routed through Squid proxy causing test failures fix: add iptables bypass for container self-traffic Feb 19, 2026
Copilot AI requested a review from Mossaka February 19, 2026 21:48
@Mossaka Mossaka marked this pull request as ready for review February 19, 2026 22:44
Copilot AI review requested due to automatic review settings February 19, 2026 22:44
@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

@github-actions
Copy link
Contributor

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 82.30% 82.45% 📈 +0.15%
Statements 82.23% 82.37% 📈 +0.14%
Functions 82.74% 82.74% ➡️ +0.00%
Branches 74.46% 74.55% 📈 +0.09%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 83.2% → 83.8% (+0.55%) 82.5% → 83.0% (+0.54%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug where test frameworks that bind HTTP servers to 0.0.0.0 and connect via the container's non-loopback IP (e.g., 172.30.0.20) fail with HTTP 403 errors. The issue occurs because the iptables NAT rules redirect this self-directed traffic through the Squid proxy, which denies it as an unauthorized domain.

Changes:

  • Added runtime detection of the container's own IP address from the eth0 interface
  • Added NAT RETURN rule to skip Squid DNAT for traffic destined to the container's own IP
  • Added filter ACCEPT rule to prevent the default DROP policy from blocking self-directed traffic

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Contributor

Smoke Test Results (Copilot)

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP - Retrieved last 2 PRs
  • ✅ Playwright - GitHub page title verified
  • ✅ File Writing - Test file created at /tmp/gh-aw/agent/smoke-test-copilot-22201432066.txt
  • ✅ Bash Tool - File content verified

Status: PASS

cc: @Copilot @Mossaka

AI generated by Smoke Copilot

@github-actions
Copy link
Contributor

Build Test: Bun - Results ✅

Project Install Tests Status
elysia 1/1 PASS ✅
hono 1/1 PASS ✅

Overall: PASS ✅

All Bun projects successfully installed dependencies and passed tests.

AI generated by Build Test Bun

@github-actions
Copy link
Contributor

C++ Build Test Results

Project CMake Build Status
fmt PASS
json PASS

Overall: PASS

All C++ projects configured and built successfully.

AI generated by Build Test C++

@github-actions
Copy link
Contributor

Build Test: Node.js - Results

Project Install Tests Status
clsx PASS PASS
execa PASS PASS
p-limit PASS PASS

Overall: PASS

All Node.js test projects installed successfully and passed their tests.

AI generated by Build Test Node.js

@github-actions
Copy link
Contributor

Smoke Test Results

Last 2 Merged PRs:

  • chore(deps): bump the all-github-actions group with 7 updates
  • feat: update agentic workflows to v0.47.0 and add smoke-gemini

Test Results:
✅ GitHub MCP - Retrieved PRs
✅ Playwright - GitHub page title verified
✅ File Writing - Test file created
✅ Bash Tool - File read successful

Status: PASS

AI generated by Smoke Claude

@github-actions
Copy link
Contributor

Go Build Test Results

Project Download Tests Status
color 1/1 PASS
env 1/1 PASS
uuid 1/1 PASS

Overall: PASS

All Go projects successfully downloaded dependencies and passed their tests.

AI generated by Build Test Go

@github-actions
Copy link
Contributor

Merged PRs: feat: update agentic workflows to v0.47.0 and add smoke-gemini; feat: set NO_COLOR=1 in agent container environment
GitHub MCP (last 2 merged PRs): ✅
safeinputs-gh pr list: ✅
Playwright title check: ✅
Tavily search: ❌
File write + cat: ✅
Discussion comment: ✅
Build (npm ci && npm run build): ✅
Overall status: FAIL

AI generated by Smoke Codex

@github-actions
Copy link
Contributor

.NET Build Test Results

Project Restore Build Run Status
hello-world PASS
json-parse PASS

Overall: PASS

All .NET projects successfully restored, built, and ran with expected output.

AI generated by Build Test .NET

@github-actions
Copy link
Contributor

Rust Build Test Results

Project Build Tests Status
fd 1/1 PASS
zoxide 1/1 PASS

Overall: PASS

All Rust projects built and tested successfully.

AI generated by Build Test Rust

@github-actions
Copy link
Contributor

Build Test: Java - Results

Project Compile Tests Status
gson 1/1 PASS
caffeine 1/1 PASS

Overall: PASS

All Java projects compiled successfully and all tests passed through the AWF firewall with Maven proxy configuration.

AI generated by Build Test Java

@github-actions
Copy link
Contributor

Build Test: Deno ✅

All Deno tests passed successfully!

Project Tests Status
oak 1/1 ✅ PASS
std 1/1 ✅ PASS

Overall: ✅ PASS

  • Deno version: 2.6.10
  • Repository: Mossaka/gh-aw-firewall-test-deno
  • All dependencies downloaded successfully from deno.land
  • Tests completed in ~2ms each

AI generated by Build Test Deno

@github-actions
Copy link
Contributor

Chroot Runtime Version Comparison Results

Runtime Host Version Chroot Version Match?
Python 3.12.12 3.12.3 ❌ NO
Node.js v24.13.0 v20.20.0 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall Result: Tests failed - not all runtime versions match between host and chroot environment.

The chroot mode successfully accessed host binaries for Go, but Python and Node.js versions differ from the host system.

AI generated by Smoke Chroot

@Mossaka Mossaka merged commit bd56836 into main Feb 19, 2026
100 checks passed
@Mossaka Mossaka deleted the copilot/fix-squid-proxy-access-errors branch February 19, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment