fix(docker): allow aio DooD socket preflight on Windows Git Bash (#5370) (#5371)

* fix(docker): allow aio DooD socket preflight on Windows Git Bash

* test(deploy): add regression tests for deploy.sh DooD socket preflight

* fix(docker): restrict Windows DooD socket bypass to default path

Limit the Windows socket bypass in docker.sh and deploy.sh to the default /var/run/docker.sock path, and add regression tests ensuring custom missing socket paths are rejected.

* test(docker): skip unreachable socket controls on hosts with live docker socket

Add @pytest.mark.skipif on Path('/var/run/docker.sock').is_socket() to prevent false test failures on daemon-live hosts.
This commit is contained in:
Dan Caldr 2026-09-12 15:12:36 +02:00 committed by GitHub
parent 81f2015fe6
commit ce635b7de6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 311 additions and 5 deletions

View File

@ -2,6 +2,8 @@
from __future__ import annotations
import os
import shutil
import subprocess
import tempfile
from pathlib import Path
@ -316,3 +318,295 @@ require_compose_version
assert result.returncode == 1, result.stdout + result.stderr
assert "too old" in result.stdout
def test_aio_dood_socket_preflight_allows_windows_when_docker_reachable():
"""Windows Git Bash without /var/run/docker.sock proceeds when Docker daemon is reachable."""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_root = Path(tmpdir)
_seed_compose_file(tmp_root)
_seed_env_examples(tmp_root)
(tmp_root / "config.yaml").write_text(
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
encoding="utf-8",
)
command = f"""
source '{SCRIPT_PATH}'
PROJECT_ROOT='{tmp_root}'
DOCKER_DIR='{tmp_root}'
require_compose_version() {{ :; }}
uname() {{ echo 'MINGW64_NT-10.0'; }}
docker() {{
if [ "$1" = info ]; then
return 0
fi
return 0
}}
DEER_FLOW_DOCKER_SOCKET='/var/run/docker.sock'
COMPOSE_CMD=echo
start
"""
result = subprocess.run(
[BASH_EXECUTABLE, "-lc", command],
capture_output=True,
text=True,
encoding="utf-8",
)
assert result.returncode == 0, result.stdout + result.stderr
assert "docker-compose.dood.yaml" in result.stdout
def test_aio_dood_socket_preflight_rejects_missing_socket_on_posix():
"""POSIX hosts without a physical socket file must fail fast."""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_root = Path(tmpdir)
_seed_compose_file(tmp_root)
_seed_env_examples(tmp_root)
(tmp_root / "config.yaml").write_text(
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
encoding="utf-8",
)
command = f"""
source '{SCRIPT_PATH}'
PROJECT_ROOT='{tmp_root}'
DOCKER_DIR='{tmp_root}'
require_compose_version() {{ :; }}
uname() {{ echo 'Linux'; }}
DEER_FLOW_DOCKER_SOCKET='/nonexistent/docker.sock'
COMPOSE_CMD=echo
start
"""
result = subprocess.run(
[BASH_EXECUTABLE, "-lc", command],
capture_output=True,
text=True,
encoding="utf-8",
)
assert result.returncode == 1, result.stdout + result.stderr
assert "Docker socket not found" in result.stdout
def test_aio_dood_socket_preflight_rejects_missing_custom_socket_on_windows():
"""Windows Git Bash must fail if a custom non-existent socket path is specified."""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_root = Path(tmpdir)
_seed_compose_file(tmp_root)
_seed_env_examples(tmp_root)
(tmp_root / "config.yaml").write_text(
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
encoding="utf-8",
)
command = f"""
source '{SCRIPT_PATH}'
PROJECT_ROOT='{tmp_root}'
DOCKER_DIR='{tmp_root}'
require_compose_version() {{ :; }}
uname() {{ echo 'MINGW64_NT-10.0'; }}
docker() {{
if [ "$1" = info ]; then
return 0
fi
return 0
}}
DEER_FLOW_DOCKER_SOCKET='/nonexistent/docker.sock'
COMPOSE_CMD=echo
start
"""
result = subprocess.run(
[BASH_EXECUTABLE, "-lc", command],
capture_output=True,
text=True,
encoding="utf-8",
)
assert result.returncode == 1, result.stdout + result.stderr
assert "Docker socket not found" in result.stdout
@pytest.mark.skipif(
Path("/var/run/docker.sock").is_socket(),
reason="Host has real /var/run/docker.sock",
)
def test_aio_dood_socket_preflight_rejects_windows_when_docker_unreachable():
"""Windows Git Bash must fail if Docker daemon is not reachable."""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_root = Path(tmpdir)
_seed_compose_file(tmp_root)
_seed_env_examples(tmp_root)
(tmp_root / "config.yaml").write_text(
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
encoding="utf-8",
)
command = f"""
source '{SCRIPT_PATH}'
PROJECT_ROOT='{tmp_root}'
DOCKER_DIR='{tmp_root}'
require_compose_version() {{ :; }}
uname() {{ echo 'MINGW64_NT-10.0'; }}
docker() {{
if [ "$1" = info ]; then
return 1
fi
return 0
}}
DEER_FLOW_DOCKER_SOCKET='/var/run/docker.sock'
COMPOSE_CMD=echo
start
"""
result = subprocess.run(
[BASH_EXECUTABLE, "-lc", command],
capture_output=True,
text=True,
encoding="utf-8",
)
assert result.returncode == 1, result.stdout + result.stderr
assert "Docker socket not found" in result.stdout
def _setup_deploy_worktree(tmp_path: Path) -> Path:
worktree = tmp_path / "repo"
shutil.copytree(REPO_ROOT / "scripts", worktree / "scripts")
shutil.copytree(REPO_ROOT / "docker", worktree / "docker")
(worktree / "backend").mkdir()
(worktree / "config.yaml").write_text(
"sandbox:\n use: deerflow.community.aio_sandbox:AioSandboxProvider\n",
encoding="utf-8",
)
(worktree / "extensions_config.json").write_text("{}\n", encoding="utf-8")
(worktree / ".env").write_text("TEST=1\n", encoding="utf-8")
return worktree
def test_aio_deploy_socket_preflight_allows_windows_when_docker_reachable(tmp_path):
"""deploy.sh on Windows Git Bash proceeds past preflight when Docker daemon is reachable."""
worktree = _setup_deploy_worktree(tmp_path)
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
capture_args = tmp_path / "docker_args.txt"
docker = bin_dir / "docker"
docker.write_text(
f'#!/usr/bin/env sh\nif [ "$1" = "info" ]; then exit 0; fi\nfor arg in "$@"; do printf "%s\\n" "$arg"; done > "{capture_args}"\nexit 0\n',
encoding="utf-8",
)
docker.chmod(0o755)
bash_env = tmp_path / "env.sh"
bash_env.write_text("uname() { echo 'MINGW64_NT-10.0'; }\n", encoding="utf-8")
env = os.environ.copy()
env["PATH"] = f"{bin_dir}{os.pathsep}{env['PATH']}"
env["BASH_ENV"] = str(bash_env)
env["DEER_FLOW_DOCKER_SOCKET"] = "/var/run/docker.sock"
env["BETTER_AUTH_SECRET"] = "test-secret"
env["DEER_FLOW_INTERNAL_AUTH_TOKEN"] = "test-token"
env["UV_EXTRAS"] = "redis"
result = subprocess.run(
[BASH_EXECUTABLE, str(worktree / "scripts" / "deploy.sh"), "start"],
cwd=worktree,
env=env,
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stdout + result.stderr
assert "docker-compose.dood.yaml" in capture_args.read_text(encoding="utf-8")
def test_aio_deploy_socket_preflight_rejects_missing_socket_on_posix(tmp_path):
"""deploy.sh on POSIX hosts without a physical socket file fails fast."""
worktree = _setup_deploy_worktree(tmp_path)
bash_env = tmp_path / "env.sh"
bash_env.write_text("uname() { echo 'Linux'; }\n", encoding="utf-8")
env = os.environ.copy()
env["BASH_ENV"] = str(bash_env)
env["DEER_FLOW_DOCKER_SOCKET"] = "/nonexistent/docker.sock"
env["BETTER_AUTH_SECRET"] = "test-secret"
env["DEER_FLOW_INTERNAL_AUTH_TOKEN"] = "test-token"
env["UV_EXTRAS"] = "redis"
result = subprocess.run(
[BASH_EXECUTABLE, str(worktree / "scripts" / "deploy.sh"), "start"],
cwd=worktree,
env=env,
capture_output=True,
text=True,
)
assert result.returncode == 1, result.stdout + result.stderr
assert "Docker socket not found" in result.stdout + result.stderr
def test_aio_deploy_socket_preflight_rejects_missing_custom_socket_on_windows(tmp_path):
"""deploy.sh on Windows Git Bash fails if a custom non-existent socket path is specified."""
worktree = _setup_deploy_worktree(tmp_path)
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
docker = bin_dir / "docker"
docker.write_text(
'#!/usr/bin/env sh\nif [ "$1" = "info" ]; then exit 0; fi\nexit 0\n',
encoding="utf-8",
)
docker.chmod(0o755)
bash_env = tmp_path / "env.sh"
bash_env.write_text("uname() { echo 'MINGW64_NT-10.0'; }\n", encoding="utf-8")
env = os.environ.copy()
env["PATH"] = f"{bin_dir}{os.pathsep}{env['PATH']}"
env["BASH_ENV"] = str(bash_env)
env["DEER_FLOW_DOCKER_SOCKET"] = "/nonexistent/docker.sock"
env["BETTER_AUTH_SECRET"] = "test-secret"
env["DEER_FLOW_INTERNAL_AUTH_TOKEN"] = "test-token"
env["UV_EXTRAS"] = "redis"
result = subprocess.run(
[BASH_EXECUTABLE, str(worktree / "scripts" / "deploy.sh"), "start"],
cwd=worktree,
env=env,
capture_output=True,
text=True,
)
assert result.returncode == 1, result.stdout + result.stderr
assert "Docker socket not found" in result.stdout + result.stderr
@pytest.mark.skipif(
Path("/var/run/docker.sock").is_socket(),
reason="Host has real /var/run/docker.sock",
)
def test_aio_deploy_socket_preflight_rejects_windows_when_docker_unreachable(tmp_path):
"""deploy.sh on Windows Git Bash fails if Docker daemon is not reachable."""
worktree = _setup_deploy_worktree(tmp_path)
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
docker = bin_dir / "docker"
docker.write_text('#!/usr/bin/env sh\nif [ "$1" = "info" ]; then exit 1; fi\nexit 0\n', encoding="utf-8")
docker.chmod(0o755)
bash_env = tmp_path / "env.sh"
bash_env.write_text("uname() { echo 'MINGW64_NT-10.0'; }\n", encoding="utf-8")
env = os.environ.copy()
env["PATH"] = f"{bin_dir}{os.pathsep}{env['PATH']}"
env["BASH_ENV"] = str(bash_env)
env["DEER_FLOW_DOCKER_SOCKET"] = "/var/run/docker.sock"
env["BETTER_AUTH_SECRET"] = "test-secret"
env["DEER_FLOW_INTERNAL_AUTH_TOKEN"] = "test-token"
env["UV_EXTRAS"] = "redis"
result = subprocess.run(
[BASH_EXECUTABLE, str(worktree / "scripts" / "deploy.sh"), "start"],
cwd=worktree,
env=env,
capture_output=True,
text=True,
)
assert result.returncode == 1, result.stdout + result.stderr
assert "Docker socket not found" in result.stdout + result.stderr

View File

@ -373,9 +373,15 @@ fi
if [ "$sandbox_mode" = "aio" ]; then
if [ ! -S "$DEER_FLOW_DOCKER_SOCKET" ]; then
echo -e "${RED}⚠ Docker socket not found at $DEER_FLOW_DOCKER_SOCKET${NC}"
echo " AioSandboxProvider (DooD) will not work."
exit 1
# On Windows (Git Bash / MSYS), Docker Desktop mounts the default
# /var/run/docker.sock into containers even though no host socket file exists.
if [ "$DEER_FLOW_DOCKER_SOCKET" = "/var/run/docker.sock" ] && [[ "$(uname -s)" =~ ^(MINGW|MSYS|CYGWIN) ]] && docker info >/dev/null 2>&1; then
:
else
echo -e "${RED}⚠ Docker socket not found at $DEER_FLOW_DOCKER_SOCKET${NC}"
echo " AioSandboxProvider (DooD) will not work."
exit 1
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}"

View File

@ -335,8 +335,14 @@ start() {
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
# On Windows (Git Bash / MSYS), Docker Desktop mounts the default
# /var/run/docker.sock into containers even though no host socket file exists.
if [ "$docker_socket" = "/var/run/docker.sock" ] && [[ "$(uname -s)" =~ ^(MINGW|MSYS|CYGWIN) ]] && docker info >/dev/null 2>&1; then
:
else
echo -e "${YELLOW}⚠ Docker socket not found at $docker_socket — AioSandboxProvider (DooD) will not work.${NC}"
exit 1
fi
fi
echo -e "${YELLOW}Mounting host Docker socket into gateway (DooD = host root-equivalent). See SECURITY.md.${NC}"
COMPOSE_CMD="$COMPOSE_CMD -f docker-compose.dood.yaml"