feat: implement AllowNet filtering on Linux via slirp4netns#3
Merged
machado144 merged 11 commits intomainfrom Feb 12, 2026
Merged
feat: implement AllowNet filtering on Linux via slirp4netns#3machado144 merged 11 commits intomainfrom
machado144 merged 11 commits intomainfrom
Conversation
init now skips group/user creation when they already exist instead of erroring out, so re-running `sudo aigate init` is safe. RunPassthrough now connects stdin/stdout/stderr to the parent process so sandboxed commands can interact with the terminal.
When AllowNet is configured, RunSandboxed now creates a network-filtered namespace using slirp4netns + iptables instead of a fully isolated one. Architecture: - Process 1: unshare --net sandbox waits for tap0, sets up iptables rules allowing only DNS + resolved AllowNet IPs, then execs the target - Process 2: slirp4netns attaches to process 1's netns providing user-mode networking (no root required) DNS resolution uses the system's upstream nameservers (from systemd-resolved or /etc/resolv.conf), falling back to 8.8.8.8/1.1.1.1. Only IPv4 addresses are used in iptables rules. Falls back to unrestricted networking with a warning when slirp4netns is not installed.
Host-side DNS resolution returned different IPs than slirp4netns DNS inside the sandbox (CDN anycast / Cloudflare load balancing), causing iptables to REJECT connections that should have been allowed. Now hostnames are resolved inside the namespace via `getent ahostsv4` after the resolv.conf mount is in place, so the iptables ACCEPT rules match exactly what the sandboxed process will connect to. Also: wire slirp4netns stderr to os.Stderr so errors are visible, and add a DNS readiness wait loop before resolving hosts.
slirp4netns needs CAP_SYS_ADMIN in both the target's owning user namespace AND its own current user namespace to call setns(CLONE_NEWNET). Launching it from the host (init user namespace) as an unprivileged user fails the second check. Restructured to two-layer unshare: - Outer: unshare --user --map-root-user (user namespace only, host net) - Inner: unshare --net --mount --pid --fork (sandbox in new net ns) - slirp4netns runs inside the user ns (has caps) with host networking The orchestration script preserves terminal stdin for the backgrounded sandbox via fd 3, uses base64-encoded inner script to avoid quoting issues, and waits for the net namespace inode to change before starting slirp4netns.
The DNS readiness check used `getent ahostsv4 localhost` which resolves from /etc/hosts, not DNS. This passed immediately while slirp4netns DNS (10.0.2.3) was still starting, causing subsequent getent calls for remote hosts to silently fail — no iptables ACCEPT rules got created. Now the readiness check queries the first AllowNet host (a real remote DNS query), and each host resolution retries up to 3 times. Also suppress slirp4netns stdout (verbose protocol debug) and remount /proc in PID namespace to fix glibc "fatal library error, lookup self".
Add PlantUML diagrams for each isolation layer: - file-isolation: dual-layer ACLs + runtime mount overrides - linux-network: slirp4netns + iptables in network namespace - macos-network: sandbox-exec Seatbelt profiles - linux-process: user/mount/PID namespace architecture Embed rendered PNG images in user README and replace ASCII art in root README with the process isolation diagram.
Print active restrictions to stderr when sandbox starts so AI agents can understand what is enforced (deny_read, deny_exec, allow_net). Use icmp-admin-prohibited for iptables REJECT to give a clearer signal than generic connection-refused.
Replace /dev/null bind-mounts with a marker file containing "[aigate] access denied: this file is protected by sandbox policy" so AI agents understand why content is unavailable instead of seeing empty files. Directories get a .aigate-denied marker file inside a read-only tmpfs.
Echo allowed hosts to stderr from inside the namespace so AI agents see an explicit "[aigate] network restricted: only X, Y are reachable" message instead of just inferring from connection-refused errors.
Write /tmp/.aigate-policy inside the sandbox with all active deny_read,
deny_exec, and allow_net rules. Deny markers in files and directories
now point to this file ("Run cat /tmp/.aigate-policy to see all active
restrictions"), giving AI agents a discoverable path to understand
the full sandbox policy including network restrictions.
Change "Run 'cat /tmp/.aigate-policy'" to "See /tmp/.aigate-policy" so AI agents don't flag the marker content as a prompt injection attempt.
murilopmachado
pushed a commit
that referenced
this pull request
Mar 5, 2026
Implement network egress filtering for sandboxed processes on Linux using slirp4netns + iptables, with no root required. ## Changes - **Network filtering**: Two-layer unshare architecture (user ns + net/mount/pid ns) with slirp4netns providing user-mode networking and iptables OUTPUT chain restricting egress to allowed hosts only. DNS resolved inside the namespace to avoid CDN/anycast IP mismatches. - **Sandbox banner**: Print active restrictions (deny_read, deny_exec, allow_net) to stderr at startup so users see what's enforced. - **Explicit deny markers**: Denied files show "[aigate] access denied" message instead of appearing empty. Policy file at /tmp/.aigate-policy inside the sandbox lists all active restrictions for AI agent discoverability. - **Architecture diagrams**: PlantUML sources + rendered PNGs for file isolation, Linux/macOS network isolation, and Linux process isolation. - **Documentation**: Rewritten user guide with prerequisites, slirp4netns install instructions, and troubleshooting. Falls back gracefully: if slirp4netns is not installed, logs a warning and runs without network restrictions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
allow_netis configured, sandboxed processes now get real but restricted connectivity on Linux. Usesslirp4netnsfor user-mode networking into aunshare --netnamespace, theniptablesinside the namespace to allow only DNS + resolved AllowNet IPs (all others rejected).initis now idempotent (safe to re-run), andRunPassthroughproperly wires stdin/stdout/stderr so sandboxed commands can use the terminal.slirp4netnsis not installed, logs a warning and runs without network restrictions (instead of the previous broken--netthat killed all connectivity).How it works
RunSandboxeddispatches: AllowNet set + slirp4netns available →runWithNetFilter; otherwise →runUnsharerunWithNetFilterstarts two processes:unshare --user --net --mount --pid --fork): waits for tap0, mounts resolv.conf pointing to slirp4netns DNS (10.0.2.3), applies iptables OUTPUT rules, then execs the target command/etc/resolv.conf, filtering out 127.* stubsTest plan
go build ./...compilesgo test ./...— all 48 tests pass (new:TestResolveAllowedIPs,TestBuildNetFilterScript,TestRunSandboxedDispatch,TestGetSystemDNS,TestParseDNSFromFile)aigate run -- curl https://api.anthropic.comworks (allowed domain)aigate run -- curl https://google.comis rejected (not in AllowNet)slirp4netns→ verify warning logged and command runs unrestricted