Skip to content
Open
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
252 changes: 28 additions & 224 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,246 +5,50 @@ A network firewall for agentic workflows with domain whitelisting. This tool pro
> [!TIP]
> This project is a part of GitHub Next's explorations of [Agentic Workflows](https://github.com/githubnext/gh-aw). For more background, check out the [project page on the GitHub Next website](https://githubnext.com/projects/agentic-workflows/)! ✨

## Features
## What it does

- **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
- **Optional Docker Isolation**: Disable Docker socket access with `--no-docker` for additional security

## Quick Start
## Get started fast

### Requirements
- **Requirement:** Docker running on your machine
- **Install:**
```bash
curl -sSL https://raw.githubusercontent.com/githubnext/gh-aw-firewall/main/install.sh | sudo bash
```
Review the script before running, or download the latest release binary and verify it with the published `checksums.txt` before installing.
- **Run your first command:**
```bash
sudo awf --allow-domains github.com -- curl https://api.github.com
```
Use the `--` separator to pass the command you want to run behind the firewall.

- **Docker**: Must be running

### Installation

**Recommended: One-line installer with SHA verification**
### GitHub Copilot CLI in one line

```bash
curl -sSL https://raw.githubusercontent.com/githubnext/gh-aw-firewall/main/install.sh | sudo bash
```

This installer automatically:
- Downloads the latest release binary
- Verifies SHA256 checksum to detect corruption or tampering
- Validates the file is a valid Linux executable
- Protects against 404 error pages being saved as binaries
- Installs to `/usr/local/bin/awf`

**Alternative: Manual installation**

```bash
# Download the latest release binary
curl -fL https://github.com/githubnext/gh-aw-firewall/releases/latest/download/awf-linux-x64 -o awf

# Download checksums for verification
curl -fL https://github.com/githubnext/gh-aw-firewall/releases/latest/download/checksums.txt -o checksums.txt

# Verify SHA256 checksum
sha256sum -c checksums.txt --ignore-missing

# Install
chmod +x awf
sudo mv awf /usr/local/bin/

# Verify installation
sudo awf --help
```

**Docker Image Verification:** All published container images are cryptographically signed with cosign. See [docs/image-verification.md](docs/image-verification.md) for verification instructions.

### Basic Usage

```bash
# Simple HTTP request
sudo awf \
--allow-domains github.com,api.github.com \
-- curl https://api.github.com

# With GitHub Copilot CLI
sudo -E awf \
--allow-domains github.com,api.github.com,googleapis.com \
--allow-domains github.com,api.github.com,githubusercontent.com \
-- copilot --prompt "List my repositories"

# Docker-in-Docker (spawned containers inherit firewall)
sudo awf \
--allow-domains api.github.com,registry-1.docker.io,auth.docker.io \
-- docker run --rm curlimages/curl -fsS https://api.github.com/zen
```

**Note:** Always use the `--` separator to pass commands and arguments. This ensures proper argument handling and avoids shell escaping issues.

### Log Viewing

View Squid proxy logs from current or previous runs:

```bash
# View recent logs with pretty formatting
awf logs

# Follow logs in real-time
awf logs -f

# View logs in JSON format for scripting
awf logs --format json

# List all available log sources
awf logs --list
```

## Domain Whitelisting

Domains automatically match all subdomains:

```bash
# github.com matches api.github.com, raw.githubusercontent.com, etc.
sudo awf --allow-domains github.com -- curl https://api.github.com # ✓ works
```

### Wildcard Patterns

You can use wildcard patterns with `*` to match multiple domains:

```bash
# Match any subdomain of github.com
--allow-domains '*.github.com'

# Match api-v1.example.com, api-v2.example.com, etc.
--allow-domains 'api-*.example.com'

# Combine plain domains and wildcards
--allow-domains 'github.com,*.googleapis.com,api-*.example.com'
```

