Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ APP_ENV=development
GATEWAY_PORT=8000
LOG_LEVEL=INFO
ENABLE_DOCS=false
# Set PUBLIC_BETA_MODE=true, or APP_ENV=public_beta, before exposing this
# to arbitrary untrusted beta users. Public beta mode rejects risky sandbox
# features and requires a stronger runtime such as gVisor/runsc or Kata.
PUBLIC_BETA_MODE=false

# --- Authentication ---
REQUIRE_AUTH=true
Expand All @@ -32,6 +36,12 @@ CORS_ALLOW_CREDENTIALS=true

# --- Docker and state ---
USE_DOCKER_DEFAULT_SECCOMP=true
DOCKER_CLIENT_TIMEOUT=30
# Optional stronger isolation runtime configured on the Docker daemon host.
# Examples: runsc for gVisor, kata-runtime for Kata Containers.
SANDBOX_RUNTIME=
STRONG_SANDBOX_RUNTIMES=runsc,kata,kata-runtime,io.containerd.runsc.v1,io.containerd.kata.v2
REQUIRE_STRONG_SANDBOX_ISOLATION=false
# If you disable Docker's RuntimeDefault seccomp policy, point this at a file
# path that exists on the Docker daemon host. The checked-in profile under
# ./security is only a source artifact; the daemon cannot read it from inside
Expand Down Expand Up @@ -61,6 +71,9 @@ MAX_CONCURRENT_EXECUTIONS=10
MAX_ACTIVE_SESSIONS=100
MAX_CONTAINERS_PER_PRINCIPAL=3
CONTAINER_CREATE_GUARD_TIMEOUT=30
SESSION_TIMEOUT_SECONDS=1200
MAX_SESSION_LIFETIME_SECONDS=3600
MAX_EXECUTIONS_PER_SESSION=100
DEFAULT_TIMEOUT=30
MAX_TIMEOUT=120
RATE_LIMIT_REQUESTS_PER_WINDOW=30
Expand Down
58 changes: 53 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ jobs:
- name: Install gateway test dependencies
run: python -m pip install -r gateway/requirements.txt
- name: Compile Python sources
run: python -m compileall gateway sandbox tests/verification_client.py tests/verify_vm_flow.py tests/verify_playwright.py tests/verify_features.py tests/test_execution.py tests/test_gateway_unit.py .github/scripts
run: python -m compileall gateway sandbox tests/verification_client.py tests/verify_vm_flow.py tests/verify_playwright.py tests/verify_features.py tests/test_execution.py tests/test_gateway_unit.py tests/test_executor_unit.py .github/scripts
- name: Run unit tests
run: python -m unittest -q tests/test_gateway_unit.py
run: python -m unittest -q tests/test_gateway_unit.py tests/test_executor_unit.py
- name: Validate version metadata
run: |
python - <<'PY'
Expand All @@ -53,7 +53,7 @@ jobs:
print(f"Validated version {version}")
PY
- name: Run Bandit
run: bandit -q -r gateway sandbox
run: bandit -q -r gateway sandbox -s B102,B108,B404,B603
- name: Audit gateway dependencies
run: pip-audit -r gateway/requirements.txt
- name: Audit sandbox dependencies
Expand All @@ -62,6 +62,8 @@ jobs:
integration:
runs-on: ubuntu-latest
needs: validate
env:
CONTAINER_RATE_LIMIT_REQUESTS_PER_WINDOW: "30"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build images
Expand Down Expand Up @@ -91,14 +93,14 @@ jobs:
API_TOKEN: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
run: python3 tests/test_execution.py
- name: Scan gateway image
uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # 0.28.0
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: code-gateway:latest
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: "1"
- name: Scan sandbox image
uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # 0.28.0
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: code-sandbox:latest
severity: HIGH,CRITICAL
Expand All @@ -107,3 +109,49 @@ jobs:
- name: Shutdown stack
if: always()
run: docker compose --profile local-docker down --remove-orphans --volumes

