diff --git a/AGENTS.md b/AGENTS.md index 4f558fd77..2e74e23ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,8 +10,8 @@ This is a firewall for GitHub Copilot CLI (package name: `@github/awf`) that pro - **[README.md](README.md)** - Main project documentation and usage guide - **[docs/chroot-mode.md](docs/chroot-mode.md)** - Chroot mode for transparent host binary access -- **[LOGGING.md](LOGGING.md)** - Comprehensive logging documentation - **[docs/logging_quickref.md](docs/logging_quickref.md)** - Quick reference for log queries and monitoring +- **[docs/environment.md](docs/environment.md)** - Environment variable configuration and security best practices ## Development Workflow diff --git a/docs/chroot-mode.md b/docs/chroot-mode.md index 39a8b46c1..851943bf1 100644 --- a/docs/chroot-mode.md +++ b/docs/chroot-mode.md @@ -56,13 +56,14 @@ iptables rules applied (redirect HTTP/HTTPS to Squid) If AWF_CHROOT_ENABLED=true: ↓ 1. Verify capsh exists on host - 2. Copy DNS configuration to /host/etc/resolv.conf - 3. Map host user by UID - 4. Write command to temp script with PATH setup - 5. chroot /host - 6. Drop capabilities (CAP_NET_ADMIN, CAP_SYS_CHROOT) - 7. Switch to host user - 8. Execute command + 2. Mount container-scoped procfs at /host/proc (for Java/dotnet) + 3. Copy DNS configuration to /host/etc/resolv.conf + 4. Map host user by UID + 5. Write command to temp script with PATH setup + 6. chroot /host + 7. Drop capabilities (CAP_NET_ADMIN, CAP_SYS_CHROOT, CAP_SYS_ADMIN) + 8. Switch to host user + 9. Execute command ↓ All child processes inherit chroot environment All HTTP/HTTPS traffic → Squid proxy → Domain filtering @@ -79,6 +80,32 @@ All HTTP/HTTPS traffic → Squid proxy → Domain filtering | PATH | Container PATH | Reconstructed for host binaries | | Network isolation | iptables → Squid | iptables → Squid (unchanged) | +### Procfs Mounting for Java and .NET + +As of v0.13.13, chroot mode mounts a fresh container-scoped procfs at `/host/proc` to support Java and .NET runtimes: + +**Why this is needed:** +- Java's JVM requires access to `/proc/cpuinfo` for CPU detection +- .NET's CLR requires `/proc/self/exe` to resolve the runtime binary path +- Static bind mounts of `/proc/self` always resolve to the parent shell, not the current process + +**How it works:** +1. Before chroot, the entrypoint mounts a fresh procfs: `mount -t proc -o nosuid,nodev,noexec proc /host/proc` +2. This requires `CAP_SYS_ADMIN` capability, which is granted during container startup +3. The procfs is container-scoped, showing only container processes (not host processes) +4. `CAP_SYS_ADMIN` is dropped via capsh before executing user commands + +**Security implications:** +- The mounted procfs only exposes container processes, not host processes +- Mount operation completes before user code runs (capability dropped) +- procfs is mounted with security restrictions: `nosuid,nodev,noexec` +- User code cannot unmount or remount (no `CAP_SYS_ADMIN`, umount blocked in seccomp) + +**Backwards compatibility:** +- Existing code continues to work without changes +- Java and .NET commands now succeed in chroot mode (previously failed with cryptic errors) +- No impact on non-chroot mode + ## Usage ### Basic Usage @@ -164,7 +191,8 @@ In chroot mode, selective paths are mounted for security instead of the entire f | `/etc/ca-certificates` | `/host/etc/ca-certificates:ro` | CA certificates | | `/etc/passwd` | `/host/etc/passwd:ro` | User lookup | | `/etc/group` | `/host/etc/group:ro` | Group lookup | -| `/proc/self` | `/host/proc/self:ro` | Process self-info (needed by Go) | + +**Note:** As of v0.13.13, `/proc` is no longer bind-mounted. Instead, a fresh container-scoped procfs is mounted at `/host/proc` during entrypoint initialization. This provides dynamic `/proc/self/exe` resolution required by Java and .NET runtimes. ### Read-Write Mounts @@ -190,10 +218,13 @@ The container starts with capabilities needed for setup, then drops them before |------------|--------------|---------------------|---------| | `CAP_NET_ADMIN` | Granted | **Dropped** | iptables setup, then prevented | | `CAP_SYS_CHROOT` | Granted | **Dropped** | Entrypoint chroot, then prevented | +| `CAP_SYS_ADMIN` | Granted (chroot mode) | **Dropped** | procfs mount for Java/dotnet, then prevented | | `CAP_NET_RAW` | Denied | Denied | Prevents raw socket bypass | | `CAP_SYS_PTRACE` | Denied | Denied | Prevents process debugging | | `CAP_SYS_MODULE` | Denied | Denied | Prevents kernel module loading | +**Note:** `CAP_SYS_ADMIN` is only granted in chroot mode (v0.13.13+) for mounting procfs. It's dropped immediately after mount completes, before user commands run. + After capability drop, the process has: ``` CapInh: 0000000000000000 diff --git a/docs/environment.md b/docs/environment.md index 2c8be159b..2bbe8b762 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -17,10 +17,12 @@ awf --env-all 'command' When using `sudo -E`, these host variables are automatically passed: `GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_PERSONAL_ACCESS_TOKEN`, `USER`, `TERM`, `HOME`, `XDG_CONFIG_HOME`. -The following are always set/overridden: `HTTP_PROXY`, `HTTPS_PROXY` (Squid proxy), `PATH` (container values). +The following are always set/overridden: `PATH` (container values). Variables from `--env` flags override everything else. +**Note:** As of v0.13.5, `HTTP_PROXY` and `HTTPS_PROXY` are no longer automatically set. Traffic is transparently redirected to Squid via iptables NAT rules. If needed, you can still set these manually with `--env HTTP_PROXY=...` + ## Security Warning: `--env-all` Using `--env-all` passes all host environment variables to the container, which creates security risks: @@ -30,7 +32,9 @@ Using `--env-all` passes all host environment variables to the container, which 3. **Unnecessary Access**: Extra variables increase attack surface (violates least privilege) 4. **Accidental Sharing**: Easy to forget what's in your environment when sharing commands -**Excluded variables** (even with `--env-all`): `PATH`, `PWD`, `OLDPWD`, `SHLVL`, `_`, `SUDO_*` +**Excluded variables** (even with `--env-all`): `PATH`, `PWD`, `OLDPWD`, `SHLVL`, `_`, `SUDO_*`, `HTTP_PROXY`, `HTTPS_PROXY`, `http_proxy`, `https_proxy`, `NO_PROXY`, `no_proxy` + +**Proxy variables:** Host proxy settings are excluded to prevent conflicts with iptables-based traffic redirection. The firewall uses transparent proxying via iptables NAT rules instead of environment variable-based proxy configuration. ## Best Practices @@ -58,13 +62,14 @@ The following environment variables are set internally by the firewall and used | Variable | Description | Example | |----------|-------------|---------| | `AWF_DNS_SERVERS` | Comma-separated list of trusted DNS servers | `8.8.8.8,8.8.4.4` | -| `HTTP_PROXY` | Squid proxy URL for HTTP traffic | `http://172.30.0.10:3128` | -| `HTTPS_PROXY` | Squid proxy URL for HTTPS traffic | `http://172.30.0.10:3128` | -| `SQUID_PROXY_HOST` | Squid container hostname | `squid-proxy` | -| `SQUID_PROXY_PORT` | Squid proxy port | `3128` | +| `AWF_CHROOT_ENABLED` | Whether chroot mode is enabled | `true` | +| `AWF_HOST_PATH` | Host PATH passed to chroot environment | `/usr/local/bin:/usr/bin` | +| `NO_PROXY` | Domains bypassing Squid (host access mode) | `localhost,host.docker.internal` | **Note:** These are set automatically based on CLI options and should not be overridden manually. +**Historical note:** Prior to v0.13.5, `HTTP_PROXY` and `HTTPS_PROXY` were set to point to Squid. These have been removed in favor of transparent iptables-based redirection, which is more reliable and avoids conflicts with tools that don't honor proxy environment variables. + ## Troubleshooting **Variable not accessible:** Use `sudo -E` or pass explicitly with `--env VAR="$VAR"` diff --git a/docs/usage.md b/docs/usage.md index fd8028316..f584904fe 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -322,12 +322,13 @@ sudo awf \ ### Security Considerations -> ⚠️ **Security Warning**: When `--enable-host-access` is combined with `host.docker.internal` in `--allow-domains`, containers can access **ANY service** running on the host machine, including: -> - Local databases (PostgreSQL, MySQL, Redis) -> - Development servers -> - Other sensitive services +> ⚠️ **Security Warning**: When `--enable-host-access` is enabled, containers can access services running on the host machine via `host.docker.internal`. > -> Only enable this for trusted workloads like MCP gateways. +> **Port restrictions:** As of v0.13.13+, access is restricted to ports 80, 443, and any ports specified with `--allow-host-ports`. This prevents access to arbitrary services like databases, admin panels, etc. +> +> **Before v0.13.13:** All ports were accessible when host access was enabled, creating a security risk. +> +> Only enable this for trusted workloads like MCP gateways or local testing with Playwright. **Why opt-in?** By default, `host.docker.internal` hostname resolution is disabled to prevent containers from accessing host services. This is a defense-in-depth measure against malicious code attempting to access local resources. @@ -337,13 +338,16 @@ sudo awf \ # Start your MCP gateway on the host (port 8080) ./my-mcp-gateway --port 8080 & -# Run awf with host access enabled +# Run awf with host access enabled and custom port sudo awf \ --enable-host-access \ + --allow-host-ports 8080 \ --allow-domains host.docker.internal,api.github.com \ -- 'copilot --mcp-gateway http://host.docker.internal:8080 --prompt "test"' ``` +**Note:** Ports 80 and 443 are always allowed when `--enable-host-access` is enabled. Use `--allow-host-ports` to allow additional ports (e.g., for MCP gateways or development servers running on non-standard ports). + ### CONNECT Method on Port 80 The firewall allows the HTTP CONNECT method on both ports 80 and 443. This is required because some HTTP clients (e.g., Node.js fetch) use the CONNECT method even for HTTP connections when going through a proxy. Domain ACLs remain the primary security control.