Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 0 additions & 11 deletions .claude/skills/debug-firewall/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ docker logs -f awf-agent

# Squid access log (traffic decisions)
docker exec awf-squid cat /var/log/squid/access.log

# Docker wrapper log (intercepted docker commands)
docker exec awf-agent cat /tmp/docker-wrapper.log
```

### Analyze Traffic
Expand Down Expand Up @@ -156,14 +153,6 @@ docker exec awf-agent cat /etc/resolv.conf
sudo dmesg | grep "FW_DNS"
```

**Docker-in-docker issues:**
```bash
# Check wrapper interception
docker exec awf-agent cat /tmp/docker-wrapper.log
# Verify network injection
docker exec awf-agent grep "INJECTING" /tmp/docker-wrapper.log
```

## Cleanup

```bash
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/firewall-escape-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,16 +127,14 @@ You are running inside the AWF (Agent Workflow Firewall) container. Your goal is
- `src/host-iptables.ts` - Host-level iptables rules
- `src/squid-config.ts` - Squid proxy configuration
- `src/docker-manager.ts` - Container lifecycle management
- `containers/copilot/setup-iptables.sh` - Container NAT rules
- `containers/copilot/docker-wrapper.sh` - Docker command interception
- `containers/copilot/entrypoint.sh` - Container startup
- `containers/agent/setup-iptables.sh` - Container NAT rules
- `containers/agent/entrypoint.sh` - Container startup
- `AGENTS.md` - Architecture documentation

3. **Understand the layered architecture**:
- How does the Squid proxy filter traffic?
- What iptables rules are applied at the host level?
- What NAT rules redirect traffic inside the container?
- How does the Docker wrapper prevent container escapes?

4. **Identify potential attack surfaces** based on what you learn:
- Look for gaps between the layers
Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/security-guard.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ This repository implements a **network firewall for AI agents** that provides L7
- Wildcard pattern security (prevents overly broad patterns)
- Protocol prefix handling

6. **Docker wrapper** (`containers/agent/docker-wrapper.sh`)
- Intercepts docker commands to enforce network restrictions
- Injects proxy configuration into spawned containers

## Your Task

Analyze PR #${{ github.event.pull_request.number }} in repository ${{ github.repository }}.
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/test-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@ jobs:
echo "=== Testing blocked-domains.sh ==="
sudo ./examples/blocked-domains.sh

- name: Test docker-in-docker.sh
run: |
echo "=== Testing docker-in-docker.sh ==="
sudo ./examples/docker-in-docker.sh

# Note: github-copilot.sh is skipped as it requires GITHUB_TOKEN for Copilot CLI
# To test it, you would need to set up a secret with a valid Copilot token

Expand Down
61 changes: 0 additions & 61 deletions .github/workflows/test-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,64 +156,3 @@ jobs:
/tmp/awf-agent-logs-*/
/tmp/squid-logs-*/
retention-days: 7

test-docker-egress:
name: Docker Egress Tests
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4

- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build project
run: npm run build

- name: Pre-test cleanup
run: sudo ./scripts/ci/cleanup.sh

- name: Run docker egress tests
id: run-tests
run: |
sudo -E npm run test:integration -- docker-egress.test.ts 2>&1 | tee test-output.log
continue-on-error: true

- name: Clean npm cache
if: always()
run: |
sudo npm cache clean --force
sudo rm -rf ~/.npm/_npx

- name: Generate test summary
if: always()
run: |
npx tsx scripts/ci/generate-test-summary.ts "docker-egress.test.ts" "Docker Egress Tests" test-output.log

- name: Check test results
if: steps.run-tests.outcome == 'failure'
run: exit 1

- name: Post-test cleanup
if: always()
run: sudo ./scripts/ci/cleanup.sh

- name: Upload test logs on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: docker-egress-test-logs
path: |
/tmp/*-test.log
/tmp/awf-*/
/tmp/awf-agent-logs-*/
/tmp/squid-logs-*/
retention-days: 7
9 changes: 1 addition & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,25 +211,18 @@ The codebase follows a modular architecture with clear separation of concerns:
- **Firewall Exemption:** Allowed unrestricted outbound access via iptables rule `-s 172.30.0.10 -j ACCEPT`

**Agent Execution Container** (`containers/agent/`)
- Based on `ubuntu:22.04` with iptables, curl, git, nodejs, npm, docker-cli
- Based on `ubuntu:22.04` with iptables, curl, git, nodejs, npm
- Mounts entire host filesystem at `/host` and user home directory for full access
- Mounts Docker socket (`/var/run/docker.sock`) for docker-in-docker support
- `NET_ADMIN` capability required for iptables setup during initialization
- **Security:** `NET_ADMIN` is dropped via `capsh --drop=cap_net_admin` before executing user commands, preventing malicious code from modifying iptables rules
- Two-stage entrypoint:
1. `setup-iptables.sh`: Configures iptables NAT rules to redirect HTTP/HTTPS traffic to Squid (agent container only)
2. `entrypoint.sh`: Drops NET_ADMIN capability, then executes user command as non-root user
- **Docker Wrapper** (`docker-wrapper.sh`): Intercepts `docker run` commands to inject network and proxy configuration
- Symlinked at `/usr/bin/docker` (real docker at `/usr/bin/docker-real`)
- Automatically injects `--network awf-net` to all spawned containers
- Injects proxy environment variables: `HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, `https_proxy`
- Logs all intercepted commands to `/tmp/docker-wrapper.log` for debugging
- 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)
- **Note:** These NAT rules only apply to the agent container itself, not spawned containers

