mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-14 03:15:58 +00:00
fix(security): mount host Docker socket only in aio (DooD) sandbox mode (#3517)
* fix(security): mount host Docker socket only in aio (DooD) sandbox mode The default Compose stack mounted /var/run/docker.sock read-write into the root gateway container in every sandbox mode, including the default `local` mode that never uses it -- an unnecessary host-escape surface (DooD = root-equivalent host control). deploy.sh already gated the socket *check* on sandbox_mode != local, but the Compose files mounted it unconditionally. Move the socket mount to an opt-in docker/docker-compose.dood.yaml overlay that deploy.sh / docker.sh append only when detect_sandbox_mode() returns `aio`. Default (local) and provisioner/Kubernetes modes no longer expose the host daemon. Tighten the socket existence check from != local to == aio. Document the DooD threat model in SECURITY.md. Reported by @greatmengqi. * refactor(docker): address review on socket-hardening PR - docker.sh: use absolute path for the dood overlay (match deploy.sh, drop cwd dependency) - deploy.sh: drop now-dead DEER_FLOW_DOCKER_SOCKET exports in down/build paths - docker-compose.yaml: fix stale header comment to point at the overlay Addresses codex + reviewer feedback on #3517. --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
+39
@@ -11,6 +11,45 @@ Currently, we have two branches to maintain:
|
|||||||
|
|
||||||
Please go to https://github.com/bytedance/deer-flow/security to report the vulnerability you find.
|
Please go to https://github.com/bytedance/deer-flow/security to report the vulnerability you find.
|
||||||
|
|
||||||
|
## Sandbox Isolation and the Docker Socket (DooD)
|
||||||
|
|
||||||
|
DeerFlow executes agent-generated shell/code through a configurable sandbox
|
||||||
|
(`sandbox.use` in `config.yaml`). The isolation guarantees differ by mode, and
|
||||||
|
one mode requires mounting the host Docker socket. Understand the trade-offs
|
||||||
|
before exposing an instance to untrusted input.
|
||||||
|
|
||||||
|
| Mode | `config.yaml` | Host Docker socket | Isolation |
|
||||||
|
|------|---------------|--------------------|-----------|
|
||||||
|
| `local` (default) | `deerflow.sandbox.local:LocalSandboxProvider` | Not mounted | Commands run **inside the gateway container** on its filesystem. Not a strong boundary — `allow_host_bash` is `false` by default and should stay off for untrusted workloads. |
|
||||||
|
| `aio` (pure DooD) | `deerflow.community.aio_sandbox:AioSandboxProvider` (no `provisioner_url`) | **Mounted** (opt-in overlay) | Sandbox containers are started via the host Docker daemon. |
|
||||||
|
| `provisioner` (Kubernetes) | `AioSandboxProvider` + `provisioner_url` | Not mounted | Sandbox pods are created through the provisioner's K8s API over HTTP. Strongest isolation. |
|
||||||
|
|
||||||
|
### The Docker socket is host root
|
||||||
|
|
||||||
|
Mounting `/var/run/docker.sock` into a container grants that container
|
||||||
|
**root-equivalent control of the host**: anything able to reach the socket can
|
||||||
|
start a new container that bind-mounts the host filesystem and escape. This
|
||||||
|
matters for DeerFlow because the gateway executes model-generated commands, so a
|
||||||
|
prompt injection or any in-container code-execution primitive could pivot to the
|
||||||
|
host through the socket.
|
||||||
|
|
||||||
|
To keep this off the default attack surface:
|
||||||
|
|
||||||
|
- The host Docker socket is **not** mounted by the default Compose stack. It is
|
||||||
|
added only for `aio` mode through the opt-in `docker/docker-compose.dood.yaml`
|
||||||
|
overlay, which `scripts/deploy.sh` and `scripts/docker.sh` append
|
||||||
|
automatically when `detect_sandbox_mode()` returns `aio`.
|
||||||
|
- Prefer **provisioner/Kubernetes mode** for multi-tenant or internet-exposed
|
||||||
|
deployments — it isolates sandboxes without handing the gateway the host
|
||||||
|
daemon.
|
||||||
|
- If you must use `aio`/DooD, treat the host as part of the gateway's trust
|
||||||
|
boundary: run it on a dedicated host, and consider a scoped Docker API proxy
|
||||||
|
instead of the raw socket.
|
||||||
|
|
||||||
|
> Note: the gateway bind-mounts `$HOME/.claude` and `$HOME/.codex` (read-only)
|
||||||
|
> for CLI auto-auth in **all** modes. These hold long-lived CLI credentials;
|
||||||
|
> scope or omit them when the gateway runs untrusted workloads.
|
||||||
|
|
||||||
## CLI Credential Mounts (Claude Code / Codex)
|
## CLI Credential Mounts (Claude Code / Codex)
|
||||||
|
|
||||||
DeerFlow can reuse your Claude Code / Codex CLI subscription login as a model
|
DeerFlow can reuse your Claude Code / Codex CLI subscription login as a model
|
||||||
|
|||||||
@@ -146,8 +146,10 @@ services:
|
|||||||
# On macOS/Docker Desktop, uv may fail to create symlinks inside shared
|
# On macOS/Docker Desktop, uv may fail to create symlinks inside shared
|
||||||
# host directories, which causes startup-time `uv sync` to crash.
|
# host directories, which causes startup-time `uv sync` to crash.
|
||||||
- gateway-uv-cache:/root/.cache/uv
|
- gateway-uv-cache:/root/.cache/uv
|
||||||
# DooD: AioSandboxProvider runs inside the Gateway process.
|
# DooD: the host Docker socket is NOT mounted by default. It is added only
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
# for aio (pure-DooD) sandbox mode via the opt-in docker-compose.dood.yaml
|
||||||
|
# overlay (appended by scripts/docker.sh). See SECURITY.md.
|
||||||
|
|
||||||
# CLI auth dirs (Claude Code / Codex) are NOT mounted by default: they
|
# CLI auth dirs (Claude Code / Codex) are NOT mounted by default: they
|
||||||
# expose the entire ~/.claude and ~/.codex (history, projects, global
|
# expose the entire ~/.claude and ~/.codex (history, projects, global
|
||||||
# config, credentials) into the container. Mount them only when you use
|
# config, credentials) into the container. Mount them only when you use
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# DeerFlow — Docker-out-of-Docker (DooD) overlay (OPT-IN, NOT loaded by default)
|
||||||
|
#
|
||||||
|
# Mounts the host Docker socket into the gateway container so that
|
||||||
|
# AioSandboxProvider running in pure-Docker mode — config.yaml:
|
||||||
|
# sandbox.use: deerflow.community.aio_sandbox:AioSandboxProvider
|
||||||
|
# with NO provisioner_url — can start per-thread sandbox containers via the
|
||||||
|
# host Docker daemon.
|
||||||
|
#
|
||||||
|
# SECURITY: the host Docker socket grants the gateway container
|
||||||
|
# root-equivalent control of the host. Only load this overlay when you have
|
||||||
|
# explicitly chosen aio (DooD) sandbox mode and accept that trade-off. The
|
||||||
|
# default LocalSandboxProvider and the provisioner/Kubernetes mode do NOT need
|
||||||
|
# it and never load this file. See SECURITY.md for the full threat model.
|
||||||
|
#
|
||||||
|
# scripts/deploy.sh and scripts/docker.sh append this overlay automatically
|
||||||
|
# only when detect_sandbox_mode() returns "aio". Manual use:
|
||||||
|
# docker compose -f docker-compose.yaml -f docker-compose.dood.yaml up -d
|
||||||
|
#
|
||||||
|
# Compatible with both docker-compose.yaml (prod) and docker-compose-dev.yaml
|
||||||
|
# (dev): both define a `gateway` service, and Compose merges this volume entry
|
||||||
|
# onto it. DEER_FLOW_DOCKER_SOCKET defaults to /var/run/docker.sock.
|
||||||
|
services:
|
||||||
|
gateway:
|
||||||
|
volumes:
|
||||||
|
- ${DEER_FLOW_DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
# DEER_FLOW_CONFIG_PATH — path to config.yaml
|
# DEER_FLOW_CONFIG_PATH — path to config.yaml
|
||||||
# DEER_FLOW_EXTENSIONS_CONFIG_PATH — path to extensions_config.json
|
# DEER_FLOW_EXTENSIONS_CONFIG_PATH — path to extensions_config.json
|
||||||
# DEER_FLOW_SKILLS_PATH — skills dir, default $DEER_FLOW_PROJECT_ROOT/skills
|
# DEER_FLOW_SKILLS_PATH — skills dir, default $DEER_FLOW_PROJECT_ROOT/skills
|
||||||
# DEER_FLOW_DOCKER_SOCKET — Docker socket path, default /var/run/docker.sock
|
# DEER_FLOW_DOCKER_SOCKET — Docker socket path for aio/DooD mode, default /var/run/docker.sock (used only by the opt-in docker-compose.dood.yaml overlay)
|
||||||
# DEER_FLOW_REPO_ROOT — repo root (used for skills host path in DooD)
|
# DEER_FLOW_REPO_ROOT — repo root (used for skills host path in DooD)
|
||||||
# BETTER_AUTH_SECRET — required for frontend auth/session security
|
# BETTER_AUTH_SECRET — required for frontend auth/session security
|
||||||
# DEER_FLOW_INTERNAL_AUTH_TOKEN — shared internal Gateway auth token for multi-worker IM channels
|
# DEER_FLOW_INTERNAL_AUTH_TOKEN — shared internal Gateway auth token for multi-worker IM channels
|
||||||
@@ -84,8 +84,10 @@ services:
|
|||||||
- ${DEER_FLOW_EXTENSIONS_CONFIG_PATH}:/app/backend/extensions_config.json:ro
|
- ${DEER_FLOW_EXTENSIONS_CONFIG_PATH}:/app/backend/extensions_config.json:ro
|
||||||
- ../skills:/app/skills:ro
|
- ../skills:/app/skills:ro
|
||||||
- ${DEER_FLOW_HOME}:/app/backend/.deer-flow
|
- ${DEER_FLOW_HOME}:/app/backend/.deer-flow
|
||||||
# DooD: AioSandboxProvider starts sandbox containers via host Docker daemon
|
# DooD: the host Docker socket is NOT mounted by default. It is added only
|
||||||
- ${DEER_FLOW_DOCKER_SOCKET}:/var/run/docker.sock
|
# for aio (pure-DooD) sandbox mode via the opt-in docker-compose.dood.yaml
|
||||||
|
# overlay (appended by scripts/deploy.sh). See SECURITY.md.
|
||||||
|
|
||||||
# CLI auth dirs (Claude Code / Codex) are NOT mounted by default: they
|
# CLI auth dirs (Claude Code / Codex) are NOT mounted by default: they
|
||||||
# expose the entire ~/.claude and ~/.codex (history, projects, global
|
# expose the entire ~/.claude and ~/.codex (history, projects, global
|
||||||
# config, credentials) into the container. Mount them only when you use
|
# config, credentials) into the container. Mount them only when you use
|
||||||
|
|||||||
+9
-10
@@ -215,7 +215,6 @@ if [ "$CMD" = "down" ]; then
|
|||||||
export DEER_FLOW_HOME="${DEER_FLOW_HOME:-$REPO_ROOT/backend/.deer-flow}"
|
export DEER_FLOW_HOME="${DEER_FLOW_HOME:-$REPO_ROOT/backend/.deer-flow}"
|
||||||
export DEER_FLOW_CONFIG_PATH="${DEER_FLOW_CONFIG_PATH:-$DEER_FLOW_HOME/config.yaml}"
|
export DEER_FLOW_CONFIG_PATH="${DEER_FLOW_CONFIG_PATH:-$DEER_FLOW_HOME/config.yaml}"
|
||||||
export DEER_FLOW_EXTENSIONS_CONFIG_PATH="${DEER_FLOW_EXTENSIONS_CONFIG_PATH:-$DEER_FLOW_HOME/extensions_config.json}"
|
export DEER_FLOW_EXTENSIONS_CONFIG_PATH="${DEER_FLOW_EXTENSIONS_CONFIG_PATH:-$DEER_FLOW_HOME/extensions_config.json}"
|
||||||
export DEER_FLOW_DOCKER_SOCKET="${DEER_FLOW_DOCKER_SOCKET:-/var/run/docker.sock}"
|
|
||||||
export DEER_FLOW_REPO_ROOT="${DEER_FLOW_REPO_ROOT:-$REPO_ROOT}"
|
export DEER_FLOW_REPO_ROOT="${DEER_FLOW_REPO_ROOT:-$REPO_ROOT}"
|
||||||
export BETTER_AUTH_SECRET="${BETTER_AUTH_SECRET:-placeholder}"
|
export BETTER_AUTH_SECRET="${BETTER_AUTH_SECRET:-placeholder}"
|
||||||
export DEER_FLOW_INTERNAL_AUTH_TOKEN="${DEER_FLOW_INTERNAL_AUTH_TOKEN:-placeholder}"
|
export DEER_FLOW_INTERNAL_AUTH_TOKEN="${DEER_FLOW_INTERNAL_AUTH_TOKEN:-placeholder}"
|
||||||
@@ -232,11 +231,6 @@ if [ "$CMD" = "build" ]; then
|
|||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Docker socket is needed for compose to parse volume specs
|
|
||||||
if [ -z "$DEER_FLOW_DOCKER_SOCKET" ]; then
|
|
||||||
export DEER_FLOW_DOCKER_SOCKET="/var/run/docker.sock"
|
|
||||||
fi
|
|
||||||
|
|
||||||
"${COMPOSE_CMD[@]}" build
|
"${COMPOSE_CMD[@]}" build
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -270,20 +264,25 @@ if [ "$sandbox_mode" = "provisioner" ]; then
|
|||||||
services="$services provisioner"
|
services="$services provisioner"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── DEER_FLOW_DOCKER_SOCKET ───────────────────────────────────────────────────
|
# ── DEER_FLOW_DOCKER_SOCKET (aio / pure-DooD mode only) ──────────────────────
|
||||||
|
# Only aio mode (AioSandboxProvider without provisioner_url) needs the host
|
||||||
|
# Docker socket. It is mounted via the opt-in docker-compose.dood.yaml overlay,
|
||||||
|
# appended here, so the default (local) and provisioner modes never expose the
|
||||||
|
# host daemon. Mounting the socket = root-equivalent host control; see SECURITY.md.
|
||||||
|
|
||||||
if [ -z "$DEER_FLOW_DOCKER_SOCKET" ]; then
|
if [ -z "$DEER_FLOW_DOCKER_SOCKET" ]; then
|
||||||
export DEER_FLOW_DOCKER_SOCKET="/var/run/docker.sock"
|
export DEER_FLOW_DOCKER_SOCKET="/var/run/docker.sock"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$sandbox_mode" != "local" ]; then
|
if [ "$sandbox_mode" = "aio" ]; then
|
||||||
if [ ! -S "$DEER_FLOW_DOCKER_SOCKET" ]; then
|
if [ ! -S "$DEER_FLOW_DOCKER_SOCKET" ]; then
|
||||||
echo -e "${RED}⚠ Docker socket not found at $DEER_FLOW_DOCKER_SOCKET${NC}"
|
echo -e "${RED}⚠ Docker socket not found at $DEER_FLOW_DOCKER_SOCKET${NC}"
|
||||||
echo " AioSandboxProvider (DooD) will not work."
|
echo " AioSandboxProvider (DooD) will not work."
|
||||||
exit 1
|
exit 1
|
||||||
else
|
|
||||||
echo -e "${GREEN}✓ Docker socket: $DEER_FLOW_DOCKER_SOCKET${NC}"
|
|
||||||
fi
|
fi
|
||||||
|
echo -e "${GREEN}✓ Docker socket: $DEER_FLOW_DOCKER_SOCKET${NC}"
|
||||||
|
echo -e "${YELLOW} Mounting host Docker socket into gateway (DooD = host root-equivalent). See SECURITY.md.${NC}"
|
||||||
|
COMPOSE_CMD+=(-f "$DOCKER_DIR/docker-compose.dood.yaml")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -196,6 +196,20 @@ start() {
|
|||||||
services="frontend gateway provisioner nginx"
|
services="frontend gateway provisioner nginx"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Only aio mode (AioSandboxProvider without provisioner_url) needs the host
|
||||||
|
# Docker socket. Mount it via the opt-in docker-compose.dood.yaml overlay so
|
||||||
|
# the default (local) and provisioner modes never expose the host daemon.
|
||||||
|
# Mounting the socket = root-equivalent host control; see SECURITY.md.
|
||||||
|
if [ "$sandbox_mode" = "aio" ]; then
|
||||||
|
local docker_socket="${DEER_FLOW_DOCKER_SOCKET:-/var/run/docker.sock}"
|
||||||
|
if [ ! -S "$docker_socket" ]; then
|
||||||
|
echo -e "${YELLOW}⚠ Docker socket not found at $docker_socket — AioSandboxProvider (DooD) will not work.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e "${YELLOW}Mounting host Docker socket into gateway (DooD = host root-equivalent). See SECURITY.md.${NC}"
|
||||||
|
COMPOSE_CMD="$COMPOSE_CMD -f $DOCKER_DIR/docker-compose.dood.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "${BLUE}Runtime: Gateway embedded agent runtime${NC}"
|
echo -e "${BLUE}Runtime: Gateway embedded agent runtime${NC}"
|
||||||
echo -e "${BLUE}Detected sandbox mode: $sandbox_mode${NC}"
|
echo -e "${BLUE}Detected sandbox mode: $sandbox_mode${NC}"
|
||||||
if [ "$sandbox_mode" = "provisioner" ]; then
|
if [ "$sandbox_mode" = "provisioner" ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user