Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
61b8d2b
Initial plan
Claude Feb 13, 2026
a0546ea
fix(ci): add BASE_URL environment variables for CODEX api-proxy routing
Claude Feb 13, 2026
ebfb173
fix(ci): remove API keys from agent env when api-proxy is enabled (#803)
Claude Feb 13, 2026
074d609
fix(firewall): add api-proxy to allowed domains when enabled (#804)
Claude Feb 13, 2026
7f123a6
fix(squid): handle bare hostnames without leading dot for api-proxy (…
Claude Feb 13, 2026
e54a36d
fix(agent): enable direct api-proxy access and remove api key from en…
Claude Feb 13, 2026
b78c2c3
fix: pass ANTHROPIC_API_KEY to validation in all Claude workflows (#808)
Claude Feb 13, 2026
dc46365
fix: only enable api-proxy when API keys are provided (#810)
Claude Feb 13, 2026
3c51dea
Merge branch 'claude/enable-api-proxy-by-default' into claude/fix-git…
lpcox Feb 13, 2026
f0814da
fix: remove unknown --anthropic-api-key flag from smoke-claude workfl…
Claude Feb 13, 2026
fba8536
fix: use api-proxy IP address instead of hostname for BASE_URL (#813)
Claude Feb 13, 2026
bee5005
fix: use api-proxy IP address instead of hostname for BASE_URL (#813)…
Claude Feb 13, 2026
08540e1
fix(api-proxy): use IP address for API base URLs to avoid DNS issues
Mossaka Feb 13, 2026
b9e3b0f
Merge branch 'claude/enable-api-proxy-by-default' into claude/fix-git…
lpcox Feb 13, 2026
25f50ec
fix: exclude API keys from agent when api-proxy is enabled (#814)
Claude Feb 13, 2026
a67144a
fix(agent): use AWF_API_PROXY_IP env var for api-proxy iptables rules…
Claude Feb 13, 2026
5bd959d
Add debug logging for BASE_URL environment variables in agent contain…
Claude Feb 13, 2026
628afed
Merge branch 'claude/enable-api-proxy-by-default' into claude/fix-git…
lpcox Feb 13, 2026
64055bb
fix: simplify api-proxy iptables bypass condition (#819)
Claude Feb 13, 2026
580b370
Initial plan (#818)
Claude Feb 13, 2026
a2b8d67
fix: add api-proxy IP to squid allowlist (#820)
Claude Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/secret-digger-claude.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions .github/workflows/security-guard.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion .github/workflows/smoke-claude.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .github/workflows/smoke-codex.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 17 additions & 6 deletions containers/agent/setup-iptables.sh
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,19 @@ fi
echo "[iptables] Allow traffic to Squid proxy (${SQUID_IP}:${SQUID_PORT})..."
iptables -t nat -A OUTPUT -d "$SQUID_IP" -j RETURN

# Allow direct traffic to API proxy sidecar (bypasses Squid)
# The api-proxy container holds API keys and proxies to LLM APIs through Squid
# Bypass Squid for api-proxy when API proxy IP is configured.
# The agent needs to connect directly to api-proxy (not through Squid).
# The api-proxy then routes outbound traffic through Squid to enforce domain whitelisting.
# Architecture: agent -> api-proxy (direct) -> Squid -> internet
# Use AWF_API_PROXY_IP environment variable set by docker-manager (172.30.0.30)
if [ -n "$AWF_API_PROXY_IP" ]; then
echo "[iptables] Allow direct traffic to API proxy sidecar (${AWF_API_PROXY_IP})..."
iptables -t nat -A OUTPUT -d "$AWF_API_PROXY_IP" -j RETURN
if is_valid_ipv4 "$AWF_API_PROXY_IP"; then
echo "[iptables] Allow direct traffic to api-proxy (${AWF_API_PROXY_IP}) - bypassing Squid..."
# NAT: skip DNAT to Squid for all traffic to api-proxy
iptables -t nat -A OUTPUT -d "$AWF_API_PROXY_IP" -j RETURN
else
echo "[iptables] WARNING: AWF_API_PROXY_IP has invalid format '${AWF_API_PROXY_IP}', skipping api-proxy bypass"
fi
fi

# Bypass Squid for host.docker.internal when host access is enabled.
Expand Down Expand Up @@ -270,9 +278,12 @@ iptables -A OUTPUT -p tcp -d 127.0.0.11 --dport 53 -j ACCEPT
# Allow traffic to Squid proxy (after NAT redirection)
iptables -A OUTPUT -p tcp -d "$SQUID_IP" -j ACCEPT

# Allow traffic to API proxy sidecar (ports 10000/10001)
# Allow traffic to API proxy sidecar (ports 10000 for OpenAI, 10001 for Anthropic)
# Must be added before the final DROP rule
if [ -n "$AWF_API_PROXY_IP" ]; then
iptables -A OUTPUT -p tcp -d "$AWF_API_PROXY_IP" -j ACCEPT
echo "[iptables] Allow traffic to api-proxy (${AWF_API_PROXY_IP}) ports 10000, 10001..."
iptables -A OUTPUT -p tcp -d "$AWF_API_PROXY_IP" --dport 10000 -j ACCEPT
iptables -A OUTPUT -p tcp -d "$AWF_API_PROXY_IP" --dport 10001 -j ACCEPT
fi

# Drop all other TCP traffic (default deny policy)
Expand Down
2 changes: 1 addition & 1 deletion containers/api-proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Node.js-based API proxy that keeps LLM API credentials isolated from the agent c

```
Agent Container (172.30.0.20)
↓ HTTP request to api-proxy:10000
↓ HTTP request to 172.30.0.30:10000
API Proxy Sidecar (172.30.0.30)
↓ Injects Authorization header
↓ Routes via HTTP_PROXY (172.30.0.10:3128)
Expand Down
16 changes: 8 additions & 8 deletions docs/api-proxy-sidecar.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ When enabled, the API proxy sidecar:
│ │ │ Agent Container │ │
│ │ │ 172.30.0.20 │ │
│ │ │ OPENAI_BASE_URL= │ │
│ │ │ http://api-proxy:10000 │────┘
│ │ │ http://172.30.0.30:10000 │────┘
│ │ │ ANTHROPIC_BASE_URL= │
│ │ │ http://api-proxy:10001
│ │ │ http://172.30.0.30:10001 │
│ │ └──────────────────────────────┘
│ │
└─────────┼─────────────────────────────────────┘
Expand All @@ -38,7 +38,7 @@ When enabled, the API proxy sidecar:
```

**Traffic Flow:**
1. Agent makes request to `api-proxy:10000` or `api-proxy:10001`
1. Agent makes request to `172.30.0.30:10000` or `172.30.0.30:10001`
2. API proxy injects authentication headers
3. API proxy routes through Squid via HTTP_PROXY/HTTPS_PROXY
4. Squid enforces domain whitelist (only allowed domains pass)
Expand Down Expand Up @@ -82,7 +82,7 @@ awf --enable-api-proxy \
-- npx @openai/codex -p "write a hello world function"
```

The agent container will automatically use `http://api-proxy:10000` as the base URL.
The agent container will automatically use `http://172.30.0.30:10000` as the base URL.

### Claude Code Example

Expand All @@ -94,7 +94,7 @@ awf --enable-api-proxy \
-- claude-code "write a hello world function"
```

The agent container will automatically use `http://api-proxy:10001` as the base URL.
The agent container will automatically use `http://172.30.0.30:10001` as the base URL.

### Both Providers

Expand All @@ -113,8 +113,8 @@ When API keys are provided, the sidecar sets these environment variables in the

| Variable | Value | When Set | Description |
|----------|-------|----------|-------------|
| `OPENAI_BASE_URL` | `http://api-proxy:10000` | When `OPENAI_API_KEY` is provided | OpenAI API proxy endpoint |
| `ANTHROPIC_BASE_URL` | `http://api-proxy:10001` | When `ANTHROPIC_API_KEY` is provided | Anthropic API proxy endpoint |
| `OPENAI_BASE_URL` | `http://172.30.0.30:10000` | When `OPENAI_API_KEY` is provided | OpenAI API proxy endpoint |
| `ANTHROPIC_BASE_URL` | `http://172.30.0.30:10001` | When `ANTHROPIC_API_KEY` is provided | Anthropic API proxy endpoint |

These are standard environment variables recognized by:
- OpenAI Python SDK
Expand Down Expand Up @@ -164,7 +164,7 @@ When API keys are present (or `--enable-api-proxy` is explicitly set):

```
Agent Code
↓ (makes HTTP request to api-proxy:10000)
↓ (makes HTTP request to 172.30.0.30:10000)
Node.js API Proxy
↓ (injects Authorization: Bearer $OPENAI_API_KEY)
↓ (routes via HTTP_PROXY to Squid)
Expand Down
13 changes: 13 additions & 0 deletions scripts/ci/postprocess-smoke-workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ const shallowDepthRegex = /^(\s+)depth: 1\n/gm;
// instead of pre-built GHCR images that may be stale.
const imageTagRegex = /--image-tag\s+[0-9.]+\s+--skip-pull/g;

// Remove ANTHROPIC_API_KEY from agent environment (security: API key should only be in api-proxy sidecar)
// The API key is passed to the api-proxy container by awf CLI when --enable-api-proxy is set.
// Match the env key + value line with any indentation
const anthropicApiKeyRegex = /^(\s*)ANTHROPIC_API_KEY:\s+\$\{\{\s*secrets\.ANTHROPIC_API_KEY\s*\}\}\n/gm;

for (const workflowPath of workflowPaths) {
let content = fs.readFileSync(workflowPath, 'utf-8');
let modified = false;
Expand Down Expand Up @@ -139,6 +144,14 @@ for (const workflowPath of workflowPaths) {
console.log(` Replaced ${imageTagMatches.length} --image-tag/--skip-pull with --build-local`);
}

// Remove ANTHROPIC_API_KEY from agent environment (security issue: key should only be in api-proxy)
const apiKeyMatches = content.match(anthropicApiKeyRegex);
if (apiKeyMatches) {
content = content.replace(anthropicApiKeyRegex, '');
modified = true;
console.log(` Removed ${apiKeyMatches.length} ANTHROPIC_API_KEY env var(s) from agent`);
}

if (modified) {
fs.writeFileSync(workflowPath, content);
console.log(`Updated ${workflowPath}`);
Expand Down
Loading
Loading