From 9b11fbf3b88b9eb71e78dc91c0fd423792f327da Mon Sep 17 00:00:00 2001 From: eligotts <78387377+eligotts@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:53:05 -0700 Subject: [PATCH] feat(v1): DockerConfig.network_access to block the workload's internet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `network_access: bool = True` to DockerConfig (mirroring prime/modal, so `--harness.runtime.network-access false` now works uniformly across all three runtimes). When false, the container's DNS resolver is disabled so every hostname-based fetch (pip install, git clone, curl ) fails — while the interception model tunnel keeps working, because it egresses from the HOST, not the container: the container only reaches host.docker.internal / host loopback, which needs no DNS. Platform-specific (matching the existing host_url() split): - Docker Desktop (macOS/Windows): drop --network host for the bridge, add --dns 0.0.0.0 and --add-host=host.docker.internal:host-gateway (host route pinned in /etc/hosts, resolves without DNS). - Linux: keep --network host (interception is reachable at 127.0.0.1) and add --dns 0.0.0.0. --dns writes a container-private resolv.conf, so the host's own DNS is untouched. A DNS-level block, not an IP-level airgap: a hardcoded raw IP could still egress; use an iptables egress allowlist if that matters. Verified end-to-end: a local-docker eval with --harness.runtime.network-access false solves a task (reward 1.0, 59 model calls over the tunnel) while pip/git to public hosts fail from inside the container. Co-Authored-By: Claude Opus 4.8 --- verifiers/v1/runtimes/docker.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/verifiers/v1/runtimes/docker.py b/verifiers/v1/runtimes/docker.py index 3fc689c8aa..59a536d1e7 100644 --- a/verifiers/v1/runtimes/docker.py +++ b/verifiers/v1/runtimes/docker.py @@ -38,6 +38,11 @@ class DockerConfig(BaseConfig): disk: float | None = None """Advisory disk request in GB. Docker has no portable per-container size limit, so this is accepted (so a task can declare it without a warning) but not enforced.""" + network_access: bool = True + """Whether the workload can reach the internet. When False, the container's DNS is disabled + so hostname fetches (pip, git clone, curl) fail, while the model tunnel still works — it + egresses from the host, which the container reaches DNS-lessly. A DNS block, not an IP + airgap. Mirrors prime/modal (which also sever the tunnel).""" class DockerRuntimeInfo(DockerConfig, BaseRuntimeInfo): @@ -98,8 +103,7 @@ async def start(self) -> None: run = await docker( "run", "--detach", - "--network", - "host", + *self._network_args(), *limits, "--workdir", self.config.workdir, @@ -115,11 +119,25 @@ async def start(self) -> None: :12 ] # `docker run -d` prints the container id logger.info( - "docker: started container %s (image=%s)", + "docker: started container %s (image=%s%s)", self._container, self.config.image, + "" if self.config.network_access else ", internet blocked", ) + def _network_args(self) -> list[str]: + """`docker run` networking. network_access=False breaks DNS to block the workload's + internet while keeping the DNS-less host route the model tunnel needs.""" + if self.config.network_access: + return ["--network", "host"] + if sys.platform == "linux": + # Host netns reaches the interception at 127.0.0.1; only DNS is broken. --dns writes + # a container-private resolv.conf, so the host's DNS is untouched. + return ["--network", "host", "--dns", "0.0.0.0"] + # Docker Desktop: bridge instead of host, host.docker.internal pinned via /etc/hosts + # (host_url() already routes the tunnel there off-Linux). + return ["--dns", "0.0.0.0", "--add-host", "host.docker.internal:host-gateway"] + def host_url(self, url: str) -> str: # Docker Desktop (macOS/Windows) runs containers in a VM, so `--network host` # doesn't reach the host's loopback; `host.docker.internal` does.