Skip to main content
The Docker sandbox provides isolated, reproducible environments for running security tests locally. Each sandbox is a Docker container running a customized Ubuntu-based image with security testing tools pre-installed.

Architecture

DockerRuntime Class

Implemented in esprit/runtime/docker_runtime.py, the DockerRuntime manages sandbox lifecycle:

Initialization

class DockerRuntime(AbstractRuntime):
    def __init__(self) -> None:
        docker_timeout = int(Config.get("esprit_sandbox_timeout") or "60")
        self.client = docker.from_env(timeout=docker_timeout)
Requirements:
  • Docker Desktop installed and running
  • Docker daemon accessible via Docker socket
  • Sufficient Docker resources (2+ GB RAM, 10+ GB disk)

Configuration

Environment variables:
VariableDefaultDescription
ESPRIT_IMAGE(required)Docker image name (e.g., esprit/sandbox:latest)
ESPRIT_SANDBOX_TIMEOUT60Docker API timeout in seconds
ESPRIT_SANDBOX_EXECUTION_TIMEOUT120Tool execution timeout in seconds
ESPRIT_DOCKER_PLATFORM(auto)Platform override (e.g., linux/amd64, linux/arm64)

Container Lifecycle

1. Container Creation

Method: _create_container() (lines 137-197)
  1. Remove existing container with same name if present
  2. Find available port for tool server (random port 1024-65535)
  3. Generate auth token (32-byte URL-safe random token)
  4. Pull image if not cached locally
  5. Create container with configuration:
    container = client.containers.run(
        image_name,
        command="sleep infinity",
        detach=True,
        name=f"esprit-scan-{scan_id}",
        ports={"48081/tcp": tool_server_port},
        cap_add=["NET_ADMIN", "NET_RAW"],
        environment={
            "TOOL_SERVER_PORT": "48081",
            "TOOL_SERVER_TOKEN": token,
            "ESPRIT_SANDBOX_EXECUTION_TIMEOUT": "120",
        },
        extra_hosts={"host.docker.internal": "host-gateway"},
    )
    
  6. Wait for tool server to become healthy (max 30 attempts)
Capabilities:
  • NET_ADMIN: Required for network manipulation (proxy setup)
  • NET_RAW: Required for raw socket access (port scanning)

2. Tool Server Health Check

Method: _wait_for_tool_server() (lines 89-135)
health_url = f"http://127.0.0.1:{tool_server_port}/health"
response = client.get(health_url)
if response.json().get("status") == "healthy":
    return  # Server ready
Health check loop:
  • Initial 5-second delay for container startup
  • Poll every 0.5-5 seconds (exponential backoff)
  • Max 30 attempts (configurable)
  • Monitors container status (fails fast if container exits)
Failure scenarios:
  • Container exits during initialization → includes container logs in error
  • Container removed during initialization → “Container was removed”
  • Timeout after 30 attempts → includes last 50 lines of container logs

3. Container Reuse

Method: _get_or_create_container() (lines 199-243) Containers are reused across agents in the same scan:
container_name = f"esprit-scan-{scan_id}"
if existing_container := client.containers.get(container_name):
    if existing_container.status == "running":
        return existing_container
    else:
        existing_container.start()
Benefits:
  • Faster agent initialization
  • Shared workspace state
  • Resource efficiency

4. Local Source Copying

Method: _copy_local_directory_to_container() (lines 275-312) When scanning local code, files are copied to container’s /workspace:
  1. Create tar archive of local directory
  2. Exclude directories:
    • node_modules, .git, __pycache__
    • .venv, venv, .env, env
    • dist, build, .next, .nuxt
    • target, vendor, .bundle
  3. Upload tar to container via Docker API
  4. Set permissions: chown -R pentester:pentester /workspace
  5. Track copied state to avoid duplicate copying
Example:
local_sources = [
    {
        "source_path": "/Users/me/my-app",
        "workspace_subdir": "my-app"
    }
]
# Results in: /workspace/my-app/ in container

5. Sandbox Info Response

Method: create_sandbox() returns SandboxInfo:
return {
    "workspace_id": container.id,        # Docker container ID
    "api_url": "http://127.0.0.1:54321", # Tool server URL
    "auth_token": "<token>",              # Bearer token
    "tool_server_port": 54321,            # Mapped port
    "agent_id": "agent-123",              # Agent identifier
}

