The firewall uses a containerized architecture with Squid proxy for L7 (HTTP/HTTPS) egress control. The system provides domain-based whitelisting while maintaining full filesystem access for the Copilot CLI and its MCP servers.
┌─────────────────────────────────────────┐
│ Host (GitHub Actions Runner / Local) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Firewall CLI │ │
│ │ - Parse arguments │ │
│ │ - Generate Squid config │ │
│ │ - Start Docker Compose │ │
│ └────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ Docker Compose │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ Squid Proxy Container │ │ │
│ │ │ - Domain ACL filtering │ │ │
│ │ │ - HTTP/HTTPS proxy │ │ │
│ │ └────────────────────────────┘ │ │
│ │ ▲ │ │
│ │ ┌────────┼───────────────────┐ │ │
│ │ │ Agent Container │ │ │
│ │ │ - Full filesystem access │ │ │
│ │ │ - iptables redirect │ │ │
│ │ │ - Spawns MCP servers │ │ │
│ │ │ - All traffic → Squid │ │ │
│ │ └────────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
- Uses
commanderfor argument parsing - Orchestrates the entire workflow: config generation → container startup → command execution → cleanup
- Handles signal interrupts (SIGINT/SIGTERM) for graceful shutdown
- Main flow:
writeConfigs()→startContainers()→runAgentCommand()→stopContainers()→cleanup()
src/squid-config.ts: Generates Squid proxy configuration with domain ACL rulessrc/docker-manager.ts: Generates Docker Compose YAML with two services (squid-proxy, agent)- All configs are written to a temporary work directory (default:
/tmp/awf-<timestamp>)
- Manages container lifecycle using
execato run docker-compose commands - Fixed network topology:
172.30.0.0/24subnet, Squid at172.30.0.10, Agent at172.30.0.20 - Squid container uses healthcheck; Agent waits for Squid to be healthy before starting
WrapperConfig: Main configuration interfaceSquidConfig,DockerComposeConfig: Typed configuration objects
- Singleton logger with configurable log levels (debug, info, warn, error)
- Uses
chalkfor colored output - All logs go to stderr (console.error) to avoid interfering with command stdout
- Based on
ubuntu/squid:latest - Mounts dynamically-generated
squid.conffrom work directory - Exposes port 3128 for proxy traffic
- Logs to shared volume
squid-logs:/var/log/squid - Network: Connected to
awf-netat172.30.0.10 - Firewall Exemption: Allowed unrestricted outbound access via iptables rule
-s 172.30.0.10 -j ACCEPT
- Based on
ubuntu:22.04with iptables, curl, git, nodejs, npm - Mounts entire host filesystem at
/hostand user home directory for full access NET_ADMINcapability required for iptables setup during initialization- Security:
NET_ADMINis dropped viacapsh --drop=cap_net_adminbefore executing user commands, preventing malicious code from modifying iptables rules - Chroot Mode: User commands run inside
chroot /hostfor transparent host binary access. See Chroot Mode for details. - Two-stage entrypoint:
setup-iptables.sh: Configures iptables NAT rules to redirect HTTP/HTTPS traffic to Squid (agent container only)entrypoint.sh: Drops NET_ADMIN capability, then executes user command as non-root user
- Key iptables rules (in
setup-iptables.sh):- Allow localhost traffic (for stdio MCP servers)
- Allow DNS queries
- Allow traffic to Squid proxy itself
- Redirect all HTTP (port 80) and HTTPS (port 443) to Squid via DNAT (NAT table)
User Command
↓
CLI generates configs (squid.conf, docker-compose.yml)
↓
Docker Compose starts Squid container (with healthcheck)
↓
Docker Compose starts Agent container (waits for Squid healthy)
↓
iptables rules applied in Agent container
↓
User command executes in Agent container
↓
All HTTP/HTTPS traffic → iptables DNAT → Squid proxy → domain ACL filtering
↓
Containers stopped, temporary files cleaned up
The wrapper generates:
- Squid configuration with domain ACLs
- Docker Compose configuration for both containers
- Temporary work directory for configs and logs
- Squid proxy starts first with healthcheck
- Agent container waits for Squid to be healthy
- iptables rules applied in agent container to redirect all HTTP/HTTPS traffic
- All HTTP (port 80) and HTTPS (port 443) traffic → Squid proxy
- Squid filters based on domain whitelist
- Localhost traffic exempt (for stdio MCP servers)
- DNS queries allowed (for name resolution)
- Stdio MCP servers: Run as child processes, no network needed
- HTTP MCP servers: Traffic routed through Squid proxy
- Docker MCP servers: Share network namespace, inherit restrictions
- Container logs streamed in real-time using
docker logs -f - TTY disabled to prevent ANSI escape sequences
- Agent and Squid logs preserved to
/tmp/*-logs-<timestamp>/(if created)
- Containers stopped and removed
- Logs moved to persistent locations:
- Agent logs →
/tmp/awf-agent-logs-<timestamp>/(if they exist) - Squid logs →
/tmp/squid-logs-<timestamp>/(if they exist)
- Agent logs →
- Temporary files deleted (unless
--keep-containersspecified) - Exit code propagated from agent command
The system uses a defense-in-depth cleanup strategy across four stages to prevent Docker resource leaks:
Location: CI/CD workflow scripts
What: Runs cleanup.sh to remove orphaned resources from previous failed runs
Why: Prevents Docker network subnet pool exhaustion and container name conflicts
Critical: Without this, timeout commands that kill the wrapper mid-cleanup leave networks/containers behind
Location: src/cli.ts (performCleanup())
What:
stopContainers()→docker compose down -v(stops containers, removes volumes)cleanup()→ Deletes workDir (/tmp/awf-<timestamp>) Trigger: Successful command completion
Location: src/cli.ts (SIGINT/SIGTERM handlers, catch blocks)
What: Same as normal exit cleanup
Trigger: User interruption (Ctrl+C), timeout signals, or errors
Limitation: Cannot catch SIGKILL (9) from timeout after grace period
Location: .github/workflows/test-*.yml (if: always())
What: Runs cleanup.sh regardless of job status
Why: Safety net for SIGKILL, job cancellation, and unexpected failures
Removes all awf resources:
- Containers by name (
awf-squid,awf-agent) - All docker-compose services from work directories
- Unused containers (
docker container prune -f) - Unused networks (
docker network prune -f) - critical for subnet pool management - Temporary directories (
/tmp/awf-*)
Note: Test scripts use timeout 60s which can kill the wrapper before Stage 2/3 cleanup completes. Stage 1 (pre-test) and Stage 4 (always) prevent accumulation across test runs.
- Domains in
--allow-domainsare normalized (protocol/trailing slash removed) - Both exact matches and subdomain matches are added to Squid ACL:
github.com→ matchesgithub.comand.github.com(subdomains).github.com→ matches all subdomains
- Squid denies any domain not in the allowlist
The wrapper propagates the exit code from the agent container:
- Command runs in agent container
- Container exits with command's exit code
- Wrapper inspects container:
docker inspect --format={{.State.ExitCode}} - Wrapper exits with same code
All temporary files are created in workDir (default: /tmp/awf-<timestamp>):
squid.conf: Generated Squid proxy configurationdocker-compose.yml: Generated Docker Compose configurationagent-logs/: Directory for agent logs (automatically preserved if logs are created)squid-logs/: Directory for Squid proxy logs (automatically preserved if logs are created)
Use --keep-containers to preserve containers and files after execution for debugging.
commander: CLI argument parsingchalk: Colored terminal outputexeca: Subprocess execution (docker-compose commands)js-yaml: YAML generation for Docker Compose config- TypeScript 5.x, compiled to ES2020 CommonJS