hardened-config:
runs-on: ubuntu-latest
needs: validate
env:
APP_ENV: public_beta
PUBLIC_BETA_MODE: "true"
REQUIRE_AUTH: "true"
METRICS_AUTH_REQUIRED: "true"
API_KEYS: ci:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
CORS_ALLOW_ORIGINS: https://ci.example.invalid
USE_DOCKER_DEFAULT_SECCOMP: "true"
SANDBOX_NETWORK_MODE: none
ALLOW_PIP_INSTALLS: "false"
ALLOW_SANDBOX_ENV_INJECTION: "false"
SANDBOX_IMAGE: code-sandbox:ci
SANDBOX_RUNTIME: runsc
STRONG_SANDBOX_RUNTIMES: runsc,kata-runtime
REQUIRE_STRONG_SANDBOX_ISOLATION: "true"
REQUIRE_SHARED_STATE: "true"
REDIS_URL: redis://redis:6379/0
DOCKER_HOST: tcp://remote-docker:2376
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
- name: Install gateway dependencies
run: python -m pip install -r gateway/requirements.txt
- name: Validate public beta guardrail configuration
run: |
python - <<'PY'
import sys
from pathlib import Path

sys.path.insert(0, str(Path.cwd() / "gateway"))
import app

app.validate_runtime_configuration()
assert app.PUBLIC_BETA_MODE is True
assert app.SANDBOX_NETWORK_MODE == "none"
assert app.ALLOW_PIP_INSTALLS is False
assert app.ALLOW_SANDBOX_ENV_INJECTION is False
assert app.strong_sandbox_runtime_configured() is True
print("Public beta guardrails validated")
PY
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ jobs:
- name: Validate bumped files
run: |
docker compose config -q
python -m compileall -q gateway sandbox tests/verification_client.py tests/verify_vm_flow.py tests/verify_playwright.py tests/verify_features.py tests/test_execution.py tests/test_gateway_unit.py .github/scripts
python -m unittest -q tests/test_gateway_unit.py
bandit -q -r gateway sandbox
python -m compileall -q gateway sandbox tests/verification_client.py tests/verify_vm_flow.py tests/verify_playwright.py tests/verify_features.py tests/test_execution.py tests/test_gateway_unit.py tests/test_executor_unit.py .github/scripts
python -m unittest -q tests/test_gateway_unit.py tests/test_executor_unit.py
bandit -q -r gateway sandbox -s B102,B108,B404,B603
pip-audit -r gateway/requirements.txt
pip-audit -r sandbox/requirements.txt
python - <<'PY'
Expand Down
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This service uses Docker containers as the sandbox boundary. It does not create

On Linux hosts, sandbox containers share the host kernel. On macOS and Windows Docker Desktop, containers usually run inside Docker Desktop's Linux VM, but this project still manages Docker containers, not VMs. Treat this as hardened container isolation, not VM-grade isolation.

Do not expose this service to arbitrary hostile users unless you add stronger isolation and operational controls. For high-risk workloads, use dedicated disposable hosts or a stronger boundary such as microVMs, VMs, gVisor, or Kata Containers. Protect Docker daemon access carefully; access to the Docker socket or an overly permissive Docker API proxy can be equivalent to host-level control.
Do not expose this service to arbitrary hostile users unless you add stronger isolation and operational controls. For public beta or high-risk workloads, use dedicated disposable hosts or a stronger boundary such as microVMs, VMs, gVisor, or Kata Containers, then enable `PUBLIC_BETA_MODE=true` so the gateway rejects risky configuration. Protect Docker daemon access carefully; access to the Docker socket or an overly permissive Docker API proxy can be equivalent to host-level control.

## What It Does

Expand Down Expand Up @@ -503,15 +503,18 @@ Do not expose this service to the internet or a shared network until all of the