6. Container Cleanup

Method: destroy_sandbox() (lines 406-415)
container = client.containers.get(container_id)
container.stop()      # Graceful stop
container.remove()    # Delete container
Automatic cleanup:
  • On normal scan completion
  • On CLI process exit (via cleanup() method)
  • Cleanup uses background subprocess to avoid blocking

Workspace Diffs

Method: get_workspace_diffs() (lines 386-404) Retrieves list of file edits made during scan:
resp = requests.get(
    f"http://127.0.0.1:{tool_server_port}/diffs",
    headers={"Authorization": f"Bearer {token}"},
)
edits = resp.json().get("edits", [])
Each edit record contains:
  • command: “edit”, “write”, “delete”
  • path: File path relative to workspace
  • old_str: Original content (for edits)
  • new_str: New content (for edits)
  • content: Full content (for writes)

Network Configuration

Port Mapping

  • Container port 48081 → Random host port (e.g., 54321)
  • Tool server binds to 0.0.0.0:48081 inside container
  • Esprit CLI connects to 127.0.0.1:<random_port>

Host Access

extra_hosts={"host.docker.internal": "host-gateway"}
Allows container to access host machine:
  • Useful for scanning localhost services
  • host.docker.internal resolves to host IP

Docker Host Resolution

Method: _resolve_docker_host() (lines 376-384)
if docker_host := os.getenv("DOCKER_HOST"):
    # Parse tcp://192.168.1.100:2375 → 192.168.1.100
    return parsed.hostname
return "127.0.0.1"
Supports remote Docker daemons.

Container Image

The Esprit sandbox image (ESPRIT_IMAGE) includes:

Base System

  • Ubuntu 22.04 LTS
  • Python 3.11+
  • Node.js 20+ (for browser automation)

Security Tools

  • nmap, nikto, sqlmap
  • ffuf, gobuster, dirb
  • whatweb, wpscan
  • Custom scanning scripts

Tool Server

  • FastAPI application
  • Esprit tools package
  • Playwright browsers (Chromium)
  • mitmproxy for HTTP interception

User Configuration

  • Non-root user: pentester (UID 1000)
  • Home directory: /home/pentester
  • Workspace: /workspace (writable)

Error Handling

raise SandboxInitializationError(
    "Docker is not available",
    "Please ensure Docker Desktop is installed and running.",
)
Thrown when:
  • Docker daemon not running
  • Docker socket not accessible
  • Connection timeout
Retry logic with exponential backoff:
  • Max 3 attempts
  • Delays: 1s, 2s, 4s
  • Verifies image metadata after pull
raise SandboxInitializationError(
    "Failed to create container",
    f"Container creation failed after 3 attempts: {error}",
)
Includes:
  • Container logs (last 50 lines)
  • Exit code if container exited
  • Detailed error message
raise SandboxInitializationError(
    "Tool server failed to start",
    f"Container logs:\n{logs}",
)
Includes full container logs for debugging.

Performance Optimization

Container Reuse

  • Single container per scan (shared across agents)
  • Avoids repeated image pulls
  • Maintains tool state (browser sessions, terminals)

Async Operations

  • HTTP requests use httpx.AsyncClient
  • Non-blocking tool execution
  • Parallel agent support

Resource Management

  • Tool server timeout prevents runaway processes
  • Container memory limits (configurable via Docker)
  • Automatic cleanup on exit

Troubleshooting

  1. Check Docker daemon: docker ps
  2. Check image exists: docker images | grep esprit
  3. Check port conflicts: lsof -i :<port>
  4. Check Docker resources: Increase memory/CPU in Docker Desktop
  5. View container logs: docker logs esprit-scan-<scan_id>
  1. Check health endpoint: curl http://127.0.0.1:<port>/health
  2. Check container status: docker ps -a
  3. Check firewall/network settings
  4. Increase timeout: ESPRIT_SANDBOX_TIMEOUT=120
  1. Ensure files are owned by pentester user
  2. Check container: docker exec -it esprit-scan-<id> ls -la /workspace
  3. Fix permissions: Container automatically runs chown on upload

Next Steps

Cloud Runtime

Alternative to local Docker setup

Tools

Learn about available tools in sandbox

Build docs developers (and LLMs) love