Skip to content

fix: always set NO_PROXY to bypass Squid for localhost#1032

Merged
Mossaka merged 2 commits intomainfrom
fix-localhost-interception
Feb 25, 2026
Merged

fix: always set NO_PROXY to bypass Squid for localhost#1032
Mossaka merged 2 commits intomainfrom
fix-localhost-interception

Conversation

@Mossaka
Copy link
Collaborator

@Mossaka Mossaka commented Feb 25, 2026

Summary

  • Always set NO_PROXY in the agent container environment so HTTP clients don't voluntarily route localhost-bound requests through Squid (which rejects them with 403)
  • The baseline NO_PROXY includes localhost, 127.0.0.1, ::1, 0.0.0.0, plus the Squid and agent container IPs
  • enableHostAccess and enableApiProxy now append to this baseline instead of conditionally creating NO_PROXY
  • Added an iptables RETURN rule for 0.0.0.0 as defense-in-depth

Problem

When HTTP_PROXY is set but NO_PROXY is not (the default case without --enable-host-access or --api-proxy), HTTP clients (Go's net/http, Python's requests, curl, etc.) see the proxy env var and voluntarily route all traffic through Squid — including requests to localhost. Squid checks its domain allowlist, finds localhost is not allowed, and returns 403 Forbidden.

This broke test frameworks that start local servers (e.g., go test with Echo, Python with uvicorn, Deno with Fresh).

Changes

File Change
src/docker-manager.ts Always set NO_PROXY with localhost entries + agent/squid IPs
containers/agent/setup-iptables.sh Add 0.0.0.0 RETURN rule (belt-and-suspenders)
src/docker-manager.test.ts 3 new tests for NO_PROXY baseline behavior

Test plan

  • npm run build — TypeScript compiles cleanly
  • npm test — All 798 unit tests pass (including 3 new tests)
  • Integration tests on CI
  • Manual verification: run a Go test suite with a local HTTP server inside AWF

🤖 Generated with Claude Code

When HTTP_PROXY is set but NO_PROXY is not, HTTP clients (Go net/http,
Python requests, curl, etc.) voluntarily route localhost-bound requests
through Squid, which rejects them with 403 because localhost is not in the
domain allowlist. This broke test frameworks that start local servers
(go/echo, python/uvicorn, deno/fresh).

The fix unconditionally sets NO_PROXY with localhost, 127.0.0.1, ::1,
0.0.0.0, plus the Squid and agent container IPs. The enableHostAccess
and enableApiProxy branches now append to this baseline instead of
conditionally creating it.

Also adds an iptables RETURN rule for 0.0.0.0 as defense-in-depth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 18:00
@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 82.30% 82.54% 📈 +0.24%
Statements 82.23% 82.46% 📈 +0.23%
Functions 82.74% 82.74% ➡️ +0.00%
Branches 74.46% 74.65% 📈 +0.19%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 83.2% → 84.1% (+0.89%) 82.5% → 83.4% (+0.87%)

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 prevents localhost-bound HTTP requests from being routed through the Squid proxy by always setting a baseline NO_PROXY/no_proxy in the agent container, then appending host-access and api-proxy addresses when enabled.

Changes:

  • Always initialize NO_PROXY/no_proxy with localhost/loopback + squid/agent IPs; append host gateway / api-proxy IPs when enabled.
  • Add an iptables NAT RETURN rule for destination 0.0.0.0 as defense-in-depth.
  • Add unit tests covering the new NO_PROXY baseline behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/docker-manager.ts Always sets baseline NO_PROXY/no_proxy and switches host/api-proxy logic to append.
containers/agent/setup-iptables.sh Adds a NAT RETURN rule for 0.0.0.0 alongside existing localhost bypass rules.
src/docker-manager.test.ts Adds tests asserting baseline NO_PROXY presence and host-access append behavior.

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

Comment on lines +371 to +372
environment.NO_PROXY = `localhost,127.0.0.1,::1,0.0.0.0,${networkConfig.squidIp},${networkConfig.agentIp}`;
environment.no_proxy = environment.NO_PROXY;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

no_proxy is initialized to mirror NO_PROXY here, but later additionalEnv (from --env) can override NO_PROXY without also updating no_proxy, leaving them inconsistent. Since many HTTP clients prefer one casing over the other, this can reintroduce proxying issues for users who set --env NO_PROXY=.... Consider normalizing after additionalEnv is applied (e.g., if either key is set, set the other to the same value, with a clear precedence rule).

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in d38f98f. After additionalEnv is applied, the code now detects if NO_PROXY and no_proxy have diverged and syncs them. If --env NO_PROXY=... was passed, no_proxy gets synced to match, and vice versa. Added 2 tests covering both override directions.

@github-actions
Copy link
Contributor

Node.js Build Test Results ✅

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

Overall: PASS

Generated by Build Test Node.js for issue #1032

@github-actions
Copy link
Contributor

Smoke Test Results — Copilot Engine

GitHub MCP: Last 2 merged PRs:

Playwright: github.com title contains "GitHub"
File Write: /tmp/gh-aw/agent/smoke-test-copilot-22409340137.txt created
Bash: File verified via cat

Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot for issue #1032

@github-actions
Copy link
Contributor

Smoke Test Results

Overall: PASS

💥 [THE END] — Illustrated by Smoke Claude for issue #1032

@github-actions
Copy link
Contributor

Go Build Test Results

Project Download Tests Status
color PASS ✅ PASS
env PASS ✅ PASS
uuid PASS ✅ PASS

Overall: ✅ PASS

Generated by Build Test Go for issue #1032

@github-actions
Copy link
Contributor

.NET Build Test Results

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