- `REQUIRE_AUTH=true` and `API_KEYS` or JWT auth is configured with fresh, long, random secrets created for this deployment.
- Traffic is protected by TLS at an upstream reverse proxy, load balancer, ingress, or service mesh.
- `APP_ENV=production`.
- `APP_ENV=production` for controlled live deployments, or `APP_ENV=public_beta` / `PUBLIC_BETA_MODE=true` for arbitrary untrusted beta users.
- `ENABLE_DOCS=false`.
- `REQUIRE_SHARED_STATE=true` and `REDIS_URL` points at a durable, access-controlled Redis deployment.
- `GATEWAY_DOCKER_HOST` or `DOCKER_HOST` points at a dedicated remote Docker daemon over TLS (`tcp://...:2376`) or SSH (`ssh://...`), not the local socket proxy.
- `CORS_ALLOW_ORIGINS` is restricted to the real ChatUI origin or origins. Do not use wildcard CORS with credentials.
- `SANDBOX_NETWORK_MODE=none` unless network access is explicitly required and isolated.
- Public beta deployments configure `SANDBOX_RUNTIME` to a stronger runtime such as gVisor/runsc or Kata Containers and set `REQUIRE_STRONG_SANDBOX_ISOLATION=true`.
- `SANDBOX_IMAGE` uses an immutable tag or digest, not `latest`.
- `SANDBOX_READ_ONLY_ROOTFS=true`.
- `ALLOW_PIP_INSTALLS=false` for untrusted workloads.
- `ALLOW_SANDBOX_ENV_INJECTION=false` unless submitted code is trusted.
- `SESSION_TIMEOUT_SECONDS`, `MAX_SESSION_LIFETIME_SECONDS`, and `MAX_EXECUTIONS_PER_SESSION` are set to realistic abuse budgets.
- CPU, memory, PID, request-size, file-size, timeout, session, and rate limits are tuned for your host capacity.
- Real secrets are stored outside source control and rotated if they were ever shared, logged, or used in another environment.

Expand Down Expand Up @@ -540,7 +543,7 @@ Implemented controls include:
- Redis-backed shared state for multi-replica coordination.
- Prometheus metrics and health endpoints for operations.

These controls reduce risk but do not make Docker containers equivalent to VMs.
These controls reduce risk but do not make Docker's default container runtime equivalent to VMs. Use a stronger runtime or dedicated disposable worker hosts before accepting arbitrary public users.

### Vulnerability Disclosure

Expand All @@ -554,7 +557,8 @@ See `.env.example` for source defaults. `setup.sh` and `setup.ps1` create `.env`

| Variable | Default | Description | Best practices |
| --- | --- | --- | --- |
| `APP_ENV` | `development` | Deployment environment. Production guardrails are enforced when this is `production` or `prod`. | Use `development` locally, `staging` before launch, and `production` for live deployments. |
| `APP_ENV` | `development` | Deployment environment. Production guardrails are enforced when this is `production` or `prod`; public beta guardrails are enabled when this is `public_beta`, `public-beta`, or `beta`. | Use `development` locally, `staging` before launch, `production` for controlled live deployments, and `public_beta` only with stronger sandbox isolation. |
| `PUBLIC_BETA_MODE` | `false` | Enables strict public beta validation regardless of `APP_ENV`. | Set `true` before exposing arbitrary untrusted beta users. This requires no sandbox network, no pip installs, no env injection, immutable images, and a stronger runtime. |
| `GATEWAY_PORT` | `8000` | Host port mapped to the gateway container's port `8000`. | Keep `8000` locally unless it conflicts. In production, place the service behind TLS infrastructure and expose only required ports. |
| `LOG_LEVEL` | `INFO` | Gateway Python logging level. | Use `INFO` normally. Use `DEBUG` only for temporary debugging because logs may contain operational details. |
| `ENABLE_DOCS` | `false` | Enables FastAPI `/docs` and `/openapi.json`. | Keep `false` in production. Enable only for local debugging or restricted non-production environments. |
Expand Down Expand Up @@ -590,6 +594,7 @@ See `.env.example` for source defaults. `setup.sh` and `setup.ps1` create `.env`
| --- | --- | --- | --- |
| `GATEWAY_DOCKER_HOST` | `tcp://docker-proxy:2375` locally | Compose variable passed into the gateway as `DOCKER_HOST`. | Use local docker-proxy only for development. In production, point at a dedicated remote daemon over TLS or SSH. |
| `DOCKER_HOST` | empty in direct process runs | Docker daemon endpoint read by `docker.from_env()` inside the gateway. | For non-Compose deployments, set this directly to a safe remote daemon. |
| `DOCKER_CLIENT_TIMEOUT` | `30` | Docker API client timeout in seconds. | Keep bounded so Docker API hangs do not pin request workers indefinitely. |
| `USE_DOCKER_DEFAULT_SECCOMP` | `true` | Uses Docker runtime default seccomp policy. | Keep `true` unless you have a tested daemon-visible profile. |
| `SECCOMP_PROFILE_DAEMON_PATH` | empty | Absolute path to a seccomp profile on the Docker daemon host when default seccomp is disabled. `SECCOMP_PROFILE_PATH` is accepted as a legacy alias. | Set only if `USE_DOCKER_DEFAULT_SECCOMP=false`; the path must exist on the daemon host, not merely in this repository. |
| `REDIS_URL` | `redis://redis:6379/0` | Redis URL for shared sessions, locks, and rate limits. | Use Redis in production and for multi-replica deployments. |
Expand Down Expand Up @@ -623,12 +628,18 @@ See `.env.example` for source defaults. `setup.sh` and `setup.ps1` create `.env`
| `MAX_ACTIVE_SESSIONS` | `100` | Maximum active sessions tracked by the gateway. | Size to host capacity and Redis/state expectations. |
| `MAX_CONTAINERS_PER_PRINCIPAL` | `3` | Maximum active sessions per authenticated subject and tenant. | Keep small for shared deployments. |
| `CONTAINER_CREATE_GUARD_TIMEOUT` | `30` | Timeout in seconds while waiting for the serialized container creation guard. | Increase only if Docker is slow during normal operation. |
| `SESSION_TIMEOUT_SECONDS` | `1200` | Idle timeout for sandbox sessions. | Keep short for public/shared deployments. |
| `MAX_SESSION_LIFETIME_SECONDS` | `3600` | Hard lifetime for a sandbox session, regardless of activity. | Prevents users from keeping containers alive forever. Lower for public beta. |
| `MAX_EXECUTIONS_PER_SESSION` | `100` | Maximum number of executions allowed in one session before it is removed. | Use this as a per-session abuse budget. Lower for public beta. |

