Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fb78d25
Initial plan
Claude Feb 14, 2026
d671964
docs: create comprehensive authentication architecture guide
Claude Feb 14, 2026
9dc62c8
docs: link to authentication architecture guide
Claude Feb 14, 2026
0f73e98
docs: clarify that Codex/OpenAI uses same credential isolation as Claude
Claude Feb 14, 2026
abedf83
fix: exclude OpenAI/Codex keys from agent when api-proxy enabled
Claude Feb 14, 2026
85c7721
fix: enable api-proxy for smoke-codex workflow
Claude Feb 14, 2026
1ec1911
feat: add api-proxy health checks for credential isolation
Claude Feb 14, 2026
8c98f95
fix: restore API keys to workflow env for AWF CLI
Claude Feb 14, 2026
eb5b911
feat: add claude code api key helper for credential isolation
Claude Feb 14, 2026
42e6e72
feat: add logging to api key helper and api-proxy for observability
Claude Feb 14, 2026
1779ee7
fix: add CLAUDE_CODE_API_KEY_HELPER env var for helper detection
Claude Feb 14, 2026
4c0e1c5
fix: unset HTTP_PROXY for claude to use api-proxy as gateway
Claude Feb 14, 2026
f62c148
feat(logs): add api-proxy log persistence and preservation
Claude Feb 14, 2026
79b23be
fix: add claude code api key helper validation and ttl config
Claude Feb 14, 2026
a412f8e
fix: make claude code api key helper validation conditional
Claude Feb 14, 2026
eefeff9
feat: add post-step to display final claude code config
Claude Feb 14, 2026
886168a
fix: set CLAUDE_CODE_API_KEY_HELPER env var for credential isolation …
Claude Feb 14, 2026
055fee3
fix: change claude code config path to ~/.claude.json
Claude Feb 14, 2026
7c1e377
fix: create ~/.claude.json with apiKeyHelper before claude starts
Claude Feb 14, 2026
2144f86
fix: mount ~/.claude.json for chroot mode accessibility
Claude Feb 14, 2026
b15ce3a
fix: handle empty ~/.claude.json created by docker-manager
Claude Feb 14, 2026
a9ea1c0
fix: use 0o666 mode for ~/.claude.json to fix permissions (#852)
Claude Feb 14, 2026
7a05809
fix: use 666 permissions for ~/.claude.json to allow host user writes
Claude Feb 14, 2026
0d8cd34
fix(ci): add api-proxy logs to smoke-claude artifacts
Claude Feb 14, 2026
2fa5627
feat: add ANTHROPIC_AUTH_TOKEN placeholder to agent environment
Claude Feb 14, 2026
1e04f42
fix: restore CODEX_API_KEY env var passing for codex agent compatibility
Claude Feb 14, 2026
5300e01
fix: disable CODEX_API_KEY health check for direct credential passing
Claude Feb 14, 2026
ee0424b
fix: disable OPENAI_BASE_URL for codex agent (temporary)
Claude Feb 14, 2026
1ae2124
docs: update OPENAI_BASE_URL to include /v1 path suffix
Claude Feb 14, 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
270 changes: 146 additions & 124 deletions .github/workflows/smoke-claude.lock.yml

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions .github/workflows/smoke-claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ safe-outputs:
run-failure: "💫 **TO BE CONTINUED...** [{workflow_name}]({run_url}) {status}! Our hero faces unexpected challenges..."
timeout-minutes: 10
post-steps:
- name: Show final Claude Code config
if: always()
run: |
echo "=== Final Claude Code Config ==="
if [ -f ~/.claude.json ]; then
echo "File: ~/.claude.json"
cat ~/.claude.json
else
echo "~/.claude.json not found"
fi
if [ -f ~/.claude/config.json ]; then
echo ""
echo "File: ~/.claude/config.json (legacy)"
cat ~/.claude/config.json
else
echo "~/.claude/config.json not found"
fi
- name: Validate safe outputs were invoked
run: |
OUTPUTS_FILE="${GH_AW_SAFE_OUTPUTS:-/opt/gh-aw/safeoutputs/outputs.jsonl}"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/smoke-codex.lock.yml

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

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The `--` separator divides firewall options from the command to run.
- [Usage guide](docs/usage.md) — CLI flags, domain allowlists, examples
- [Chroot mode](docs/chroot-mode.md) — use host binaries with network isolation
- [API proxy sidecar](docs/api-proxy-sidecar.md) — secure credential management for LLM APIs
- [Authentication architecture](docs/authentication-architecture.md) — deep dive into token handling and credential isolation
- [SSL Bump](docs/ssl-bump.md) — HTTPS content inspection for URL path filtering
- [GitHub Actions](docs/github_actions.md) — CI/CD integration and MCP server setup
- [Environment variables](docs/environment.md) — passing environment variables to containers
Expand Down
6 changes: 4 additions & 2 deletions containers/agent/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ RUN if ! getent group awfuser >/dev/null 2>&1; then \
mkdir -p /home/awfuser/.copilot/logs && \
chown -R awfuser:awfuser /home/awfuser

# Copy iptables setup script and PID logger
# Copy iptables setup script, PID logger, API proxy health check, and Claude key helper
COPY setup-iptables.sh /usr/local/bin/setup-iptables.sh
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
COPY pid-logger.sh /usr/local/bin/pid-logger.sh
RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh
COPY api-proxy-health-check.sh /usr/local/bin/api-proxy-health-check.sh
COPY get-claude-key.sh /usr/local/bin/get-claude-key.sh
RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh /usr/local/bin/api-proxy-health-check.sh /usr/local/bin/get-claude-key.sh

# Copy pre-built one-shot-token library from rust-builder stage
# This prevents tokens from being read multiple times (e.g., by malicious code)
Expand Down
108 changes: 108 additions & 0 deletions containers/agent/api-proxy-health-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/bin/bash
# api-proxy-health-check.sh
# Pre-flight health check to verify API proxy credential isolation
# This script ensures:
# 1. API keys are NOT present in agent environment (credential isolation working)
# 2. API proxy is reachable and healthy (connectivity established)
#
# Usage: source this script before running agent commands
# Returns: 0 if checks pass, 1 if checks fail (prevents agent from running)

set -e

echo "[health-check] API Proxy Pre-flight Check"
echo "[health-check] =========================================="

# Track if any API proxy is configured
API_PROXY_CONFIGURED=false

# Check Claude/Anthropic configuration
if [ -n "$ANTHROPIC_BASE_URL" ]; then
API_PROXY_CONFIGURED=true
echo "[health-check] Checking Anthropic API proxy configuration..."

# Verify credentials are NOT in agent environment
if [ -n "$ANTHROPIC_API_KEY" ] || [ -n "$CLAUDE_API_KEY" ]; then
echo "[health-check][ERROR] Anthropic API key found in agent environment!"
echo "[health-check][ERROR] Credential isolation failed - keys should only be in api-proxy container"
echo "[health-check][ERROR] ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:+<present>}"
echo "[health-check][ERROR] CLAUDE_API_KEY=${CLAUDE_API_KEY:+<present>}"
exit 1
fi
echo "[health-check] ✓ Anthropic credentials NOT in agent environment (correct)"

# Verify ANTHROPIC_AUTH_TOKEN is placeholder (if present)
if [ -n "$ANTHROPIC_AUTH_TOKEN" ]; then
if [ "$ANTHROPIC_AUTH_TOKEN" != "placeholder-token-for-credential-isolation" ]; then
echo "[health-check][ERROR] ANTHROPIC_AUTH_TOKEN contains non-placeholder value!"
echo "[health-check][ERROR] Token should be 'placeholder-token-for-credential-isolation'"
exit 1
fi
echo "[health-check] ✓ ANTHROPIC_AUTH_TOKEN is placeholder value (correct)"
fi

# Perform health check using BASE_URL
echo "[health-check] Testing connectivity to Anthropic API proxy at $ANTHROPIC_BASE_URL..."

# Extract host and port from BASE_URL (format: http://IP:PORT)
PROXY_HOST=$(echo "$ANTHROPIC_BASE_URL" | sed -E 's|^https?://([^:]+):.*|\1|')
PROXY_PORT=$(echo "$ANTHROPIC_BASE_URL" | sed -E 's|^https?://[^:]+:([0-9]+).*|\1|')

# Test TCP connectivity with timeout
if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$PROXY_HOST/$PROXY_PORT" 2>/dev/null; then
echo "[health-check] ✓ Anthropic API proxy is reachable at $ANTHROPIC_BASE_URL"
else
echo "[health-check][ERROR] Cannot connect to Anthropic API proxy at $ANTHROPIC_BASE_URL"
echo "[health-check][ERROR] Proxy may not be running or network is blocked"
exit 1
fi
fi

# Check OpenAI/Codex configuration
if [ -n "$OPENAI_BASE_URL" ]; then
API_PROXY_CONFIGURED=true
echo "[health-check] Checking OpenAI API proxy configuration..."

# Verify credentials are NOT in agent environment
# Note: CODEX_API_KEY check is temporarily disabled - Codex receives credentials directly
if [ -n "$OPENAI_API_KEY" ] || [ -n "$OPENAI_KEY" ]; then
echo "[health-check][ERROR] OpenAI API key found in agent environment!"
echo "[health-check][ERROR] Credential isolation failed - keys should only be in api-proxy container"
echo "[health-check][ERROR] OPENAI_API_KEY=${OPENAI_API_KEY:+<present>}"
# echo "[health-check][ERROR] CODEX_API_KEY=${CODEX_API_KEY:+<present>}" # Temporarily disabled - Codex uses direct credentials
echo "[health-check][ERROR] OPENAI_KEY=${OPENAI_KEY:+<present>}"
exit 1
fi
echo "[health-check] ✓ OpenAI credentials NOT in agent environment (correct)"
# Note: CODEX_API_KEY is intentionally passed through for Codex agent compatibility

# Perform health check using BASE_URL
echo "[health-check] Testing connectivity to OpenAI API proxy at $OPENAI_BASE_URL..."

# Extract host and port from BASE_URL (format: http://IP:PORT)
PROXY_HOST=$(echo "$OPENAI_BASE_URL" | sed -E 's|^https?://([^:]+):.*|\1|')
PROXY_PORT=$(echo "$OPENAI_BASE_URL" | sed -E 's|^https?://[^:]+:([0-9]+).*|\1|')

# Test TCP connectivity with timeout
if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$PROXY_HOST/$PROXY_PORT" 2>/dev/null; then
echo "[health-check] ✓ OpenAI API proxy is reachable at $OPENAI_BASE_URL"
else
echo "[health-check][ERROR] Cannot connect to OpenAI API proxy at $OPENAI_BASE_URL"
echo "[health-check][ERROR] Proxy may not be running or network is blocked"
exit 1
fi
fi

# Summary
if [ "$API_PROXY_CONFIGURED" = "true" ]; then
echo "[health-check] =========================================="
echo "[health-check] ✓ All API proxy health checks passed"
echo "[health-check] ✓ Credential isolation verified"
echo "[health-check] ✓ Connectivity established"
echo "[health-check] =========================================="
else
echo "[health-check] No API proxy configured (ANTHROPIC_BASE_URL and OPENAI_BASE_URL not set)"
echo "[health-check] Skipping health checks"
fi

exit 0
48 changes: 48 additions & 0 deletions containers/agent/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,54 @@ fi
# Setup iptables rules
/usr/local/bin/setup-iptables.sh

# Run API proxy health checks (verifies credential isolation and connectivity)
# This must run AFTER iptables setup (which allows api-proxy traffic) but BEFORE user command
# If health check fails, the script exits with non-zero code and prevents agent from running
/usr/local/bin/api-proxy-health-check.sh || exit 1

# Configure Claude Code API key helper
# This ensures the apiKeyHelper is properly configured in the config file
# The config file must exist before Claude Code starts for authentication to work
# In chroot mode, we write to /host$HOME/.claude.json so it's accessible after chroot
if [ -n "$CLAUDE_CODE_API_KEY_HELPER" ]; then
echo "[entrypoint] Claude Code API key helper configured: $CLAUDE_CODE_API_KEY_HELPER"

# In chroot mode, write to /host path so file is accessible after chroot transition
if [ "${AWF_CHROOT_ENABLED}" = "true" ]; then
CONFIG_FILE="/host$HOME/.claude.json"
else
CONFIG_FILE="$HOME/.claude.json"
fi

if [ -f "$CONFIG_FILE" ]; then
# File exists - check if it has apiKeyHelper
if grep -q '"apiKeyHelper"' "$CONFIG_FILE"; then
# apiKeyHelper exists - validate it matches the environment variable
echo "[entrypoint] Claude Code config file exists with apiKeyHelper, validating..."
CONFIGURED_HELPER=$(grep -o '"apiKeyHelper":"[^"]*"' "$CONFIG_FILE" | cut -d'"' -f4)
if [ "$CONFIGURED_HELPER" != "$CLAUDE_CODE_API_KEY_HELPER" ]; then
echo "[entrypoint][ERROR] apiKeyHelper mismatch:"
echo "[entrypoint][ERROR] Environment variable: $CLAUDE_CODE_API_KEY_HELPER"
echo "[entrypoint][ERROR] Config file value: $CONFIGURED_HELPER"
exit 1
fi
echo "[entrypoint] ✓ Claude Code API key helper validated: $CLAUDE_CODE_API_KEY_HELPER"
else
# File exists but no apiKeyHelper - write it (overwrites empty {} created by docker-manager)
echo "[entrypoint] Claude Code config file exists but missing apiKeyHelper, writing..."
echo "{\"apiKeyHelper\":\"$CLAUDE_CODE_API_KEY_HELPER\"}" > "$CONFIG_FILE"
chmod 666 "$CONFIG_FILE"
echo "[entrypoint] ✓ Wrote apiKeyHelper to $CONFIG_FILE"
fi
else
# File doesn't exist - create it
echo "[entrypoint] Creating Claude Code config file with apiKeyHelper..."
echo "{\"apiKeyHelper\":\"$CLAUDE_CODE_API_KEY_HELPER\"}" > "$CONFIG_FILE"
chmod 666 "$CONFIG_FILE"
echo "[entrypoint] ✓ Created $CONFIG_FILE with apiKeyHelper: $CLAUDE_CODE_API_KEY_HELPER"
fi
fi

# Print proxy environment
echo "[entrypoint] Proxy configuration:"
echo "[entrypoint] HTTP_PROXY=$HTTP_PROXY"
Expand Down
20 changes: 20 additions & 0 deletions containers/agent/get-claude-key.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
# API Key Helper for Claude Code
# This script outputs a placeholder API key since the real key is held
# exclusively in the api-proxy sidecar container for credential isolation.
#
# The api-proxy intercepts requests and injects the real ANTHROPIC_API_KEY,
# so this placeholder key will never reach the actual Anthropic API.
#
# This approach ensures:
# 1. Claude Code agent never has access to the real API key
# 2. Only api-proxy container holds the real credentials
# 3. Health checks verify keys are NOT in agent environment

# Log helper invocation to stderr (stdout is reserved for the API key)
echo "[get-claude-key.sh] API key helper invoked at $(date -Iseconds)" >&2
echo "[get-claude-key.sh] Returning placeholder key for credential isolation" >&2
echo "[get-claude-key.sh] Real authentication via ANTHROPIC_BASE_URL=${ANTHROPIC_BASE_URL:-not set}" >&2

# Output a placeholder key (will be replaced by api-proxy)
echo "sk-ant-placeholder-key-for-credential-isolation"
5 changes: 3 additions & 2 deletions containers/api-proxy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ USER apiproxy
# 10001 - Anthropic API proxy
EXPOSE 10000 10001

# Start the proxy server
CMD ["node", "server.js"]
# Redirect stdout/stderr to log file for persistence
# Use shell form to enable redirection and tee for both file and console
CMD node server.js 2>&1 | tee -a /var/log/api-proxy/api-proxy.log
2 changes: 2 additions & 0 deletions containers/api-proxy/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ if (OPENAI_API_KEY) {
}

console.log(`[OpenAI Proxy] ${sanitizeForLog(req.method)} ${sanitizeForLog(req.url)}`);
console.log(`[OpenAI Proxy] Injecting Authorization header with OPENAI_API_KEY`);
proxyRequest(req, res, 'api.openai.com', {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
});
Expand Down Expand Up @@ -216,6 +217,7 @@ if (ANTHROPIC_API_KEY) {
}

console.log(`[Anthropic Proxy] ${sanitizeForLog(req.method)} ${sanitizeForLog(req.url)}`);
console.log(`[Anthropic Proxy] Injecting x-api-key header with ANTHROPIC_API_KEY`);
// Only set anthropic-version as default; preserve agent-provided version
const anthropicHeaders = { 'x-api-key': ANTHROPIC_API_KEY };
if (!req.headers['anthropic-version']) {
Expand Down
3 changes: 3 additions & 0 deletions docs/api-proxy-sidecar.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

The AWF firewall supports an optional Node.js-based API proxy sidecar that securely holds LLM API credentials and automatically injects authentication headers while routing all traffic through Squid to respect domain whitelisting.

> [!NOTE]
> For a comprehensive deep dive into how AWF handles authentication tokens and credential isolation, see the [Authentication Architecture](authentication-architecture.md) guide.

## Overview

When enabled, the API proxy sidecar:
Expand Down
Loading
Loading