The proxy is an optional add-on. When enabled, the devtools container routes all HTTP/HTTPS traffic through a URL-allowlisting proxy. It is experimental, but it has worked well for the current developer flows using Claude, Codex, and pi. Contributions are welcome, especially from folks with Rust and HTTP/2 expertise.
- acl-proxy runs on the host, listening on ports 8881 (HTTP) and 8889 (HTTPS)
- iptables rules inside the container redirect outbound traffic to the proxy
- The proxy checks each request against a URL allowlist
- Allowed requests are forwarded; denied requests get blocked
To inspect HTTPS traffic, the proxy performs TLS termination:
- On first run, acl-proxy generates a CA (Certificate Authority) and saves it to
certs/ca-cert.pem - When a client connects to
https://github.com, the proxy:- Extracts the hostname from TLS SNI (Server Name Indication)
- Generates a certificate for
git.832008.xyzsigned by its CA (cached for reuse) - Completes the TLS handshake with the client using this certificate
- The proxy then opens its own TLS connection to the real
github.com - Traffic flows: Client ↔ Proxy (decrypted) ↔ Upstream
This is why clients must trust the proxy's CA—without it, they'd see certificate errors for every HTTPS site.
- Security: Containers can only access approved URLs
- Audit: Requests can be logged
- Control: Prevent data exfiltration, limit API access
-
iptables - required on host for proxy redirection rules
# RHEL/Fedora/Rocky sudo dnf install iptables # Debian/Ubuntu sudo apt install iptables
-
openssl-devel - required to build acl-proxy
# RHEL/Fedora/Rocky sudo dnf install openssl-devel # Debian/Ubuntu sudo apt install libssl-dev
git clone https://github.com/kcosr/acl-proxy.git
cd acl-proxy
cargo install --path .This installs acl-proxy to ~/.cargo/bin/.
acl-proxy performs HTTPS interception (MITM). Clients must trust its CA.
Since this setup bind-mounts certificate directories (/etc/pki/ca-trust, /etc/pki/tls) read-only from the host, installing the CA on the host makes it automatically available inside containers.
# Start proxy to generate CA (first time only, run from devtools/)
acl-proxy --config proxy/acl-proxy.toml
# Install CA system-wide (RHEL/Fedora/Rocky)
# certs/ is created in the working directory where acl-proxy runs
sudo cp certs/ca-cert.pem /etc/pki/ca-trust/source/anchors/acl-proxy-ca.pem
sudo update-ca-trust
# Install CA system-wide (Debian/Ubuntu)
sudo cp certs/ca-cert.pem /usr/local/share/ca-certificates/acl-proxy-ca.crt
sudo update-ca-certificatesNote: If you use a self-contained container (not bind-mounting
/etc), you'll need to copy the CA cert into the image and runupdate-ca-trustduring the build.
Edit proxy/acl-proxy.toml:
[policy]
default = "deny"
[[policy.rules]]
action = "allow"
pattern = "https://api.github.com/**"
description = "GitHub API"
[[policy.rules]]
action = "allow"
pattern = "https://registry.npmjs.org/**"
description = "NPM registry"acl-proxy --config proxy/acl-proxy.tomlPROXY=1 ./container/run.shWhen PROXY=1, run.sh uses nsenter to set up these rules in the container's network namespace:
# NAT table - redirect HTTP/HTTPS
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN # skip loopback
iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to 169.254.1.2:8881
iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to 169.254.1.2:8889
# Filter table - default deny
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT # DNS
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT # DNS
iptables -A OUTPUT -p tcp -d 169.254.1.2 --dport 8881 -j ACCEPT
iptables -A OUTPUT -p tcp -d 169.254.1.2 --dport 8889 -j ACCEPT
iptables -A OUTPUT -j DROP # deny all elseThe final DROP rule ensures all outbound traffic not explicitly allowed is blocked. Only DNS and proxy traffic can leave the container—everything else must go through the proxy to reach the network.
169.254.1.2 is host.containers.internal in podman.
The proxy can inject real GitHub credentials without exposing tokens inside the container. This works by:
Note: Use Git over HTTPS (not SSH) so requests go through the acl-proxy and can be allowlisted/filtered. SSH traffic does not traverse the HTTP/HTTPS proxy.
-
Dummy credentials in container - The overlay provides:
~/.config/gh/hosts.yml- dummy token forghCLI~/.gitconfig- inline credential helper that returns dummy creds for git
Why inline helper? The standard
credential.helper = storetries to write to~/.git-credentials. Since overlay files are bind-mounted read-only, the write fails and breaks auth. The inline function just echoes credentials without writing. -
Proxy replaces the header - When
ghorgitsends anAuthorizationheader, the proxy replaces it with the real token.
Ensure your overlay files are in place and updated (see README.md for overlay setup), including:
container/overlay/home/<username>/.config/gh/hosts.yml- set your GitHub usernamecontainer/overlay/home/<username>/.gitconfig- set your name/email
GitHub requires different formats for different endpoints:
API (api.github.com) - token format:
[[policy.rules.header_actions]]
direction = "request"
action = "set"
name = "authorization"
value = "token ghp_YOUR_TOKEN"
when = "if_present"Git over HTTPS (github.com) - Basic auth format:
# Generate the value:
echo -n "ghp_YOUR_TOKEN:x-oauth-basic" | base64[[policy.rules.header_actions]]
direction = "request"
action = "set"
name = "authorization"
value = "Basic <base64_output>"
when = "if_present"- Requests with an auth header → replaced with real credentials
- Requests without an auth header → pass through unauthenticated
This means:
- Tools that send auth (gh, git with credential helper) get real credentials
- Unauthenticated requests (curl without
-u) stay unauthenticated - The real token never exists inside the container
The proxy-iptables.sh script manages iptables rules for any running container:
./container/proxy-iptables.sh <command> <container-name>| Command | Description |
|---|---|
add |
Add proxy redirect rules |
remove |
Remove all rules (disable proxy) |
status |
Show current iptables rules |
# Check current rules
./container/proxy-iptables.sh status devcontainer
# Disable proxy (flush rules)
./container/proxy-iptables.sh remove devcontainer
# Re-enable proxy
./container/proxy-iptables.sh add devcontainerYou can also use nsenter directly:
PID=$(podman inspect --format '{{.State.Pid}}' devcontainer)
# View NAT rules
sudo nsenter -t $PID -n iptables -t nat -L OUTPUT -n
# View filter rules
sudo nsenter -t $PID -n iptables -L OUTPUT -n
# Flush all rules
sudo nsenter -t $PID -n iptables -t nat -F OUTPUT
sudo nsenter -t $PID -n iptables -F OUTPUT- nsenter requires sudo on the host to enter the container's network namespace
- Container has no network capabilities - users inside cannot modify iptables
- Rules are per-container - each container has its own network namespace
The URL is not in your allowlist. Check proxy/acl-proxy.toml.
The CA certificate isn't trusted. Make sure you ran update-ca-trust.
Check that acl-proxy is running on the host:
curl https://api.github.com/zenFrom inside the container, test that dummy auth gets replaced with real credentials:
# Should work - gh sends auth header, proxy replaces with real token
gh repo list --limit 3
# Should work - curl with dummy auth header triggers replacement
curl -H "Authorization: token dummy" https://api.github.com/user
# Should fail (401) - no auth header sent, so none injected
curl https://api.github.com/user