### Sandbox Runtime

| Variable | Default | Description | Best practices |
| --- | --- | --- | --- |
| `SANDBOX_IMAGE` | `code-sandbox:latest` | Docker image used for sandbox sessions. | Use immutable image tags or digests in production. |
| `SANDBOX_RUNTIME` | empty | Optional Docker runtime for sandbox containers, for example `runsc` for gVisor or `kata-runtime` for Kata Containers. | Required in public beta mode. Configure the runtime on the Docker daemon host first. |
| `STRONG_SANDBOX_RUNTIMES` | `runsc,kata,kata-runtime,io.containerd.runsc.v1,io.containerd.kata.v2` | Comma-separated runtime names that satisfy strong isolation checks. | Keep narrow and aligned with runtimes actually installed on workers. |
| `REQUIRE_STRONG_SANDBOX_ISOLATION` | public-beta-aware, `.env.example`: `false` | Requires `SANDBOX_RUNTIME` to match `STRONG_SANDBOX_RUNTIMES`. | Set `true` for any deployment that accepts arbitrary untrusted users. |
| `SANDBOX_USER` | `sandbox` | User name recorded for sandbox behavior and defaults. | Keep aligned with the sandbox image. |
| `SANDBOX_UID` | `10001` | Sandbox Linux user ID. | Keep non-root. |
| `SANDBOX_GID` | `10001` | Sandbox Linux group ID. | Keep non-root. |
Expand Down Expand Up @@ -765,10 +776,11 @@ Recommended production deployment pattern:
2. Run the gateway behind TLS infrastructure.
3. Run Redis as a managed or persistent service.
4. Run sandbox containers on dedicated worker hosts or a dedicated remote Docker daemon.
5. Keep Docker daemon credentials and API keys out of source control.
6. Monitor request rates, execution latency, error rates, `429` responses, active executions, active sessions, Redis health, Docker daemon health, container restarts, memory, CPU, and disk pressure.
7. Rotate API keys and JWT secrets on a schedule.
8. Keep base images, Python dependencies, Docker, and host kernels patched.
5. For public beta, configure gVisor/runsc, Kata Containers, or an equivalent stronger runtime and set `PUBLIC_BETA_MODE=true`.
6. Keep Docker daemon credentials and API keys out of source control.
7. Monitor request rates, execution latency, error rates, `429` responses, active executions, active sessions, session expirations, Redis health, Docker daemon health, container restarts, memory, CPU, and disk pressure.
8. Rotate API keys and JWT secrets on a schedule.
9. Keep base images, Python dependencies, Docker, runtimes, and host kernels patched.