**Pattern rules:**
- `*` matches any characters (converted to regex `.*`)
- Patterns are case-insensitive (DNS is case-insensitive)
- Overly broad patterns like `*`, `*.*`, or `*.*.*` are rejected for security
- Use quotes around patterns to prevent shell expansion

**Examples:**
| Pattern | Matches | Does Not Match |
|---------|---------|----------------|
| `*.github.com` | `api.github.com`, `raw.github.com` | `github.com` |
| `api-*.example.com` | `api-v1.example.com`, `api-test.example.com` | `api.example.com` |
| `github.com` | `github.com`, `api.github.com` | `notgithub.com` |

### Using Command-Line Flag

Common domain lists:

```bash
# For GitHub Copilot with GitHub API
--allow-domains github.com,api.github.com,githubusercontent.com,googleapis.com

# For MCP servers
--allow-domains github.com,arxiv.org,example.com
```

### Using a Domains File

You can also specify domains in a file using `--allow-domains-file`:

```bash
# Create a domains file (see examples/domains.txt)
cat > allowed-domains.txt << 'EOF'
# GitHub domains
github.com
api.github.com

# NPM registry
npmjs.org, registry.npmjs.org

# Wildcard patterns
*.googleapis.com

# Example with inline comment
example.com # Example domain
EOF

# Use the domains file
sudo awf --allow-domains-file allowed-domains.txt -- curl https://api.github.com
```

**File format:**
- One domain per line or comma-separated
- Comments start with `#` (full line or inline)
- Empty lines are ignored
- Whitespace is trimmed
- Wildcard patterns are supported

**Combining both methods:**
```bash
# You can use both flags together - domains are merged
sudo awf \
--allow-domains github.com \
--allow-domains-file my-domains.txt \
-- curl https://api.github.com
```


## Security Considerations

### What This Protects Against
- Unauthorized egress to non-whitelisted domains
- Data exfiltration via HTTP/HTTPS
- DNS-based data exfiltration to unauthorized DNS servers
- MCP servers accessing unexpected endpoints
## Explore the docs