### Traffic Flow

Expand Down
156 changes: 1 addition & 155 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,25 +176,18 @@ The codebase follows a modular architecture with clear separation of concerns:
- **Firewall Exemption:** Allowed unrestricted outbound access via iptables rule `-s 172.30.0.10 -j ACCEPT`

**Agent Execution Container** (`containers/agent/`)
- Based on `ubuntu:22.04` with iptables, curl, git, nodejs, npm, docker-cli
- Based on `ubuntu:22.04` with iptables, curl, git, nodejs, npm
- Mounts entire host filesystem at `/host` and user home directory for full access
- Mounts Docker socket (`/var/run/docker.sock`) for docker-in-docker support
- `NET_ADMIN` capability required for iptables setup during initialization
- **Security:** `NET_ADMIN` is dropped via `capsh --drop=cap_net_admin` before executing user commands, preventing malicious code from modifying iptables rules
- Two-stage entrypoint:
1. `setup-iptables.sh`: Configures iptables NAT rules to redirect HTTP/HTTPS traffic to Squid (agent container only)
2. `entrypoint.sh`: Drops NET_ADMIN capability, then executes user command as non-root user
- **Docker Wrapper** (`docker-wrapper.sh`): Intercepts `docker run` commands to inject network and proxy configuration
- Symlinked at `/usr/bin/docker` (real docker at `/usr/bin/docker-real`)
- Automatically injects `--network awf-net` to all spawned containers
- Injects proxy environment variables: `HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, `https_proxy`
- Logs all intercepted commands to `/tmp/docker-wrapper.log` for debugging
- 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)
- **Note:** These NAT rules only apply to the agent container itself, not spawned containers

### Traffic Flow

Expand Down Expand Up @@ -411,153 +404,6 @@ sudo cat /tmp/squid-logs-<timestamp>/access.log
- Currently no test files exist (tsconfig excludes `**/*.test.ts`)
- Integration testing: Run commands with `--log-level debug` and `--keep-containers` to inspect generated configs and container logs

## MCP Server Configuration for Copilot CLI

### Overview

GitHub Copilot CLI v0.0.347+ includes a **built-in GitHub MCP server** that connects to a read-only remote endpoint (`https://api.enterprise.githubcopilot.com/mcp/readonly`). This built-in server takes precedence over local MCP configurations by default, which prevents write operations like creating issues or pull requests.

To use a local, writable GitHub MCP server with Copilot CLI, you must:
1. Configure the MCP server in the correct location with the correct format
2. Disable the built-in GitHub MCP server
3. Ensure proper environment variable passing

### Correct MCP Configuration

**Location:** The MCP configuration must be placed at:
- `~/.copilot/mcp-config.json` (primary location)

The agent container mounts the HOME directory, so this config file is automatically accessible to GitHub Copilot CLI running inside the container.

**Format:**
```json
{
"mcpServers": {
"github": {
"type": "local",
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"-e",
"GITHUB_TOOLSETS=default",
"ghcr.io/github/github-mcp-server:v0.19.0"
],
"tools": ["*"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
}
}
}
}
```

**Key Requirements:**
- ✅ **`"tools": ["*"]`** - Required field. Use `["*"]` to enable all tools, or list specific tool names
- ⚠️ Empty array `[]` means NO tools will be available
- ✅ **`"type": "local"`** - Required to specify local MCP server type
- ✅ **`"env"` section** - Environment variables must be declared here with `${VAR}` syntax for interpolation
- ✅ **Environment variable in args** - Use bare variable names in `-e` flags (e.g., `"GITHUB_PERSONAL_ACCESS_TOKEN"` without `$`)
- ✅ **Shell environment** - Variables must be exported in the shell before running awf
- ✅ **MCP server name** - Use `"github"` as the server name (must match `--allow-tool` flag)

### Running Copilot CLI with Local MCP Through Firewall

**Required setup:**
```bash
# Export environment variables (both required)
export GITHUB_TOKEN="<your-copilot-cli-token>" # For Copilot CLI authentication
export GITHUB_PERSONAL_ACCESS_TOKEN="<your-github-pat>" # For GitHub MCP server

# Run awf with sudo -E to preserve environment variables
sudo -E awf \
--allow-domains raw.githubusercontent.com,api.github.com,github.com,registry.npmjs.org,api.enterprise.githubcopilot.com \
"npx @github/copilot@0.0.347 \
--disable-builtin-mcps \
--allow-tool github \
--prompt 'your prompt here'"
```

**Critical requirements:**
- `sudo -E` - **REQUIRED** to pass environment variables through sudo to the agent container
- `--disable-builtin-mcps` - Disables the built-in read-only GitHub MCP server
- `--allow-tool github` - Grants permission to use all tools from the `github` MCP server (must match server name in config)
- MCP config at `~/.copilot/mcp-config.json` - Automatically accessible since agent container mounts HOME directory

**Why `sudo -E` is required:**
1. `awf` needs sudo for iptables manipulation
2. `-E` preserves GITHUB_TOKEN and GITHUB_PERSONAL_ACCESS_TOKEN
3. These variables are passed into the agent container via the HOME directory mount
4. The GitHub MCP server Docker container inherits them from the agent container's environment

### Troubleshooting

**Problem:** MCP server starts but says "GITHUB_PERSONAL_ACCESS_TOKEN not set"
- **Cause:** Environment variable not passed correctly through sudo or to Docker container
- **Solution:** Use `sudo -E` when running awf, and ensure the variable is exported before running the command

**Problem:** MCP config validation error: "Invalid input"
- **Cause:** Missing `"tools"` field
- **Solution:** Add `"tools": ["*"]` to the MCP server config

**Problem:** Copilot uses read-only remote MCP instead of local
- **Cause:** Built-in MCP not disabled
- **Solution:** Add `--disable-builtin-mcps` flag to the copilot command

**Problem:** Tools not available even with local MCP
- **Cause:** Wrong server name in `--allow-tool` flag
- **Solution:** Use `--allow-tool github` (must match the server name in mcp-config.json)

**Problem:** Permission denied when running awf
- **Cause:** iptables requires root privileges
- **Solution:** Use `sudo -E awf` (not just `sudo awf`)

### Verifying Local MCP Usage

Check GitHub Copilot CLI logs (use `--log-level debug`) for these indicators:

**Local MCP working:**
```
Starting MCP client for github with command: docker
GitHub MCP Server running on stdio
readOnly=false
MCP client for github connected
```

**Built-in remote MCP (not what you want):**
```
Using Copilot API endpoint: https://api.enterprise.githubcopilot.com/mcp/readonly
Starting remote MCP client for github-mcp-server
```

### CI/CD Configuration

For GitHub Actions workflows:
1. Create MCP config script that writes to `~/.copilot/mcp-config.json` (note: `~` = `/home/runner` in GitHub Actions)
2. Export both `GITHUB_TOKEN` (for GitHub Copilot CLI) and `GITHUB_PERSONAL_ACCESS_TOKEN` (for GitHub MCP server) as environment variables
3. Pull the MCP server Docker image before running tests: `docker pull ghcr.io/github/github-mcp-server:v0.19.0`
4. Run awf with `sudo -E` to preserve environment variables
5. Always use `--disable-builtin-mcps` and `--allow-tool github` flags when running GitHub Copilot CLI

**Example workflow step:**
```yaml
- name: Test GitHub Copilot CLI with GitHub MCP through firewall
env:
GITHUB_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
sudo -E awf \
--allow-domains raw.githubusercontent.com,api.github.com,github.com,registry.npmjs.org,api.enterprise.githubcopilot.com \
"npx @github/copilot@0.0.347 \
--disable-builtin-mcps \
--allow-tool github \
--log-level debug \
--prompt 'your prompt here'"
```

## Logging Implementation

### Overview
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ A network firewall for agentic workflows with domain whitelisting. This tool pro

- **L7 Domain Whitelisting**: Control HTTP/HTTPS traffic at the application layer
- **Host-Level Enforcement**: Uses iptables DOCKER-USER chain to enforce firewall on ALL containers
- **Docker-in-Docker Support**: Spawned containers inherit firewall restrictions

## Get started fast

Expand Down Expand Up @@ -113,7 +112,7 @@ sudo awf --help
## Explore the docs

- [Quick start](docs/quickstart.md) — install, verify, and run your first command
- [Usage guide](docs/usage.md) — CLI flags, domain allowlists, Docker-in-Docker examples
- [Usage guide](docs/usage.md) — CLI flags, domain allowlists, examples
- [SSL Bump](docs/ssl-bump.md) — HTTPS content inspection for URL path filtering
- [Logging quick reference](docs/logging_quickref.md) and [Squid log filtering](docs/squid_log_filtering.md) — view and filter traffic
- [Security model](docs/security.md) — what the firewall protects and how
Expand Down
22 changes: 7 additions & 15 deletions containers/agent/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@ RUN apt-get update && \
gosu \
libcap2-bin && \
# Install Node.js 22 from NodeSource
# Remove any existing nodejs packages first to avoid conflicts
apt-get remove -y nodejs npm || true && \
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get install -y nodejs && \
# Install Docker CLI for MCP servers that run as containers
install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc && \
chmod a+r /etc/apt/keyrings/docker.asc && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \
apt-get update && \
apt-get install -y docker-ce-cli && \
# Verify Node.js 22 was installed correctly
node --version | grep -q "^v22\." || (echo "ERROR: Node.js 22 not installed correctly" && exit 1) && \
npx --version || (echo "ERROR: npx not found" && exit 1) && \
rm -rf /var/lib/apt/lists/*

# Create non-root user with UID/GID matching host user
Expand All @@ -36,17 +34,11 @@ RUN groupadd -g ${USER_GID} awfuser && \
mkdir -p /home/awfuser/.copilot/logs && \
chown -R awfuser:awfuser /home/awfuser

# Copy iptables setup script, docker wrapper, and PID logger
# Copy iptables setup script and PID logger
COPY setup-iptables.sh /usr/local/bin/setup-iptables.sh
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
COPY docker-wrapper.sh /usr/local/bin/docker-wrapper.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/docker-wrapper.sh /usr/local/bin/pid-logger.sh

# Install docker wrapper to intercept docker commands
# Rename real docker binary and replace with wrapper
RUN mv /usr/bin/docker /usr/bin/docker-real && \
ln -s /usr/local/bin/docker-wrapper.sh /usr/bin/docker
RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh

# Set working directory
WORKDIR /workspace
Expand Down
Loading
Loading