drydock

Egress & widening

The sandbox's internet access is deny-by-default. The agent can reach only the hosts on your allowlist; everything else is blocked. This is what stops a hostile agent from exfiltrating your code or calling home.

How enforcement works

Two host-side components sit on the seam between the VM and the internet:

Inside the VM, a default-deny firewall (init-firewall.sh) allows traffic only to the gateway and proxy ports, so the agent has no path to the internet that bypasses them.

The default allowlist

~/.drydock/egress.yaml is the source of truth (the seed template lives at $HOMEBREW_PREFIX/share/drydock/config/egress.yaml):

default:
  domains:
    - { host: api.anthropic.com,      ports: [443] }   # routed via gateway
    - { host: api.openai.com,         ports: [443] }   # routed via gateway
    - { host: registry.npmjs.org,     ports: [443] }   # JavaScript, via squid
    - { host: pypi.org,               ports: [443] }   # Python, via squid
    - { host: files.pythonhosted.org, ports: [443] }   # Python, via squid
    - { host: proxy.golang.org,       ports: [443] }   # Go, via squid
    - { host: sum.golang.org,         ports: [443] }   # Go, via squid
per_task_widening:
  requires_approval: true

The sandbox image ships Node 22, Python 3.11, and Go 1.26, so JS, Python, and Go tasks work out of the box. Other toolchains: extend image/Dockerfile and rebuild with make image (or drydock init, which detects stale images). Restart brokerd after editing the default allowlist.

Per-task widening

Deny-by-default is strict on purpose, but a real task may need a host that isn't on the default list (a private registry, an internal API). Request it per task with --egress-extra:

drydock submit --repo … --instruction "…" \
  --egress-extra internal.example.com:443

When per_task_widening.requires_approval: true (the default), widening goes through the same human gate as the diff push: brokerd blocks the task, records the requested hosts to ~/.drydock/audit/<id>.widen.json, and shows it in drydock pending under gate egress. Approve it once you've reviewed:

drydock pending
drydock approve <id>

Approved hosts are reachable only by that task, for that task's lifetime: each task gets its own scoped proxy credential, isolated from other concurrent tasks. The default (non-widened) egress path is unchanged.

Plain HTTP vs HTTPS (the CONNECT edge)

squid enforces the allowlist via two different request paths, and the rules differ:

HTTPS (CONNECT tunnel): the agent opens a CONNECT tunnel to the target host. squid permits CONNECT only to port 443; a CONNECT to any other port is denied immediately, before the allowlist is consulted. Once the tunnel is open, squid cannot inspect the TLS payload, so an allowlisted HTTPS host is trusted at the host level: any URL path on that host is reachable over the tunnel. The allowlist matches by hostname (dstdomain), not by path.

Plain HTTP (forward-proxy GET): a GET http://host/... request does not use CONNECT. Rule 1 (CONNECT-only-443) does not apply, so it reaches the SSRF guard and the allowlist normally. The default allowlist lists all hosts on port 443 only, so plain HTTP (which targets port 80) is denied by default (fail-closed). This is intentional: if no operator has explicitly added a :80 entry, HTTP traffic never flows.

Asymmetry and remaining limits, stated plainly:

Troubleshooting egress

A CONNECT 403 to a host you expected to reach means it isn't on the allowlist:

cat ~/.drydock/squid/squid-default-acl.conf    # the compiled allowlist

Add it permanently in egress.yaml, or per-task with --egress-extra. If the VM reaches a host it shouldn't, confirm init-firewall.sh ran inside the VM; overriding --entrypoint skips it.