Overall: ✅ PASS

Run output

hello-world:

Hello, World!

json-parse:

{
  "Name": "AWF Test",
  "Version": 1,
  "Success": true
}
Name: AWF Test, Success: True

Generated by Build Test .NET for issue #1032

@github-actions
Copy link
Contributor

🦀 Rust Build Test Results

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

Overall: ✅ PASS

Generated by Build Test Rust for issue #1032

@github-actions
Copy link
Contributor

Deno Build Test Results

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

Overall: ✅ PASS

Generated by Build Test Deno for issue #1032

@github-actions
Copy link
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.12 Python 3.12.3
Node.js v24.13.1 v20.20.0
Go go1.22.12 go1.22.12

Result: Not all tests passed — Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot for issue #1032

@github-actions
Copy link
Contributor

Java Build Test Results

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

Overall: PASS

Generated by Build Test Java for issue #1032

Address review feedback: when --env overrides NO_PROXY (or no_proxy)
without setting the other casing, HTTP clients that prefer the other
casing would still route through Squid. After additionalEnv is applied,
detect the inconsistency and sync both variables with clear precedence.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Contributor

Build Test: Bun Results

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

Overall: PASS

  • Bun version: 1.3.9
  • elysia: 1 test passed (add)
  • hono: 1 test passed (mul)

Generated by Build Test Bun for issue #1032

@github-actions
Copy link
Contributor

Node.js Build Test Results

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

Overall: ✅ PASS

Generated by Build Test Node.js for issue #1032

@github-actions
Copy link
Contributor

Build Test: Deno Results

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

Overall: ✅ PASS

Generated by Build Test Deno for issue #1032

@github-actions
Copy link
Contributor

C++ Build Test Results

Project CMake Build Status
fmt PASS
json PASS

Overall: PASS 🎉

Generated by Build Test C++ for issue #1032

@github-actions
Copy link
Contributor

Smoke Test Results — PASS

💥 [THE END] — Illustrated by Smoke Claude for issue #1032

@github-actions
Copy link
Contributor

GitHub MCP review: ✅ docs: add sandbox design rationale (Docker vs microVMs); docs: update runner and architecture compatibility
safeinputs-gh pr list: ✅ fix: always set NO_PROXY to bypass Squid for localhost; [Deps] Safe dependency updates (2026-02-25)
Playwright title check: ✅
Tavily search: ❌ (tool unavailable)
File write: ✅
Bash cat verify: ✅
Discussion comment: ✅
Build npm ci && npm run build: ✅
Overall: FAIL

🔮 The oracle has spoken through Smoke Codex for issue #1032

@github-actions
Copy link
Contributor

Java Build Test Results

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

Overall: PASS

Generated by Build Test Java for issue #1032

@github-actions
Copy link
Contributor

Rust Build Test Results

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

Overall: PASS

Generated by Build Test Rust for issue #1032

@github-actions
Copy link
Contributor

Smoke test results for run 22409728925@Mossaka

Test Result
GitHub MCP (last 2 merged PRs: #1025, #992)
Playwright (github.com title contains "GitHub")
File write (smoke-test-copilot-22409728925.txt)
Bash verify (cat confirms content)

Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot for issue #1032

@github-actions
Copy link
Contributor

Chroot Version Comparison Results

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

Overall: Tests did not fully pass — Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot for issue #1032

@github-actions
Copy link
Contributor

Go Build Test Results ✅

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

Overall: PASS

Generated by Build Test Go for issue #1032

@github-actions
Copy link
Contributor

.NET Build Test Results

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

Overall: PASS

Run output

hello-world:

Hello, World!
```

**json-parse:**
```
{
  "Name": "AWF Test",
  "Version": 1,
  "Success": true
}
Name: AWF Test, Success: True

Generated by Build Test .NET for issue #1032

@Mossaka Mossaka requested a review from Copilot February 25, 2026 18:43
@Mossaka Mossaka merged commit 78b798b into main Feb 25, 2026
129 of 137 checks passed
@Mossaka Mossaka deleted the fix-localhost-interception branch February 25, 2026 18:43
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

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


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

Comment on lines +458 to +464
if (environment.NO_PROXY !== environment.no_proxy) {
if (config.additionalEnv?.NO_PROXY) {
environment.no_proxy = environment.NO_PROXY;
} else if (config.additionalEnv?.no_proxy) {
environment.NO_PROXY = environment.no_proxy;
}
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The sync logic doesn't explicitly handle the edge case where both NO_PROXY and no_proxy are provided in additionalEnv with different values. In this case, NO_PROXY takes precedence (line 460 executes), but this behavior isn't documented. Consider adding a comment explaining the precedence order, or handling this case more explicitly to make the behavior clear to future maintainers.

Copilot uses AI. Check for mistakes.
# Allow localhost traffic (for stdio MCP servers and test frameworks)
echo "[iptables] Allow localhost traffic..."
iptables -t nat -A OUTPUT -o lo -j RETURN
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The established pattern in this codebase for iptables bypass rules is to pair NAT RETURN with filter ACCEPT (see agent self-traffic bypass at lines 75-77, host gateway bypass at lines 158-161, network gateway bypass at lines 185-186). The 0.0.0.0 rule here only has NAT RETURN without a corresponding filter ACCEPT. While 0.0.0.0 isn't a typical destination address, consider following the established pattern for consistency, or add a comment explaining why the filter rule is intentionally omitted.

Suggested change
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN
# Note: 0.0.0.0 is not used as a real destination address; this NAT RETURN
# rule exists only to avoid redirecting any such traffic to Squid. A matching
# filter-table ACCEPT rule is intentionally omitted.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants