diff --git a/.env.example b/.env.example index f1ead384..232ff5e2 100644 --- a/.env.example +++ b/.env.example @@ -20,9 +20,18 @@ KVM_DEVICE_PATH=/dev/kvm SANDBOX_USE_CGROUPV2=true SANDBOX_LOG_LEVEL=DEBUG SANDBOX_MAX_PROCESS_COUNT=100 -SANDBOX_RUN_CPU_TIME=10000 -SANDBOX_RUN_TIMEOUT=15000 -SANDBOX_OUTPUT_MAX_SIZE=65536 + +# Execution limits passed through by docker-compose.yaml (Compose defaults shown). +# SANDBOX_RUN_TIMEOUT and SANDBOX_RUN_CPU_TIME bound one sandbox run (ms). +# JOB_TIMEOUT bounds the whole job, including file transfer, in the API, worker +# and egress gateway (ms); raise it together with SANDBOX_RUN_TIMEOUT. +# SANDBOX_OUTPUT_MAX_SIZE caps each output stream (bytes). MAX_FILE_SIZE caps +# each uploaded file (bytes). +# SANDBOX_RUN_TIMEOUT=300000 +# SANDBOX_RUN_CPU_TIME=300000 +# SANDBOX_OUTPUT_MAX_SIZE=65536 +# JOB_TIMEOUT=300000 +# MAX_FILE_SIZE=26214400 # Docker Compose keeps hardened mode enabled. Its API exposes bridge routes even # with the default HTTP sandbox backend, so configure a private enrollment token diff --git a/README.md b/README.md index 94ad3b9c..ef1d78ab 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,13 @@ the resolver handoff and rootfs assembly without requiring KVM. Setting `KVM_ENABLED=false` still selects the directory-root target and the host package mount automatically for direct NsJail development. +Compose reads the execution limits from `.env`: `SANDBOX_RUN_TIMEOUT` and +`SANDBOX_RUN_CPU_TIME` (default `300000` ms each), `SANDBOX_OUTPUT_MAX_SIZE` +(default `65536` bytes per stream), `JOB_TIMEOUT` (default `300000` ms, applied +to the API, worker and egress gateway) and `MAX_FILE_SIZE` (default `26214400` +bytes per uploaded file). Raise `JOB_TIMEOUT` together with +`SANDBOX_RUN_TIMEOUT`; the job deadline also covers file transfer. + Local Docker Compose files set `CODEAPI_INTERNAL_SERVICE_TOKEN` to a shared development value by default. Production deployments must override it with a strong secret; when it is unset, file object routes and Tool Call Server diff --git a/docker-compose.yaml b/docker-compose.yaml index a4a43cdd..8ca7f9fa 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -45,6 +45,8 @@ services: - EXECUTION_MANIFEST_MAX_OUTPUT_FILES=${EXECUTION_MANIFEST_MAX_OUTPUT_FILES:-} - EXECUTION_MANIFEST_MAX_REQUESTS=${EXECUTION_MANIFEST_MAX_REQUESTS:-} - CODEAPI_HTTP_JSON_LIMIT=${CODEAPI_HTTP_JSON_LIMIT:-50mb} + - MAX_FILE_SIZE=${MAX_FILE_SIZE:-26214400} + - JOB_TIMEOUT=${JOB_TIMEOUT:-300000} - SERVICE_PORT=3112 - SANDBOX_ENDPOINT=http://sandbox-runner:2000/api/v2 - EGRESS_GATEWAY_URL=http://egress_gateway:3190 @@ -95,6 +97,7 @@ services: - REDIS_PASSWORD=localdev - PYTHON_CONCURRENCY=${PYTHON_CONCURRENCY:-1} - OTHER_CONCURRENCY=${OTHER_CONCURRENCY:-8} + - JOB_TIMEOUT=${JOB_TIMEOUT:-300000} - WORKER_HEALTH_PORT=3113 depends_on: redis: @@ -118,6 +121,7 @@ services: - EGRESS_GATEWAY_FILE_SERVER_URL=http://file_server:3000 - EGRESS_GATEWAY_TOOL_CALL_SERVER_URL=http://tool_call_server:3033 - EGRESS_GATEWAY_MAX_TOOL_CALL_BYTES=1048576 + - JOB_TIMEOUT=${JOB_TIMEOUT:-300000} - CODEAPI_EGRESS_GRANT_SECRET=${CODEAPI_EGRESS_GRANT_SECRET:-localdev-egress-grant-secret-change-me-32b} - CODEAPI_INTERNAL_SERVICE_TOKEN=${CODEAPI_INTERNAL_SERVICE_TOKEN:-localdev-internal-service-token} - REDIS_HOST=redis @@ -169,15 +173,15 @@ services: - SANDBOX_EXECUTE_BODY_LIMIT=${SANDBOX_EXECUTE_BODY_LIMIT:-50mb} - SANDBOX_PACKAGES_DIRECTORY=/pkgs - SANDBOX_LOG_LEVEL=DEBUG - - SANDBOX_OUTPUT_MAX_SIZE=65536 + - SANDBOX_OUTPUT_MAX_SIZE=${SANDBOX_OUTPUT_MAX_SIZE:-65536} - SANDBOX_MAX_CONCURRENT_JOBS=${SANDBOX_MAX_CONCURRENT_JOBS:-8} - SANDBOX_PER_JOB_UIDS=${SANDBOX_PER_JOB_UIDS:-true} - SANDBOX_JOB_UID_BASE=${SANDBOX_JOB_UID_BASE:-200000} - SANDBOX_JOB_GID_BASE=${SANDBOX_JOB_GID_BASE:-200000} - SANDBOX_JOB_UID_COUNT=${SANDBOX_JOB_UID_COUNT:-} - SANDBOX_WORKSPACE_REAPER_MAX_AGE_SECONDS=${SANDBOX_WORKSPACE_REAPER_MAX_AGE_SECONDS:-3600} - - SANDBOX_RUN_TIMEOUT=300000 - - SANDBOX_RUN_CPU_TIME=300000 + - SANDBOX_RUN_TIMEOUT=${SANDBOX_RUN_TIMEOUT:-300000} + - SANDBOX_RUN_CPU_TIME=${SANDBOX_RUN_CPU_TIME:-300000} - SANDBOX_COMPILE_TIMEOUT=30000 - LAUNCHER_VCPUS=2 - LAUNCHER_RAM_MIB=2048 diff --git a/tests/compose-bridge-config.cjs b/tests/compose-bridge-config.cjs index 79268b52..31591eaf 100644 --- a/tests/compose-bridge-config.cjs +++ b/tests/compose-bridge-config.cjs @@ -23,6 +23,11 @@ function render(overrides) { CODEAPI_BRIDGE_RECOVERY_MAX_CHALLENGES_PER_MINUTE: '', CODEAPI_BRIDGE_RECOVERY_MAX_ATTEMPTS_PER_MINUTE: '', CODEAPI_BRIDGE_RECOVERY_MAX_UNTRUSTED_PER_MINUTE: '', + SANDBOX_RUN_TIMEOUT: '', + SANDBOX_RUN_CPU_TIME: '', + SANDBOX_OUTPUT_MAX_SIZE: '', + JOB_TIMEOUT: '', + MAX_FILE_SIZE: '', ...overrides, }, })); @@ -76,4 +81,34 @@ for (const overrides of [ assert.ok(config.services.redis.volumes.some(volume => volume.target === '/data' && volume.type === 'volume')); } assert.equal(render({}).services.api.environment.CODEAPI_BRIDGE_TOKEN, ''); -console.log('Compose bridge configuration passed (pairing, opt-in recovery, durable Redis).'); + +const limitServices = { + SANDBOX_RUN_TIMEOUT: ['sandbox-runner'], + SANDBOX_RUN_CPU_TIME: ['sandbox-runner'], + SANDBOX_OUTPUT_MAX_SIZE: ['sandbox-runner'], + JOB_TIMEOUT: ['api', 'service-worker', 'egress_gateway'], + MAX_FILE_SIZE: ['api'], +}; +const limitDefaults = { + SANDBOX_RUN_TIMEOUT: '300000', + SANDBOX_RUN_CPU_TIME: '300000', + SANDBOX_OUTPUT_MAX_SIZE: '65536', + JOB_TIMEOUT: '300000', + MAX_FILE_SIZE: '26214400', +}; +const limitOverrides = { + SANDBOX_RUN_TIMEOUT: '600000', + SANDBOX_RUN_CPU_TIME: '600000', + SANDBOX_OUTPUT_MAX_SIZE: '16384', + JOB_TIMEOUT: '615000', + MAX_FILE_SIZE: '104857600', +}; +for (const [overrides, expected] of [[{}, limitDefaults], [limitOverrides, limitOverrides]]) { + const config = render(overrides); + for (const [name, services] of Object.entries(limitServices)) { + for (const service of services) { + assert.equal(config.services[service].environment[name], expected[name], `${service} ${name}`); + } + } +} +console.log('Compose bridge configuration passed (pairing, opt-in recovery, durable Redis, execution limits).');