Add Docker sandbox host mappings (#488)

* Add Docker sandbox host mappings

* Address docker extra hosts review feedback

* Revert README change for STRIX_SANDBOX_EXTRA_HOSTS

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: 0xallam <ahmed39652003@gmail.com>
This commit is contained in:
Chethas Dileep
2026-05-19 14:19:22 +05:30
committed by GitHub
parent 6b9bd4d5f2
commit dc395316ae
3 changed files with 127 additions and 10 deletions
+1
View File
@@ -44,6 +44,7 @@ class Config:
strix_runtime_backend = "docker"
strix_sandbox_execution_timeout = "120"
strix_sandbox_connect_timeout = "10"
strix_sandbox_extra_hosts = None
# Telemetry
strix_telemetry = "1"
+39 -10
View File
@@ -2,9 +2,13 @@ import contextlib
import os
import secrets
import socket
import subprocess
import tarfile
import time
from io import BytesIO
from pathlib import Path
from typing import cast
from urllib.parse import urlparse
import docker
import httpx
@@ -47,14 +51,14 @@ class DockerRuntime(AbstractRuntime):
def _get_scan_id(self, agent_id: str) -> str:
try:
from strix.telemetry.tracer import get_global_tracer
from strix.telemetry.tracer import get_global_tracer # noqa: PLC0415
tracer = get_global_tracer()
if tracer and tracer.scan_config:
return str(tracer.scan_config.get("scan_id", "default-scan"))
except (ImportError, AttributeError):
pass
return f"scan-{agent_id.split('-')[0]}"
return f"scan-{agent_id.split('-', maxsplit=1)[0]}"
def _verify_image_available(self, image_name: str, max_retries: int = 3) -> None:
for attempt in range(max_retries):
@@ -108,6 +112,33 @@ class DockerRuntime(AbstractRuntime):
"Container initialization timed out. Please try again.",
)
def _get_extra_hosts(self) -> dict[str, str]:
extra_hosts = {HOST_GATEWAY_HOSTNAME: "host-gateway"}
configured_hosts = Config.get("strix_sandbox_extra_hosts")
if not configured_hosts:
return extra_hosts
for raw_host_entry in configured_hosts.split(","):
host_entry = raw_host_entry.strip()
if not host_entry:
continue
parts = [part.strip() for part in host_entry.split("=")]
if len(parts) != 2:
raise ValueError(
"STRIX_SANDBOX_EXTRA_HOSTS entries must use hostname=address format"
)
hostname, address = parts
if not hostname or not address:
raise ValueError(
"STRIX_SANDBOX_EXTRA_HOSTS entries must include both hostname and address"
)
extra_hosts[hostname] = address
return extra_hosts
def _create_container(self, scan_id: str, max_retries: int = 2) -> Container:
container_name = f"strix-scan-{scan_id}"
image_name = Config.get("strix_image")
@@ -150,7 +181,7 @@ class DockerRuntime(AbstractRuntime):
"STRIX_SANDBOX_EXECUTION_TIMEOUT": str(execution_timeout),
"HOST_GATEWAY": HOST_GATEWAY_HOSTNAME,
},
extra_hosts={HOST_GATEWAY_HOSTNAME: "host-gateway"},
extra_hosts=self._get_extra_hosts(),
tty=True,
)
@@ -164,6 +195,11 @@ class DockerRuntime(AbstractRuntime):
self._tool_server_token = None
self._caido_port = None
time.sleep(2**attempt)
except ValueError as e:
raise SandboxInitializationError(
"Invalid Docker sandbox host mapping",
str(e),
) from e
else:
return container
@@ -222,9 +258,6 @@ class DockerRuntime(AbstractRuntime):
def _copy_local_directory_to_container(
self, container: Container, local_path: str, target_name: str | None = None
) -> None:
import tarfile
from io import BytesIO
try:
local_path_obj = Path(local_path).resolve()
if not local_path_obj.exists() or not local_path_obj.is_dir():
@@ -312,8 +345,6 @@ class DockerRuntime(AbstractRuntime):
def _resolve_docker_host(self) -> str:
docker_host = os.getenv("DOCKER_HOST", "")
if docker_host:
from urllib.parse import urlparse
parsed = urlparse(docker_host)
if parsed.scheme in ("tcp", "http", "https") and parsed.hostname:
return parsed.hostname
@@ -342,8 +373,6 @@ class DockerRuntime(AbstractRuntime):
if container_name is None:
return
import subprocess
subprocess.Popen( # noqa: S603
["docker", "rm", "-f", container_name], # noqa: S607
stdout=subprocess.DEVNULL,
+87
View File
@@ -0,0 +1,87 @@
from types import SimpleNamespace
from unittest.mock import MagicMock
import pytest
from docker.errors import NotFound
from strix.runtime import SandboxInitializationError
from strix.runtime.docker_runtime import HOST_GATEWAY_HOSTNAME, DockerRuntime
def test_get_extra_hosts_includes_host_gateway(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("STRIX_SANDBOX_EXTRA_HOSTS", raising=False)
runtime = DockerRuntime.__new__(DockerRuntime)
assert runtime._get_extra_hosts() == {HOST_GATEWAY_HOSTNAME: "host-gateway"}
def test_get_extra_hosts_merges_configured_entries(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv(
"STRIX_SANDBOX_EXTRA_HOSTS",
"test.internal.lan=host-gateway, api.local = 192.168.1.20",
)
runtime = DockerRuntime.__new__(DockerRuntime)
assert runtime._get_extra_hosts() == {
HOST_GATEWAY_HOSTNAME: "host-gateway",
"test.internal.lan": "host-gateway",
"api.local": "192.168.1.20",
}
def test_get_extra_hosts_rejects_invalid_entries(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("STRIX_SANDBOX_EXTRA_HOSTS", "test.internal.lan")
runtime = DockerRuntime.__new__(DockerRuntime)
with pytest.raises(ValueError, match="hostname=address"):
runtime._get_extra_hosts()
def test_get_extra_hosts_rejects_multiple_equals(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("STRIX_SANDBOX_EXTRA_HOSTS", "test.internal.lan==host-gateway")
runtime = DockerRuntime.__new__(DockerRuntime)
with pytest.raises(ValueError, match="hostname=address"):
runtime._get_extra_hosts()
def test_create_container_passes_configured_extra_hosts(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("STRIX_SANDBOX_EXTRA_HOSTS", "test.internal.lan=host-gateway")
run = MagicMock(return_value=object())
containers = SimpleNamespace(get=MagicMock(side_effect=NotFound("missing")), run=run)
runtime = DockerRuntime.__new__(DockerRuntime)
runtime.client = SimpleNamespace(containers=containers)
runtime._verify_image_available = MagicMock()
runtime._find_available_port = MagicMock(side_effect=[12345, 12346])
runtime._wait_for_tool_server = MagicMock()
runtime._scan_container = None
runtime._create_container("scan-id")
assert run.call_args.kwargs["extra_hosts"] == {
HOST_GATEWAY_HOSTNAME: "host-gateway",
"test.internal.lan": "host-gateway",
}
def test_create_container_wraps_invalid_extra_hosts(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("STRIX_SANDBOX_EXTRA_HOSTS", "test.internal.lan")
run = MagicMock()
containers = SimpleNamespace(get=MagicMock(side_effect=NotFound("missing")), run=run)
runtime = DockerRuntime.__new__(DockerRuntime)
runtime.client = SimpleNamespace(containers=containers)
runtime._verify_image_available = MagicMock()
runtime._find_available_port = MagicMock(side_effect=[12345, 12346])
runtime._wait_for_tool_server = MagicMock()
runtime._scan_container = None
with pytest.raises(SandboxInitializationError, match="Invalid Docker sandbox host mapping"):
runtime._create_container("scan-id")
run.assert_not_called()