### Agent Container Security (User Mode)
- [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
- [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
- [Architecture](docs/architecture.md) — how Squid, Docker, and iptables fit together
- [Troubleshooting](docs/troubleshooting.md) — common issues and fixes
- [Image verification](docs/image-verification.md) — cosign signature verification

The agent container runs user commands as a **non-root user** (`awfuser`) for enhanced security:
## Development

- **Privilege Separation**: Privileged operations (iptables setup, DNS configuration) run as root in the entrypoint, then privileges are dropped before executing user commands
- **UID/GID Matching**: The `awfuser` UID/GID is automatically adjusted to match the host user's UID/GID, ensuring correct file ownership for mounted volumes
- **Reduced Attack Surface**: If a user command is compromised, it cannot modify system files or escape the container's security boundaries
- **Docker Access**: The `awfuser` is added to the docker group, allowing MCP servers to spawn containers while still running as non-root

**Note:** The `awf` CLI itself requires `sudo` for host-level iptables configuration (DOCKER-USER chain), but the agent processes (GitHub Copilot CLI, etc.) run without root privileges inside the container.

### DNS Server Restriction

DNS traffic is restricted to trusted servers only (default: Google DNS 8.8.8.8, 8.8.4.4). This prevents DNS-based data exfiltration attacks where an attacker encodes data in DNS queries to a malicious DNS server.

```bash
# Use custom DNS servers
sudo awf \
--allow-domains github.com \
--dns-servers 1.1.1.1,1.0.0.1 \
-- curl https://api.github.com
```

## Development & Testing

### Running Tests

```bash
# Install dependencies
npm install

# Run all tests
npm test

# Run tests with coverage report
npm run test:coverage

# Run tests in watch mode
npm run test:watch
```

### Building

```bash
# Build TypeScript
npm run build

# Run linter
npm run lint

# Clean build artifacts
npm run clean
```
- Install dependencies: `npm install`
- Run tests: `npm test`
- Build: `npm run build`

## Contributing

Expand Down
42 changes: 42 additions & 0 deletions containers/agent/seccomp-profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"defaultAction": "SCMP_ACT_ALLOW",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_AARCH64"
],
"syscalls": [
{
"names": [
"kexec_load",
"kexec_file_load",
"reboot",
"init_module",
"finit_module",
"delete_module",
"acct",
"swapon",
"swapoff",
"mount",
"umount",
"umount2",
"pivot_root",
"syslog",
"add_key",
"request_key",
"keyctl",
"uselib",
"personality",
"ustat",
"sysfs",
"vhangup",
"get_kernel_syms",
"query_module",
"create_module",
"nfsservctl"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1
}
]
}
20 changes: 20 additions & 0 deletions docs/logging_quickref.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,26 @@ docker exec awf-agent dmesg | grep FW_BLOCKED
sudo journalctl -k | grep FW_BLOCKED
```

### DNS Query Logging (Audit Trail)
```bash
# View all DNS queries made by containers
sudo dmesg | grep FW_DNS_QUERY

# Using journalctl (systemd)
sudo journalctl -k | grep FW_DNS_QUERY

# Real-time DNS query monitoring
sudo dmesg -w | grep FW_DNS_QUERY

# Count DNS queries by destination
sudo dmesg | grep FW_DNS_QUERY | grep -oP 'DST=\K[^ ]+' | sort | uniq -c | sort -rn

# Show DNS queries to specific resolver (e.g., 8.8.8.8)
sudo dmesg | grep FW_DNS_QUERY | grep 'DST=8.8.8.8'
```

**Note:** DNS queries are logged for audit trail purposes. This helps detect potential DNS tunneling attempts or unusual DNS activity. The log prefix `[FW_DNS_QUERY]` is used to identify DNS traffic.

## Log Format

### Squid Log Entry
Expand Down
19 changes: 19 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Options:
--log-level <level> Log level: debug, info, warn, error (default: info)
--keep-containers Keep containers running after command exits
--work-dir <dir> Working directory for temporary files
--no-docker Disable Docker-in-Docker: do not mount Docker socket
-V, --version Output the version number
-h, --help Display help for command

Expand Down Expand Up @@ -45,6 +46,24 @@ sudo awf \
# Returns: curl: (22) The requested URL returned error: 403
```

### Disabling Docker-in-Docker

By default, the agent container has access to the Docker socket, allowing it to spawn additional containers (which inherit firewall restrictions). For additional security, you can disable this with the `--no-docker` flag:

```bash
# Disable Docker-in-Docker - Docker socket is not mounted
sudo awf \
--allow-domains github.com \
--no-docker \
-- curl https://api.github.com
```

When `--no-docker` is enabled:
- The Docker socket (`/var/run/docker.sock`) is not mounted
- Commands cannot spawn new Docker containers from within the firewall
- Provides additional security by preventing container escapes via Docker
- Useful when running untrusted code that shouldn't have Docker access

### With GitHub Copilot CLI

```bash
Expand Down
6 changes: 6 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,11 @@ program
'--proxy-logs-dir <path>',
'Directory to save Squid proxy logs to (writes access.log directly to this directory)'
)
.option(
'--no-docker',
'Disable Docker-in-Docker: do not mount Docker socket, preventing container spawning',
false
)
.argument('[args...]', 'Command and arguments to execute (use -- to separate from options)')
.action(async (args: string[], options) => {
// Require -- separator for passing command arguments
Expand Down Expand Up @@ -506,6 +511,7 @@ program
containerWorkDir: options.containerWorkdir,
dnsServers,
proxyLogsDir: options.proxyLogsDir,
disableDocker: options.noDocker,
};

// Warn if --env-all is used
Expand Down
Loading
Loading