Skip to content

Commit 6b82715

Browse files
Claudelpcox
andauthored
feat(api-proxy): centralize port configuration in types.ts (#955)
* Initial plan * feat(api-proxy): centralize port configuration in types.ts Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * 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> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
1 parent c419d69 commit 6b82715

5 files changed

Lines changed: 114 additions & 21 deletions

File tree

containers/api-proxy/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ RUN addgroup -S apiproxy && adduser -S apiproxy -G apiproxy
2424
USER apiproxy
2525

2626
# Expose ports
27-
# 10000 - OpenAI API proxy
27+
# 10000 - OpenAI API proxy (also serves as health check endpoint)
2828
# 10001 - Anthropic API proxy
29-
EXPOSE 10000 10001
29+
# 10002 - GitHub Copilot API proxy
30+
EXPOSE 10000 10001 10002
3031

3132
# Redirect stdout/stderr to log file for persistence
3233
# Use shell form to enable redirection and tee for both file and console

docs/api-proxy-sidecar.md

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,68 @@ sudo awf --enable-api-proxy \
103103

104104
## Environment variables
105105

106-
When API keys are provided, AWF sets these environment variables in the agent container:
106+
AWF manages environment variables differently across the three containers (squid, api-proxy, agent) to ensure secure credential isolation.
107+
108+
### Squid container
109+
110+
The Squid proxy container runs with minimal environment variables:
111+
112+
| Variable | Value | Description |
113+
|----------|-------|-------------|
114+
| `HTTP_PROXY` | Not set | Squid is the proxy, not a client |
115+
| `HTTPS_PROXY` | Not set | Squid is the proxy, not a client |
116+
117+
### API proxy container
118+
119+
The API proxy sidecar receives **real credentials** and routing configuration:
120+
121+
| Variable | Value | When set | Description |
122+
|----------|-------|----------|-------------|
123+
| `OPENAI_API_KEY` | Real API key | `--enable-api-proxy` and env set | OpenAI API key (injected into requests) |
124+
| `ANTHROPIC_API_KEY` | Real API key | `--enable-api-proxy` and env set | Anthropic API key (injected into requests) |
125+
| `COPILOT_GITHUB_TOKEN` | Real token | `--enable-api-proxy` and env set | GitHub Copilot token (injected into requests) |
126+
| `HTTP_PROXY` | `http://172.30.0.10:3128` | Always | Routes through Squid for domain filtering |
127+
| `HTTPS_PROXY` | `http://172.30.0.10:3128` | Always | Routes through Squid for domain filtering |
128+
129+
:::danger[Real credentials in api-proxy]
130+
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.
131+
:::
132+
133+
### Agent container
134+
135+
The agent container receives **redacted placeholders** and proxy URLs:
107136

108137
| Variable | Value | When set | Description |
109138
|----------|-------|----------|-------------|
110-
| `OPENAI_BASE_URL` | `http://172.30.0.30:10000/v1` | `OPENAI_API_KEY` is set | OpenAI API proxy endpoint |
111-
| `ANTHROPIC_BASE_URL` | `http://172.30.0.30:10001` | `ANTHROPIC_API_KEY` is set | Anthropic API proxy endpoint |
139+
| `OPENAI_BASE_URL` | `http://172.30.0.30:10000/v1` | `OPENAI_API_KEY` provided to host | Redirects OpenAI SDK to proxy |
140+
| `ANTHROPIC_BASE_URL` | `http://172.30.0.30:10001` | `ANTHROPIC_API_KEY` provided to host | Redirects Anthropic SDK to proxy |
141+
| `ANTHROPIC_AUTH_TOKEN` | `placeholder-token-for-credential-isolation` | `ANTHROPIC_API_KEY` provided to host | Placeholder token (real auth via BASE_URL) |
142+
| `CLAUDE_CODE_API_KEY_HELPER` | `/usr/local/bin/get-claude-key.sh` | `ANTHROPIC_API_KEY` provided to host | Helper script for Claude Code CLI |
143+
| `COPILOT_API_URL` | `http://172.30.0.30:10002` | `COPILOT_GITHUB_TOKEN` provided to host | Redirects Copilot CLI to proxy |
144+
| `COPILOT_TOKEN` | `placeholder-token-for-credential-isolation` | `COPILOT_GITHUB_TOKEN` provided to host | Placeholder token (real auth via API_URL) |
145+
| `COPILOT_GITHUB_TOKEN` | `placeholder-token-for-credential-isolation` | `COPILOT_GITHUB_TOKEN` provided to host | Placeholder token protected by one-shot-token |
146+
| `OPENAI_API_KEY` | Not set | `--enable-api-proxy` | Excluded from agent (held in api-proxy) |
147+
| `ANTHROPIC_API_KEY` | Not set | `--enable-api-proxy` | Excluded from agent (held in api-proxy) |
148+
| `HTTP_PROXY` | `http://172.30.0.10:3128` | Always | Routes through Squid proxy |
149+
| `HTTPS_PROXY` | `http://172.30.0.10:3128` | Always | Routes through Squid proxy |
150+
| `NO_PROXY` | `localhost,127.0.0.1,172.30.0.30` | `--enable-api-proxy` | Bypass proxy for localhost and api-proxy |
151+
| `AWF_API_PROXY_IP` | `172.30.0.30` | `--enable-api-proxy` | Used by iptables setup script |
152+
| `AWF_ONE_SHOT_TOKENS` | `COPILOT_GITHUB_TOKEN,GITHUB_TOKEN,...` | Always | Tokens protected by one-shot-token library |
153+
154+
:::tip[Placeholder tokens]
155+
Token variables in the agent are set to `placeholder-token-for-credential-isolation` instead of real values. This ensures:
156+
- Agent code cannot exfiltrate credentials
157+
- CLI tools that check for token presence still work
158+
- Real authentication happens via the `*_BASE_URL` or `*_API_URL` environment variables
159+
- The one-shot-token library protects placeholder values from being read more than once
160+
:::
112161

113-
These are standard environment variables recognized by:
162+
These environment variables are recognized by:
114163
- OpenAI Python SDK (`openai`)
115164
- OpenAI Node.js SDK (`openai`)
116165
- Anthropic Python SDK (`anthropic`)
117166
- Anthropic TypeScript SDK (`@anthropic-ai/sdk`)
167+
- GitHub Copilot CLI (`@github/copilot`)
118168
- Codex CLI
119169
- Claude Code CLI
120170

@@ -218,7 +268,7 @@ The sidecar container:
218268
- **Image**: `ghcr.io/github/gh-aw-firewall/api-proxy:latest`
219269
- **Base**: `node:22-alpine`
220270
- **Network**: `awf-net` at `172.30.0.30`
221-
- **Ports**: 10000 (OpenAI), 10001 (Anthropic)
271+
- **Ports**: 10000 (OpenAI), 10001 (Anthropic), 10002 (GitHub Copilot)
222272
- **Proxy**: Routes via Squid at `http://172.30.0.10:3128`
223273

224274
### Health check

src/docker-manager.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as path from 'path';
33
import * as os from 'os';
44
import * as yaml from 'js-yaml';
55
import execa from 'execa';
6-
import { DockerComposeConfig, WrapperConfig, BlockedTarget } from './types';
6+
import { DockerComposeConfig, WrapperConfig, BlockedTarget, API_PROXY_PORTS, API_PROXY_HEALTH_PORT } from './types';
77
import { logger } from './logger';
88
import { generateSquidConfig } from './squid-config';
99
import { generateSessionCa, initSslDb, CaFiles, parseUrlPatterns } from './ssl-bump';
@@ -968,7 +968,7 @@ export function generateDockerCompose(
968968
HTTPS_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`,
969969
},
970970
healthcheck: {
971-
test: ['CMD', 'curl', '-f', 'http://localhost:10000/health'],
971+
test: ['CMD', 'curl', '-f', `http://localhost:${API_PROXY_HEALTH_PORT}/health`],
972972
interval: '5s',
973973
timeout: '3s',
974974
retries: 5,
@@ -1009,12 +1009,12 @@ export function generateDockerCompose(
10091009
// container names in chroot mode
10101010
environment.AWF_API_PROXY_IP = networkConfig.proxyIp;
10111011
if (config.openaiApiKey) {
1012-
environment.OPENAI_BASE_URL = `http://${networkConfig.proxyIp}:10000/v1`;
1013-
logger.debug(`OpenAI API will be proxied through sidecar at http://${networkConfig.proxyIp}:10000/v1`);
1012+
environment.OPENAI_BASE_URL = `http://${networkConfig.proxyIp}:${API_PROXY_PORTS.OPENAI}/v1`;
1013+
logger.debug(`OpenAI API will be proxied through sidecar at http://${networkConfig.proxyIp}:${API_PROXY_PORTS.OPENAI}/v1`);
10141014
}
10151015
if (config.anthropicApiKey) {
1016-
environment.ANTHROPIC_BASE_URL = `http://${networkConfig.proxyIp}:10001`;
1017-
logger.debug(`Anthropic API will be proxied through sidecar at http://${networkConfig.proxyIp}:10001`);
1016+
environment.ANTHROPIC_BASE_URL = `http://${networkConfig.proxyIp}:${API_PROXY_PORTS.ANTHROPIC}`;
1017+
logger.debug(`Anthropic API will be proxied through sidecar at http://${networkConfig.proxyIp}:${API_PROXY_PORTS.ANTHROPIC}`);
10181018

10191019
// Set placeholder token for Claude Code CLI compatibility
10201020
// Real authentication happens via ANTHROPIC_BASE_URL pointing to api-proxy
@@ -1027,8 +1027,8 @@ export function generateDockerCompose(
10271027
logger.debug('Claude Code API key helper configured: /usr/local/bin/get-claude-key.sh');
10281028
}
10291029
if (config.copilotGithubToken) {
1030-
environment.COPILOT_API_URL = `http://${networkConfig.proxyIp}:10002`;
1031-
logger.debug(`GitHub Copilot API will be proxied through sidecar at http://${networkConfig.proxyIp}:10002`);
1030+
environment.COPILOT_API_URL = `http://${networkConfig.proxyIp}:${API_PROXY_PORTS.COPILOT}`;
1031+
logger.debug(`GitHub Copilot API will be proxied through sidecar at http://${networkConfig.proxyIp}:${API_PROXY_PORTS.COPILOT}`);
10321032

10331033
// Set placeholder token for GitHub Copilot CLI compatibility
10341034
// Real authentication happens via COPILOT_API_URL pointing to api-proxy

src/host-iptables.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import execa from 'execa';
22
import { logger } from './logger';
33
import { isIPv6 } from 'net';
4+
import { API_PROXY_PORTS } from './types';
45

56
const NETWORK_NAME = 'awf-net';
67
const CHAIN_NAME = 'FW_WRAPPER';
@@ -442,13 +443,15 @@ export async function setupHostIptables(squidIp: string, squidPort: number, dnsS
442443
]);
443444

444445
// 5b. Allow traffic to API proxy sidecar (when enabled)
445-
// Only allow ports 10000 (OpenAI) and 10001 (Anthropic) — nothing else.
446+
// Allow all API proxy ports (OpenAI, Anthropic, GitHub Copilot).
446447
// The sidecar itself routes through Squid, so domain whitelisting is still enforced.
447448
if (apiProxyIp) {
448-
logger.debug(`Allowing traffic to API proxy sidecar at ${apiProxyIp}:10000-10001`);
449+
const minPort = Math.min(API_PROXY_PORTS.OPENAI, API_PROXY_PORTS.ANTHROPIC, API_PROXY_PORTS.COPILOT);
450+
const maxPort = Math.max(API_PROXY_PORTS.OPENAI, API_PROXY_PORTS.ANTHROPIC, API_PROXY_PORTS.COPILOT);
451+
logger.debug(`Allowing traffic to API proxy sidecar at ${apiProxyIp}:${minPort}-${maxPort}`);
449452
await execa('iptables', [
450453
'-t', 'filter', '-A', CHAIN_NAME,
451-
'-p', 'tcp', '-d', apiProxyIp, '--dport', '10000:10001',
454+
'-p', 'tcp', '-d', apiProxyIp, '--dport', `${minPort}:${maxPort}`,
452455
'-j', 'ACCEPT',
453456
]);
454457
}

src/types.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,44 @@
22
* Configuration types for the agentic workflow firewall
33
*/
44

5+
/**
6+
* API Proxy port configuration
7+
*
8+
* These ports are used by the api-proxy sidecar container to expose
9+
* authentication-injecting proxies for different LLM providers.
10+
*
11+
* All ports must be allowed in:
12+
* - containers/api-proxy/Dockerfile (EXPOSE directive)
13+
* - src/host-iptables.ts (firewall rules)
14+
* - containers/agent/setup-iptables.sh (NAT rules)
15+
*/
16+
export const API_PROXY_PORTS = {
17+
/**
18+
* OpenAI API proxy port
19+
* Also serves as the health check endpoint for Docker healthcheck
20+
* @see containers/api-proxy/server.js
21+
*/
22+
OPENAI: 10000,
23+
24+
/**
25+
* Anthropic (Claude) API proxy port
26+
* @see containers/api-proxy/server.js
27+
*/
28+
ANTHROPIC: 10001,
29+
30+
/**
31+
* GitHub Copilot API proxy port
32+
* @see containers/api-proxy/server.js
33+
*/
34+
COPILOT: 10002,
35+
} as const;
36+
37+
/**
38+
* Health check port for the API proxy sidecar
39+
* Always uses the OpenAI port (10000) for Docker healthcheck
40+
*/
41+
export const API_PROXY_HEALTH_PORT = API_PROXY_PORTS.OPENAI;
42+
543
/**
644
* Main configuration interface for the firewall wrapper
745
*
@@ -391,9 +429,9 @@ export interface WrapperConfig {
391429
* - Proxies requests to LLM providers
392430
*
393431
* The sidecar exposes three endpoints accessible from the agent container:
394-
* - http://api-proxy:10000 - OpenAI API proxy (for Codex)
395-
* - http://api-proxy:10001 - Anthropic API proxy (for Claude)
396-
* - http://api-proxy:10002 - GitHub Copilot API proxy
432+
* - http://api-proxy:10000 - OpenAI API proxy (for Codex) {@link API_PROXY_PORTS.OPENAI}
433+
* - http://api-proxy:10001 - Anthropic API proxy (for Claude) {@link API_PROXY_PORTS.ANTHROPIC}
434+
* - http://api-proxy:10002 - GitHub Copilot API proxy {@link API_PROXY_PORTS.COPILOT}
397435
*
398436
* When the corresponding API key is provided, the following environment
399437
* variables are set in the agent container:
@@ -416,6 +454,7 @@ export interface WrapperConfig {
416454
* export COPILOT_GITHUB_TOKEN="ghp_..."
417455
* awf --enable-api-proxy --allow-domains api.openai.com,api.anthropic.com,api.githubcopilot.com -- command
418456
* ```
457+
* @see API_PROXY_PORTS for port configuration
419458
*/
420459
enableApiProxy?: boolean;
421460

0 commit comments

Comments
 (0)