From 6192306cc3b3791d7127a7346f84ea41267a0c2a Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:15:27 +0000 Subject: [PATCH 1/3] Initial plan From 0be7495bbddd9ce38052a469f1991315869b8710 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:19:53 +0000 Subject: [PATCH 2/3] feat(api-proxy): centralize port configuration in types.ts Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/api-proxy/Dockerfile | 5 ++-- docs/api-proxy-sidecar.md | 2 +- src/docker-manager.ts | 16 ++++++------ src/host-iptables.ts | 9 ++++--- src/types.ts | 45 ++++++++++++++++++++++++++++++--- 5 files changed, 60 insertions(+), 17 deletions(-) diff --git a/containers/api-proxy/Dockerfile b/containers/api-proxy/Dockerfile index 3e8d8d85..505ec49c 100644 --- a/containers/api-proxy/Dockerfile +++ b/containers/api-proxy/Dockerfile @@ -24,9 +24,10 @@ RUN addgroup -S apiproxy && adduser -S apiproxy -G apiproxy USER apiproxy # Expose ports -# 10000 - OpenAI API proxy +# 10000 - OpenAI API proxy (also serves as health check endpoint) # 10001 - Anthropic API proxy -EXPOSE 10000 10001 +# 10002 - GitHub Copilot API proxy +EXPOSE 10000 10001 10002 # Redirect stdout/stderr to log file for persistence # Use shell form to enable redirection and tee for both file and console diff --git a/docs/api-proxy-sidecar.md b/docs/api-proxy-sidecar.md index 0591b6db..f4adf0fc 100644 --- a/docs/api-proxy-sidecar.md +++ b/docs/api-proxy-sidecar.md @@ -218,7 +218,7 @@ The sidecar container: - **Image**: `ghcr.io/github/gh-aw-firewall/api-proxy:latest` - **Base**: `node:22-alpine` - **Network**: `awf-net` at `172.30.0.30` -- **Ports**: 10000 (OpenAI), 10001 (Anthropic) +- **Ports**: 10000 (OpenAI), 10001 (Anthropic), 10002 (GitHub Copilot) - **Proxy**: Routes via Squid at `http://172.30.0.10:3128` ### Health check diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 8705e707..299b4128 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as os from 'os'; import * as yaml from 'js-yaml'; import execa from 'execa'; -import { DockerComposeConfig, WrapperConfig, BlockedTarget } from './types'; +import { DockerComposeConfig, WrapperConfig, BlockedTarget, API_PROXY_PORTS, API_PROXY_HEALTH_PORT } from './types'; import { logger } from './logger'; import { generateSquidConfig } from './squid-config'; import { generateSessionCa, initSslDb, CaFiles, parseUrlPatterns } from './ssl-bump'; @@ -968,7 +968,7 @@ export function generateDockerCompose( HTTPS_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`, }, healthcheck: { - test: ['CMD', 'curl', '-f', 'http://localhost:10000/health'], + test: ['CMD', 'curl', '-f', `http://localhost:${API_PROXY_HEALTH_PORT}/health`], interval: '5s', timeout: '3s', retries: 5, @@ -1009,12 +1009,12 @@ export function generateDockerCompose( // container names in chroot mode environment.AWF_API_PROXY_IP = networkConfig.proxyIp; if (config.openaiApiKey) { - environment.OPENAI_BASE_URL = `http://${networkConfig.proxyIp}:10000/v1`; - logger.debug(`OpenAI API will be proxied through sidecar at http://${networkConfig.proxyIp}:10000/v1`); + environment.OPENAI_BASE_URL = `http://${networkConfig.proxyIp}:${API_PROXY_PORTS.OPENAI}/v1`; + logger.debug(`OpenAI API will be proxied through sidecar at http://${networkConfig.proxyIp}:${API_PROXY_PORTS.OPENAI}/v1`); } if (config.anthropicApiKey) { - environment.ANTHROPIC_BASE_URL = `http://${networkConfig.proxyIp}:10001`; - logger.debug(`Anthropic API will be proxied through sidecar at http://${networkConfig.proxyIp}:10001`); + environment.ANTHROPIC_BASE_URL = `http://${networkConfig.proxyIp}:${API_PROXY_PORTS.ANTHROPIC}`; + logger.debug(`Anthropic API will be proxied through sidecar at http://${networkConfig.proxyIp}:${API_PROXY_PORTS.ANTHROPIC}`); // Set placeholder token for Claude Code CLI compatibility // Real authentication happens via ANTHROPIC_BASE_URL pointing to api-proxy @@ -1027,8 +1027,8 @@ export function generateDockerCompose( logger.debug('Claude Code API key helper configured: /usr/local/bin/get-claude-key.sh'); } if (config.copilotGithubToken) { - environment.COPILOT_API_URL = `http://${networkConfig.proxyIp}:10002`; - logger.debug(`GitHub Copilot API will be proxied through sidecar at http://${networkConfig.proxyIp}:10002`); + environment.COPILOT_API_URL = `http://${networkConfig.proxyIp}:${API_PROXY_PORTS.COPILOT}`; + logger.debug(`GitHub Copilot API will be proxied through sidecar at http://${networkConfig.proxyIp}:${API_PROXY_PORTS.COPILOT}`); // Set placeholder token for GitHub Copilot CLI compatibility // Real authentication happens via COPILOT_API_URL pointing to api-proxy diff --git a/src/host-iptables.ts b/src/host-iptables.ts index 4a8a7c68..5f130b73 100644 --- a/src/host-iptables.ts +++ b/src/host-iptables.ts @@ -1,6 +1,7 @@ import execa from 'execa'; import { logger } from './logger'; import { isIPv6 } from 'net'; +import { API_PROXY_PORTS } from './types'; const NETWORK_NAME = 'awf-net'; const CHAIN_NAME = 'FW_WRAPPER'; @@ -442,13 +443,15 @@ export async function setupHostIptables(squidIp: string, squidPort: number, dnsS ]); // 5b. Allow traffic to API proxy sidecar (when enabled) - // Only allow ports 10000 (OpenAI) and 10001 (Anthropic) — nothing else. + // Allow all API proxy ports (OpenAI, Anthropic, GitHub Copilot). // The sidecar itself routes through Squid, so domain whitelisting is still enforced. if (apiProxyIp) { - logger.debug(`Allowing traffic to API proxy sidecar at ${apiProxyIp}:10000-10001`); + const minPort = Math.min(API_PROXY_PORTS.OPENAI, API_PROXY_PORTS.ANTHROPIC, API_PROXY_PORTS.COPILOT); + const maxPort = Math.max(API_PROXY_PORTS.OPENAI, API_PROXY_PORTS.ANTHROPIC, API_PROXY_PORTS.COPILOT); + logger.debug(`Allowing traffic to API proxy sidecar at ${apiProxyIp}:${minPort}-${maxPort}`); await execa('iptables', [ '-t', 'filter', '-A', CHAIN_NAME, - '-p', 'tcp', '-d', apiProxyIp, '--dport', '10000:10001', + '-p', 'tcp', '-d', apiProxyIp, '--dport', `${minPort}:${maxPort}`, '-j', 'ACCEPT', ]); } diff --git a/src/types.ts b/src/types.ts index affd0a42..4e95b517 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,6 +2,44 @@ * Configuration types for the agentic workflow firewall */ +/** + * API Proxy port configuration + * + * These ports are used by the api-proxy sidecar container to expose + * authentication-injecting proxies for different LLM providers. + * + * All ports must be allowed in: + * - containers/api-proxy/Dockerfile (EXPOSE directive) + * - src/host-iptables.ts (firewall rules) + * - containers/agent/setup-iptables.sh (NAT rules) + */ +export const API_PROXY_PORTS = { + /** + * OpenAI API proxy port + * Also serves as the health check endpoint for Docker healthcheck + * @see containers/api-proxy/server.js + */ + OPENAI: 10000, + + /** + * Anthropic (Claude) API proxy port + * @see containers/api-proxy/server.js + */ + ANTHROPIC: 10001, + + /** + * GitHub Copilot API proxy port + * @see containers/api-proxy/server.js + */ + COPILOT: 10002, +} as const; + +/** + * Health check port for the API proxy sidecar + * Always uses the OpenAI port (10000) for Docker healthcheck + */ +export const API_PROXY_HEALTH_PORT = API_PROXY_PORTS.OPENAI; + /** * Main configuration interface for the firewall wrapper * @@ -391,9 +429,9 @@ export interface WrapperConfig { * - Proxies requests to LLM providers * * The sidecar exposes three endpoints accessible from the agent container: - * - http://api-proxy:10000 - OpenAI API proxy (for Codex) - * - http://api-proxy:10001 - Anthropic API proxy (for Claude) - * - http://api-proxy:10002 - GitHub Copilot API proxy + * - http://api-proxy:10000 - OpenAI API proxy (for Codex) {@link API_PROXY_PORTS.OPENAI} + * - http://api-proxy:10001 - Anthropic API proxy (for Claude) {@link API_PROXY_PORTS.ANTHROPIC} + * - http://api-proxy:10002 - GitHub Copilot API proxy {@link API_PROXY_PORTS.COPILOT} * * When the corresponding API key is provided, the following environment * variables are set in the agent container: @@ -416,6 +454,7 @@ export interface WrapperConfig { * export COPILOT_GITHUB_TOKEN="ghp_..." * awf --enable-api-proxy --allow-domains api.openai.com,api.anthropic.com,api.githubcopilot.com -- command * ``` + * @see API_PROXY_PORTS for port configuration */ enableApiProxy?: boolean; From 2c56fc783a6bfbc71e8d0863a498e5e6438f2a14 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:25:25 +0000 Subject: [PATCH 3/3] docs: document environment variables for all three containers Add comprehensive environment variable documentation for squid, api-proxy, and agent containers. Clearly document which token variables are redacted with placeholder values ('placeholder-token-for-credential-isolation') in the agent container for credential isolation. Key additions: - Separate tables for each container's environment variables - Document real credentials in api-proxy vs placeholders in agent - Explain one-shot-token protection mechanism - Include all three API proxy endpoints (OpenAI, Anthropic, Copilot) Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- docs/api-proxy-sidecar.md | 58 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/docs/api-proxy-sidecar.md b/docs/api-proxy-sidecar.md index f4adf0fc..dc9b5c81 100644 --- a/docs/api-proxy-sidecar.md +++ b/docs/api-proxy-sidecar.md @@ -103,18 +103,68 @@ sudo awf --enable-api-proxy \ ## Environment variables -When API keys are provided, AWF sets these environment variables in the agent container: +AWF manages environment variables differently across the three containers (squid, api-proxy, agent) to ensure secure credential isolation. + +### Squid container + +The Squid proxy container runs with minimal environment variables: + +| Variable | Value | Description | +|----------|-------|-------------| +| `HTTP_PROXY` | Not set | Squid is the proxy, not a client | +| `HTTPS_PROXY` | Not set | Squid is the proxy, not a client | + +### API proxy container + +The API proxy sidecar receives **real credentials** and routing configuration: + +| Variable | Value | When set | Description | +|----------|-------|----------|-------------| +| `OPENAI_API_KEY` | Real API key | `--enable-api-proxy` and env set | OpenAI API key (injected into requests) | +| `ANTHROPIC_API_KEY` | Real API key | `--enable-api-proxy` and env set | Anthropic API key (injected into requests) | +| `COPILOT_GITHUB_TOKEN` | Real token | `--enable-api-proxy` and env set | GitHub Copilot token (injected into requests) | +| `HTTP_PROXY` | `http://172.30.0.10:3128` | Always | Routes through Squid for domain filtering | +| `HTTPS_PROXY` | `http://172.30.0.10:3128` | Always | Routes through Squid for domain filtering | + +:::danger[Real credentials in api-proxy] +The api-proxy container holds **real, unredacted credentials**. These are used to authenticate requests to LLM providers. This container is isolated from the agent and has all capabilities dropped for security. +::: + +### Agent container + +The agent container receives **redacted placeholders** and proxy URLs: | Variable | Value | When set | Description | |----------|-------|----------|-------------| -| `OPENAI_BASE_URL` | `http://172.30.0.30:10000/v1` | `OPENAI_API_KEY` is set | OpenAI API proxy endpoint | -| `ANTHROPIC_BASE_URL` | `http://172.30.0.30:10001` | `ANTHROPIC_API_KEY` is set | Anthropic API proxy endpoint | +| `OPENAI_BASE_URL` | `http://172.30.0.30:10000/v1` | `OPENAI_API_KEY` provided to host | Redirects OpenAI SDK to proxy | +| `ANTHROPIC_BASE_URL` | `http://172.30.0.30:10001` | `ANTHROPIC_API_KEY` provided to host | Redirects Anthropic SDK to proxy | +| `ANTHROPIC_AUTH_TOKEN` | `placeholder-token-for-credential-isolation` | `ANTHROPIC_API_KEY` provided to host | Placeholder token (real auth via BASE_URL) | +| `CLAUDE_CODE_API_KEY_HELPER` | `/usr/local/bin/get-claude-key.sh` | `ANTHROPIC_API_KEY` provided to host | Helper script for Claude Code CLI | +| `COPILOT_API_URL` | `http://172.30.0.30:10002` | `COPILOT_GITHUB_TOKEN` provided to host | Redirects Copilot CLI to proxy | +| `COPILOT_TOKEN` | `placeholder-token-for-credential-isolation` | `COPILOT_GITHUB_TOKEN` provided to host | Placeholder token (real auth via API_URL) | +| `COPILOT_GITHUB_TOKEN` | `placeholder-token-for-credential-isolation` | `COPILOT_GITHUB_TOKEN` provided to host | Placeholder token protected by one-shot-token | +| `OPENAI_API_KEY` | Not set | `--enable-api-proxy` | Excluded from agent (held in api-proxy) | +| `ANTHROPIC_API_KEY` | Not set | `--enable-api-proxy` | Excluded from agent (held in api-proxy) | +| `HTTP_PROXY` | `http://172.30.0.10:3128` | Always | Routes through Squid proxy | +| `HTTPS_PROXY` | `http://172.30.0.10:3128` | Always | Routes through Squid proxy | +| `NO_PROXY` | `localhost,127.0.0.1,172.30.0.30` | `--enable-api-proxy` | Bypass proxy for localhost and api-proxy | +| `AWF_API_PROXY_IP` | `172.30.0.30` | `--enable-api-proxy` | Used by iptables setup script | +| `AWF_ONE_SHOT_TOKENS` | `COPILOT_GITHUB_TOKEN,GITHUB_TOKEN,...` | Always | Tokens protected by one-shot-token library | + +:::tip[Placeholder tokens] +Token variables in the agent are set to `placeholder-token-for-credential-isolation` instead of real values. This ensures: +- Agent code cannot exfiltrate credentials +- CLI tools that check for token presence still work +- Real authentication happens via the `*_BASE_URL` or `*_API_URL` environment variables +- The one-shot-token library protects placeholder values from being read more than once +::: -These are standard environment variables recognized by: +These environment variables are recognized by: - OpenAI Python SDK (`openai`) - OpenAI Node.js SDK (`openai`) - Anthropic Python SDK (`anthropic`) - Anthropic TypeScript SDK (`@anthropic-ai/sdk`) +- GitHub Copilot CLI (`@github/copilot`) - Codex CLI - Claude Code CLI