Development notes:

Expand Down
10 changes: 7 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ Please help us keep ChatUI secure by reporting suspected vulnerabilities respons

**Please do not report security vulnerabilities through public GitHub issues, pull requests, discussions, or comments.**

Instead, please report vulnerabilities privately by contacting us at:
Instead, please report vulnerabilities privately through GitHub private vulnerability
reporting for this repository. If you are reviewing a self-hosted deployment,
use the monitored security contact published by that deployment operator.

**[TODO: Add security contact]**
Self-hosted operators should publish a dedicated security email or intake form
before any public beta, and route it to an on-call owner who can triage container
escape, data exposure, and denial-of-service reports quickly.

Please include as much detail as possible, including:

Expand Down Expand Up @@ -53,4 +57,4 @@ Security-related changes may be described more generally until disclosure is app

We appreciate your help in making the ChatUI Project more secure for everyone.

Thank you for supporting responsible disclosure.
Thank you for supporting responsible disclosure.
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ services:
- MAX_ACTIVE_SESSIONS=${MAX_ACTIVE_SESSIONS:-100}
- MAX_CONTAINERS_PER_PRINCIPAL=${MAX_CONTAINERS_PER_PRINCIPAL:-3}
- CONTAINER_CREATE_GUARD_TIMEOUT=${CONTAINER_CREATE_GUARD_TIMEOUT:-30}
- SESSION_TIMEOUT_SECONDS=${SESSION_TIMEOUT_SECONDS:-1200}
- MAX_SESSION_LIFETIME_SECONDS=${MAX_SESSION_LIFETIME_SECONDS:-3600}
- MAX_EXECUTIONS_PER_SESSION=${MAX_EXECUTIONS_PER_SESSION:-100}
- DEFAULT_TIMEOUT=${DEFAULT_TIMEOUT:-30}
- MAX_TIMEOUT=${MAX_TIMEOUT:-120}
- RATE_LIMIT_REQUESTS_PER_WINDOW=${RATE_LIMIT_REQUESTS_PER_WINDOW:-30}
Expand All @@ -127,6 +130,10 @@ services:
- ALLOW_PIP_INSTALLS=${ALLOW_PIP_INSTALLS:-false}
- MAX_PIP_PACKAGES=${MAX_PIP_PACKAGES:-5}
- MAX_PIP_PACKAGE_NAME_LENGTH=${MAX_PIP_PACKAGE_NAME_LENGTH:-64}
- DOCKER_CLIENT_TIMEOUT=${DOCKER_CLIENT_TIMEOUT:-30}
- SANDBOX_RUNTIME=${SANDBOX_RUNTIME:-}
- STRONG_SANDBOX_RUNTIMES=${STRONG_SANDBOX_RUNTIMES:-runsc,kata,kata-runtime,io.containerd.runsc.v1,io.containerd.kata.v2}
- REQUIRE_STRONG_SANDBOX_ISOLATION=${REQUIRE_STRONG_SANDBOX_ISOLATION:-false}
- USE_DOCKER_DEFAULT_SECCOMP=${USE_DOCKER_DEFAULT_SECCOMP:-true}
- SECCOMP_PROFILE_DAEMON_PATH=${SECCOMP_PROFILE_DAEMON_PATH:-}
- ALLOW_SANDBOX_ENV_INJECTION=${ALLOW_SANDBOX_ENV_INJECTION:-false}
Expand All @@ -136,6 +143,7 @@ services:
- SANDBOX_ENV_SOURCE_PATH=/etc/code-execution/.env_sandbox
- FILE_PROVISION_TIMEOUT=${FILE_PROVISION_TIMEOUT:-30}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- PUBLIC_BETA_MODE=${PUBLIC_BETA_MODE:-false}
- ENABLE_DOCS=${ENABLE_DOCS:-false}
- ENABLE_CORS=${ENABLE_CORS:-true}
- CORS_ALLOW_ORIGINS=${CORS_ALLOW_ORIGINS:-http://localhost:3000}
Expand Down
1 change: 1 addition & 0 deletions gateway/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ FROM ${PYTHON_BASE_IMAGE} AS base

# --- System Setup ---
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/* && \
apt-get clean
Expand Down
Loading
Loading