mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-19 04:15:32 +00:00
feat: more executor guards
This commit is contained in:
1 parent
a0f10925af
commit
47cc0314af
29 files changed
+1485
-199
No files matched your search
@@ -25,10 +25,14 @@ services:
|
||||
ports:
|
||||
- "7091:7091"
|
||||
networks:
|
||||
# `default` reaches the sandbox gateway; `data-net` reaches redis/postgres
|
||||
# (which are NOT on the sandbox's networks).
|
||||
# `default` reaches the internet (LLM APIs) + frontend/host; `data-net`
|
||||
# reaches redis/postgres; `sandbox-net` reaches the code-exec runner over an
|
||||
# internal-only net (so the runner needs no shared internet bridge to be
|
||||
# reachable, and the egress overlay can cut its internet without severing
|
||||
# this control path).
|
||||
- default
|
||||
- data-net
|
||||
- sandbox-net
|
||||
volumes:
|
||||
- ../application/indexes:/app/application/indexes
|
||||
- ../application/inputs:/app/application/inputs
|
||||
@@ -54,23 +58,31 @@ services:
|
||||
- SANDBOX_GATEWAY_URL=http://docsgpt-sandbox:8888
|
||||
- SANDBOX_KERNEL_NAME=docsgpt-python
|
||||
networks:
|
||||
# See backend: control-plane access to the code-exec runner over sandbox-net
|
||||
# (workflow code nodes run in the worker).
|
||||
- default
|
||||
- data-net
|
||||
- sandbox-net
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_started
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
# Always-on code-execution runner (Jupyter Kernel Gateway). In-process kernels,
|
||||
# no Docker socket, no published host port. On the internal-only sandbox-net
|
||||
# plus `default` (for internet egress); deliberately NOT on data-net, so
|
||||
# sandboxed code cannot reach the unauthenticated redis broker (Celery task
|
||||
# injection -> RCE) or postgres. Do NOT add `env_file: ../.env` here -- the
|
||||
# runner needs no app secrets. For untrusted multi-tenant workloads use a
|
||||
# per-session VM via SANDBOX_BACKEND=daytona instead.
|
||||
# Opt-in code-execution runner (Jupyter Kernel Gateway). Started ONLY with
|
||||
# `docker compose --profile sandbox up`: the `code_executor` tool is off by
|
||||
# default (removed from DEFAULT_CHAT_TOOLS) and must be enabled per-agent in
|
||||
# the agent tool picker, so a plain `up` does not start this service.
|
||||
# In-process kernels, no Docker socket, no published host port. On the
|
||||
# internal-only sandbox-net plus `default` (for internet egress); deliberately
|
||||
# NOT on data-net, so sandboxed code cannot reach the unauthenticated redis
|
||||
# broker (Celery task injection -> RCE) or postgres. Do NOT add
|
||||
# `env_file: ../.env` here -- the runner needs no app secrets. For untrusted
|
||||
# multi-tenant workloads use a per-session VM via SANDBOX_BACKEND=daytona
|
||||
# instead.
|
||||
docsgpt-sandbox:
|
||||
build: ./sandbox
|
||||
profiles: ["sandbox"]
|
||||
mem_limit: ${SANDBOX_MEMORY:-1g}
|
||||
cpus: ${SANDBOX_CPUS:-1.0}
|
||||
pids_limit: 256
|
||||
@@ -82,8 +94,12 @@ services:
|
||||
tmpfs:
|
||||
- /tmp
|
||||
networks:
|
||||
# Reachable by backend/worker over the internal sandbox-net; internet
|
||||
# egress (runtime pip install, etc.) via the dedicated sandbox-egress net.
|
||||
# Deliberately NOT on `default`: the egress overlay cuts internet by
|
||||
# flipping sandbox-egress to internal, without severing the control path.
|
||||
- sandbox-net
|
||||
- default
|
||||
- sandbox-egress
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
@@ -92,7 +108,9 @@ services:
|
||||
networks:
|
||||
- data-net
|
||||
ports:
|
||||
- 6379:6379
|
||||
# Loopback only: the published port must NOT be reachable from the sandbox
|
||||
# via the host/bridge gateway. Host-local dev tools still use localhost.
|
||||
- 127.0.0.1:6379:6379
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
@@ -104,7 +122,8 @@ services:
|
||||
- POSTGRES_PASSWORD=docsgpt
|
||||
- POSTGRES_DB=docsgpt
|
||||
ports:
|
||||
- "5432:5432"
|
||||
# Loopback only (see redis): not reachable from the sandbox via the gateway.
|
||||
- "127.0.0.1:5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -114,10 +133,16 @@ services:
|
||||
retries: 10
|
||||
|
||||
networks:
|
||||
# Internal-only runner network (no external egress via this net; the sandbox
|
||||
# still reaches the internet via the default bridge).
|
||||
# Control plane between backend/worker and the code-exec runner. internal:true
|
||||
# => no internet route on this net; the runner egresses via sandbox-egress.
|
||||
sandbox-net:
|
||||
internal: true
|
||||
# The runner's outbound internet route (runtime pip install, etc.). Kept
|
||||
# internet-facing so the base stack works out of the box;
|
||||
# deployment/optional/docker-compose.optional.sandbox-egress.yaml flips it to
|
||||
# internal:true and forces egress through a deny-private proxy for full SSRF
|
||||
# containment (blocking RFC1918 / link-local / cloud-metadata).
|
||||
sandbox-egress: {}
|
||||
# Internal data plane: redis/postgres + backend/worker. The sandbox is NOT
|
||||
# attached, so sandboxed code cannot reach the broker or database.
|
||||
data-net:
|
||||
|
||||
@@ -30,10 +30,14 @@ services:
|
||||
ports:
|
||||
- "7091:7091"
|
||||
networks:
|
||||
# `default` reaches the sandbox gateway; `data-net` reaches redis/postgres
|
||||
# (which are NOT on the sandbox's networks).
|
||||
# `default` reaches the internet (LLM APIs) + frontend/host; `data-net`
|
||||
# reaches redis/postgres; `sandbox-net` reaches the code-exec runner over an
|
||||
# internal-only net (so the runner needs no shared internet bridge to be
|
||||
# reachable, and the egress overlay can cut its internet without severing
|
||||
# this control path).
|
||||
- default
|
||||
- data-net
|
||||
- sandbox-net
|
||||
volumes:
|
||||
- ../application/indexes:/app/indexes
|
||||
- ../application/inputs:/app/inputs
|
||||
@@ -61,8 +65,11 @@ services:
|
||||
- SANDBOX_GATEWAY_URL=http://docsgpt-sandbox:8888
|
||||
- SANDBOX_KERNEL_NAME=docsgpt-python
|
||||
networks:
|
||||
# See backend: control-plane access to the code-exec runner over sandbox-net
|
||||
# (workflow code nodes run in the worker).
|
||||
- default
|
||||
- data-net
|
||||
- sandbox-net
|
||||
volumes:
|
||||
- ../application/indexes:/app/indexes
|
||||
- ../application/inputs:/app/inputs
|
||||
@@ -73,18 +80,23 @@ services:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
# Always-on code-execution runner (Jupyter Kernel Gateway). In-process kernels,
|
||||
# no Docker socket, no published host port. On the internal-only sandbox-net
|
||||
# plus `default` (for internet egress); deliberately NOT on data-net, so
|
||||
# sandboxed code cannot reach the unauthenticated redis broker (Celery task
|
||||
# injection -> RCE) or postgres. Do NOT add `env_file: ../.env` here -- the
|
||||
# runner needs no app secrets. For untrusted multi-tenant workloads use a
|
||||
# per-session VM via SANDBOX_BACKEND=daytona instead.
|
||||
# Opt-in code-execution runner (Jupyter Kernel Gateway). Started ONLY with
|
||||
# `docker compose --profile sandbox up`: the `code_executor` tool is off by
|
||||
# default (removed from DEFAULT_CHAT_TOOLS) and must be enabled per-agent in
|
||||
# the agent tool picker, so a plain `up` does not start this service.
|
||||
# In-process kernels, no Docker socket, no published host port. On the
|
||||
# internal-only sandbox-net plus `default` (for internet egress); deliberately
|
||||
# NOT on data-net, so sandboxed code cannot reach the unauthenticated redis
|
||||
# broker (Celery task injection -> RCE) or postgres. Do NOT add
|
||||
# `env_file: ../.env` here -- the runner needs no app secrets. For untrusted
|
||||
# multi-tenant workloads use a per-session VM via SANDBOX_BACKEND=daytona
|
||||
# instead.
|
||||
docsgpt-sandbox:
|
||||
# Built from the repo (no published arc53/docsgpt-sandbox image exists yet); the
|
||||
# hub compose ships alongside ./sandbox, so this stays self-contained and the
|
||||
# stack always starts. Switch to a published image once CI builds/pushes one.
|
||||
build: ./sandbox
|
||||
profiles: ["sandbox"]
|
||||
mem_limit: ${SANDBOX_MEMORY:-1g}
|
||||
cpus: ${SANDBOX_CPUS:-1.0}
|
||||
pids_limit: 256
|
||||
@@ -96,8 +108,12 @@ services:
|
||||
tmpfs:
|
||||
- /tmp
|
||||
networks:
|
||||
# Reachable by backend/worker over the internal sandbox-net; internet
|
||||
# egress (runtime pip install, etc.) via the dedicated sandbox-egress net.
|
||||
# Deliberately NOT on `default`: the egress overlay cuts internet by
|
||||
# flipping sandbox-egress to internal, without severing the control path.
|
||||
- sandbox-net
|
||||
- default
|
||||
- sandbox-egress
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
@@ -106,7 +122,9 @@ services:
|
||||
networks:
|
||||
- data-net
|
||||
ports:
|
||||
- 6379:6379
|
||||
# Loopback only: the published port must NOT be reachable from the sandbox
|
||||
# via the host/bridge gateway. Host-local dev tools still use localhost.
|
||||
- 127.0.0.1:6379:6379
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
@@ -118,7 +136,8 @@ services:
|
||||
- POSTGRES_PASSWORD=docsgpt
|
||||
- POSTGRES_DB=docsgpt
|
||||
ports:
|
||||
- "5432:5432"
|
||||
# Loopback only (see redis): not reachable from the sandbox via the gateway.
|
||||
- "127.0.0.1:5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -128,10 +147,16 @@ services:
|
||||
retries: 10
|
||||
|
||||
networks:
|
||||
# Internal-only runner network (no external egress via this net; the sandbox
|
||||
# still reaches the internet via the default bridge).
|
||||
# Control plane between backend/worker and the code-exec runner. internal:true
|
||||
# => no internet route on this net; the runner egresses via sandbox-egress.
|
||||
sandbox-net:
|
||||
internal: true
|
||||
# The runner's outbound internet route (runtime pip install, etc.). Kept
|
||||
# internet-facing so the base stack works out of the box;
|
||||
# deployment/optional/docker-compose.optional.sandbox-egress.yaml flips it to
|
||||
# internal:true and forces egress through a deny-private proxy for full SSRF
|
||||
# containment (blocking RFC1918 / link-local / cloud-metadata).
|
||||
sandbox-egress: {}
|
||||
# Internal data plane: redis/postgres + backend/worker. The sandbox is NOT
|
||||
# attached, so sandboxed code cannot reach the broker or database.
|
||||
data-net:
|
||||
|
||||
@@ -33,10 +33,14 @@ services:
|
||||
ports:
|
||||
- "7091:7091"
|
||||
networks:
|
||||
# `default` reaches the internet + the sandbox gateway; `data-net` reaches
|
||||
# redis/postgres (which are NOT on the sandbox's networks).
|
||||
# `default` reaches the internet (LLM APIs) + frontend/host; `data-net`
|
||||
# reaches redis/postgres; `sandbox-net` reaches the code-exec runner over an
|
||||
# internal-only net (so the runner needs no shared internet bridge to be
|
||||
# reachable, and the egress overlay can cut its internet without severing
|
||||
# this control path).
|
||||
- default
|
||||
- data-net
|
||||
- sandbox-net
|
||||
volumes:
|
||||
- ../application/indexes:/app/indexes
|
||||
- ../application/inputs:/app/inputs
|
||||
@@ -56,8 +60,11 @@ services:
|
||||
# deployment/sandbox/README.md).
|
||||
command: celery -A application.app.celery worker -l INFO -B -Q docsgpt,parsing
|
||||
networks:
|
||||
# See backend: control-plane access to the code-exec runner over sandbox-net
|
||||
# (workflow code nodes run in the worker).
|
||||
- default
|
||||
- data-net
|
||||
- sandbox-net
|
||||
env_file:
|
||||
- ../.env
|
||||
environment:
|
||||
@@ -80,12 +87,15 @@ services:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
# Always-on code-execution runner (Jupyter Kernel Gateway). Sessions are
|
||||
# in-process kernels, never child containers; the Docker socket is NOT
|
||||
# mounted. On an internal-only network — no host port is published, so the
|
||||
# runner is reachable only from backend/worker, not from the host/internet.
|
||||
# Egress/SSRF blocks, the gVisor `runsc` runtime, and seccomp profile come in
|
||||
# the hardening slice.
|
||||
# Opt-in code-execution runner (Jupyter Kernel Gateway). Started ONLY with
|
||||
# `docker compose --profile sandbox up`: the `code_executor` tool is off by
|
||||
# default (removed from DEFAULT_CHAT_TOOLS) and must be enabled per-agent in
|
||||
# the agent tool picker, so a plain `up` does not start this service.
|
||||
# Sessions are in-process kernels, never child containers; the Docker socket
|
||||
# is NOT mounted. On an internal-only network — no host port is published, so
|
||||
# the runner is reachable only from backend/worker, not from the
|
||||
# host/internet. Egress/SSRF blocks, the gVisor `runsc` runtime, and seccomp
|
||||
# profile come in the hardening slice.
|
||||
#
|
||||
# SINGLE TRUST DOMAIN: all sessions share this one container/uid and are
|
||||
# isolated by working directory only (per-session cwd) — not by a kernel/OS
|
||||
@@ -96,6 +106,7 @@ services:
|
||||
# per-session VM via SANDBOX_BACKEND=daytona instead.
|
||||
docsgpt-sandbox:
|
||||
build: ./sandbox
|
||||
profiles: ["sandbox"]
|
||||
mem_limit: ${SANDBOX_MEMORY:-1g}
|
||||
cpus: ${SANDBOX_CPUS:-1.0}
|
||||
pids_limit: 256
|
||||
@@ -109,8 +120,12 @@ services:
|
||||
# runtime files live on tmpfs; the root FS is read-only everywhere else.
|
||||
- /tmp
|
||||
networks:
|
||||
# Reachable by backend/worker over the internal sandbox-net; internet
|
||||
# egress (runtime pip install, etc.) via the dedicated sandbox-egress net.
|
||||
# Deliberately NOT on `default`: the egress overlay cuts internet by
|
||||
# flipping sandbox-egress to internal, without severing the control path.
|
||||
- sandbox-net
|
||||
- default
|
||||
- sandbox-egress
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
@@ -119,7 +134,9 @@ services:
|
||||
networks:
|
||||
- data-net
|
||||
ports:
|
||||
- 6379:6379
|
||||
# Loopback only: the published port must NOT be reachable from the sandbox
|
||||
# via the host/bridge gateway. Host-local dev tools still use localhost.
|
||||
- 127.0.0.1:6379:6379
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
@@ -131,7 +148,8 @@ services:
|
||||
- POSTGRES_PASSWORD=docsgpt
|
||||
- POSTGRES_DB=docsgpt
|
||||
ports:
|
||||
- "5432:5432"
|
||||
# Loopback only (see redis): not reachable from the sandbox via the gateway.
|
||||
- "127.0.0.1:5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -141,15 +159,20 @@ services:
|
||||
retries: 10
|
||||
|
||||
networks:
|
||||
# Internal-only network for the sandbox runner (no external egress route via
|
||||
# this network; the runner still reaches the internet via the default bridge).
|
||||
# Control plane between backend/worker and the code-exec runner. internal:true
|
||||
# => no internet route on this net; the runner egresses via sandbox-egress.
|
||||
sandbox-net:
|
||||
internal: true
|
||||
# The runner's outbound internet route (runtime pip install, etc.). Kept
|
||||
# internet-facing so the base stack works out of the box;
|
||||
# deployment/optional/docker-compose.optional.sandbox-egress.yaml flips it to
|
||||
# internal:true and forces egress through a deny-private proxy for full SSRF
|
||||
# containment (blocking RFC1918 / link-local / cloud-metadata).
|
||||
sandbox-egress: {}
|
||||
# Internal-only data plane. redis (broker/cache) and postgres live here with
|
||||
# backend/worker, but the code-exec sandbox is NOT attached, so arbitrary
|
||||
# sandboxed code cannot reach the unauthenticated broker (Celery task
|
||||
# injection -> worker RCE) or the database. For full internet-egress
|
||||
# containment additionally apply deployment/optional/docker-compose.optional.sandbox-egress.yaml.
|
||||
# injection -> worker RCE) or the database.
|
||||
data-net:
|
||||
internal: true
|
||||
|
||||
|
||||
@@ -2,40 +2,52 @@
|
||||
#
|
||||
# Docker Compose cannot express L3 egress filtering the way a Kubernetes
|
||||
# NetworkPolicy can, so SSRF containment in compose deployments is delivered by
|
||||
# routing the sandbox's outbound traffic through an egress-gateway sidecar that
|
||||
# DENIES private / link-local / metadata ranges and ALLOWS the public internet.
|
||||
# taking away the runner's direct internet route and forcing its outbound
|
||||
# traffic through an egress-gateway sidecar that DENIES private / link-local /
|
||||
# metadata ranges and ALLOWS the public internet.
|
||||
#
|
||||
# HOW THIS OVERLAY WORKS
|
||||
# The base stack puts docsgpt-sandbox on two networks: the internal `sandbox-net`
|
||||
# (control plane to backend/worker) and `sandbox-egress` (its internet route).
|
||||
# This overlay flips `sandbox-egress` to `internal: true`, which removes the
|
||||
# runner's direct route to the internet / host / RFC1918 / metadata entirely --
|
||||
# so even raw sockets in arbitrary sandbox code have nowhere to go except the
|
||||
# proxy on `sandbox-net`. The proxy is the ONLY container with an internet route
|
||||
# (on its own `sandbox-egress-out` network) and its ACL denies private
|
||||
# destinations. We flip a network's scalar property by KEY (a well-defined
|
||||
# Compose merge); we do NOT try to remove an item from the service's `networks`
|
||||
# LIST, which Compose cannot express (it unions lists) -- the reason a naive
|
||||
# `networks: [sandbox-net]` override silently leaves the runner on its old net.
|
||||
#
|
||||
# Two viable approaches; pick one:
|
||||
#
|
||||
# (1) Host / cloud firewall (simplest, recommended for single-host compose):
|
||||
# Drop egress to RFC1918 (10/8, 172.16/12, 192.168/16), link-local
|
||||
# (1) Host / cloud firewall (simplest, belt-and-suspenders for single-host):
|
||||
# Also drop egress to RFC1918 (10/8, 172.16/12, 192.168/16), link-local
|
||||
# (169.254/16, incl. the 169.254.169.254 metadata IP), and ULA on the
|
||||
# docsgpt-sandbox container's interface using iptables/nftables on the
|
||||
# Docker host (Docker does not do this for you). Example (host root):
|
||||
# SBX=$(docker inspect -f '{{.NetworkSettings.Networks.docsgpt_sandbox-net.IPAddress}}' docsgpt-sandbox)
|
||||
# Docker host. Example (host root):
|
||||
# SBX=$(docker inspect -f '{{.NetworkSettings.Networks.docsgpt-oss_sandbox-egress.IPAddress}}' docsgpt-sandbox)
|
||||
# iptables -I DOCKER-USER -s "$SBX" -d 169.254.0.0/16 -j DROP
|
||||
# iptables -I DOCKER-USER -s "$SBX" -d 10.0.0.0/8 -j DROP
|
||||
# iptables -I DOCKER-USER -s "$SBX" -d 172.16.0.0/12 -j DROP
|
||||
# iptables -I DOCKER-USER -s "$SBX" -d 192.168.0.0/16 -j DROP
|
||||
# (Allow established/return traffic and DNS as needed for your setup.)
|
||||
#
|
||||
# (2) Egress-gateway sidecar (this overlay): pin the sandbox to an
|
||||
# internal-only network with NO direct internet route, and force all of
|
||||
# its outbound traffic through a small forward proxy that blocks private
|
||||
# destinations. The proxy is the ONLY container on both the internal and
|
||||
# the egress network, so the sandbox cannot reach internal services
|
||||
# directly. Configure the runner's outbound HTTP(S) via the proxy env
|
||||
# vars below; lock down non-HTTP egress with the proxy's own rules.
|
||||
# (2) Egress-gateway sidecar (this overlay): the runner has NO direct internet
|
||||
# route (see above); its only path out is the deny-private forward proxy.
|
||||
# Point the runner's HTTP(S) client at the proxy via the env vars below.
|
||||
#
|
||||
# Apply alongside the base stack:
|
||||
# docker compose -f deployment/docker-compose.yaml \
|
||||
# -f deployment/optional/docker-compose.optional.sandbox-egress.yaml up -d
|
||||
# (also works layered on docker-compose-hub.yaml / docker-compose-azure.yaml,
|
||||
# which share the same service/network names).
|
||||
#
|
||||
# NOTE: a forward proxy only constrains traffic that honors the proxy env vars.
|
||||
# Truly arbitrary sandbox code can ignore them, so on a multi-tenant or
|
||||
# untrusted deployment prefer approach (1) (or the Kubernetes NetworkPolicy)
|
||||
# which enforces at the network layer regardless of what the code does.
|
||||
version: "3.8"
|
||||
# NOTE: because `sandbox-egress` is internal here, non-HTTP raw egress is blocked
|
||||
# at L3 regardless of what the code does (there is simply no route). HTTP(S) that
|
||||
# honors the proxy env is filtered by the proxy ACL. For belt-and-suspenders on a
|
||||
# multi-tenant or untrusted deployment, also apply approach (1) or the Kubernetes
|
||||
# NetworkPolicy.
|
||||
|
||||
services:
|
||||
# Forward proxy that denies private/link-local/metadata destinations and
|
||||
@@ -46,26 +58,28 @@ services:
|
||||
image: ghcr.io/example/egress-deny-private:latest # replace with your proxy image
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- sandbox-net # reachable by the sandbox
|
||||
- sandbox-egress # the only container with an internet route
|
||||
- sandbox-net # reachable by the runner (control-plane net)
|
||||
- sandbox-egress-out # the ONLY container with an internet route
|
||||
|
||||
docsgpt-sandbox:
|
||||
# Cut the sandbox off from the default (internet) bridge; it keeps only the
|
||||
# internal sandbox-net and must egress via the proxy.
|
||||
networks:
|
||||
- sandbox-net
|
||||
# Match the base service's profile so this overlay fragment merges onto the
|
||||
# profiled base and the runner still starts only under `--profile sandbox`
|
||||
# (Compose unions service fragments across -f files; keeping the profile
|
||||
# here means a bare `up` never starts the runner via this overlay either).
|
||||
profiles: ["sandbox"]
|
||||
# No `networks:` override here on purpose: the base already attaches the
|
||||
# runner to sandbox-net + sandbox-egress. Flipping sandbox-egress to internal
|
||||
# (below) removes its direct internet route; its only way out is this proxy.
|
||||
environment:
|
||||
- HTTP_PROXY=http://sandbox-egress-proxy:8080
|
||||
- HTTPS_PROXY=http://sandbox-egress-proxy:8080
|
||||
- NO_PROXY=localhost,127.0.0.1
|
||||
|
||||
networks:
|
||||
# Internal-only network shared by the sandbox and the proxy. ``internal:
|
||||
# true`` removes the default gateway so the sandbox has NO direct internet
|
||||
# route -- its only path out is through the proxy on the egress network.
|
||||
sandbox-net:
|
||||
driver: bridge
|
||||
internal: true
|
||||
# Internet-facing network for the proxy only.
|
||||
# Flip the runner's egress net to internal: no direct route to the internet,
|
||||
# host, RFC1918, or cloud metadata. (Merged by key over the base definition.)
|
||||
sandbox-egress:
|
||||
internal: true
|
||||
# Internet-facing route for the proxy ONLY.
|
||||
sandbox-egress-out:
|
||||
driver: bridge
|
||||
@@ -1,10 +1,35 @@
|
||||
# docsgpt-sandbox runner
|
||||
|
||||
Always-on Jupyter Kernel Gateway that executes sandboxed LLM code. The DocsGPT
|
||||
Opt-in Jupyter Kernel Gateway that executes sandboxed LLM code. The DocsGPT
|
||||
backend/worker is the **client** and connects over HTTP + WebSocket via
|
||||
`SANDBOX_GATEWAY_URL`. Each session is an **in-process kernel** (child process),
|
||||
never a child container; the Docker socket is **not** mounted.
|
||||
|
||||
## Enabling code execution (opt-in)
|
||||
|
||||
The runner is **opt-in**. `code_executor` is no longer a default chat tool (it
|
||||
was removed from `DEFAULT_CHAT_TOOLS`), so a plain `docker compose up` does
|
||||
**not** start `docsgpt-sandbox`. Start it explicitly with the `sandbox`
|
||||
profile:
|
||||
|
||||
```bash
|
||||
docker compose -f deployment/docker-compose.yaml --profile sandbox up
|
||||
```
|
||||
|
||||
With the egress-firewall overlay (see *Network egress / SSRF* below):
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f deployment/docker-compose.yaml \
|
||||
-f deployment/optional/docker-compose.optional.sandbox-egress.yaml \
|
||||
--profile sandbox up
|
||||
```
|
||||
|
||||
Then enable `code_executor` **per-agent** in the agent tool picker — it is not a
|
||||
default chat tool. Agents without it never call the runner, and the
|
||||
backend/worker degrade gracefully when the runner is absent. The `-hub` and
|
||||
`-azure` compose variants gate the runner behind the same `sandbox` profile.
|
||||
|
||||
## Isolation model
|
||||
|
||||
Read this before pointing untrusted or multi-tenant workloads at the runner.
|
||||
@@ -102,7 +127,9 @@ required there.
|
||||
## In docker-compose
|
||||
|
||||
The `docsgpt-sandbox` service is defined in `deployment/docker-compose.yaml` on
|
||||
an internal-only network. The backend and worker reach it at
|
||||
an internal-only network and is gated behind the `sandbox` Compose profile
|
||||
(opt-in — start it with `docker compose --profile sandbox up`; see *Enabling
|
||||
code execution (opt-in)* above). The backend and worker reach it at
|
||||
`http://docsgpt-sandbox:8888` and select the scrubbing kernel by setting
|
||||
`SANDBOX_KERNEL_NAME=docsgpt-python` (the runner only ships the kernelspec; the
|
||||
app chooses it). The same applies to k8s: `SANDBOX_KERNEL_NAME=docsgpt-python`
|
||||
@@ -194,14 +221,19 @@ The hardened container runs **without `NET_ADMIN`**, so it cannot self-apply
|
||||
```
|
||||
|
||||
- **docker-compose** — compose cannot express L3 egress filtering natively. The
|
||||
base stack puts the runner on an `internal: true` network (no host port), but
|
||||
that does not by itself block the metadata IP or RFC1918 reachable via the
|
||||
default bridge. Add a **host/cloud firewall rule** (drop the four private
|
||||
ranges on the sandbox container's interface) **or** route egress through an
|
||||
**egress-gateway proxy** sidecar. Both are documented in
|
||||
[`deployment/optional/docker-compose.optional.sandbox-egress.yaml`](../optional/docker-compose.optional.sandbox-egress.yaml).
|
||||
On untrusted/multi-tenant hosts prefer the host-firewall rule — a forward
|
||||
proxy only constrains code that honors `HTTP(S)_PROXY`.
|
||||
base stack reaches the runner over an `internal: true` control network
|
||||
(`sandbox-net`, no host port) and gives it internet egress on a dedicated
|
||||
`sandbox-egress` bridge — but that bridge does not by itself block the metadata
|
||||
IP or RFC1918. Apply
|
||||
[`deployment/optional/docker-compose.optional.sandbox-egress.yaml`](../optional/docker-compose.optional.sandbox-egress.yaml),
|
||||
which flips `sandbox-egress` to `internal: true` (removing the runner's direct
|
||||
internet/RFC1918/metadata route entirely) and forces egress through a
|
||||
deny-private **egress-gateway proxy** sidecar; for belt-and-suspenders on
|
||||
untrusted/multi-tenant hosts also add a **host/cloud firewall rule** (drop the
|
||||
four private ranges on the sandbox container's interface), since a forward
|
||||
proxy only filters code that honors `HTTP(S)_PROXY` — the `internal` flip is
|
||||
what contains raw sockets. Note the broker/DB published ports are bound to
|
||||
`127.0.0.1` so the runner cannot reach them via the host gateway either.
|
||||
|
||||
## Other hardening (deployment-level)
|
||||
|
||||
|
||||
Reference in new issue
Block a user