00dev-org/sandboxeed
GitHub: 00dev-org/sandboxeed
一个基于 Docker 的轻量级沙箱工具,通过 Squid 代理实现网络隔离,为 AI 编程 Agent 提供统一、安全的运行环境。
Stars: 1 | Forks: 0
# sandboxeed
一个基于 Docker 的沙箱,通过 Squid 代理实现过滤的互联网访问。
为您所有的 AI 编程 Agent 提供统一的环境。
它解决了流行 Agent 工具中的常见问题:
- Claude 创建空 dotfile 导致 git 崩溃
命令:https://github.com/anthropic-experimental/sandbox-runtime/issues/139
- 启用沙箱时 Codex 无网络访问
- OpenCode 或 Copilot CLI 没有内置沙箱
- 使用不同 AI 工具时沙箱体验不一致
## 架构
```
HOST
+--------------------------------------------------------------------------+
| |
| $ sandboxeed claude |
| | |
| | manages container lifecycle |
| | |
| +-----+----------------- internal network ----------------------------+ |
| | | |
| | +-----------------------------+ +-----------------------------+ | |
| | | sandbox | | squid proxy | | |
| | | |-->| | | |
| | | AI agent | | [+] github.com | | |
| | | (claude / codex / | | [+] api.openai.com | | |
| | | gemini / opencode / ...) | | [-] everything else -> 403 | | |
| | | | +---------------+-------------+ | |
| | | /workspace <- ./ | | | |
| | | (host dir, always mounted) | | egress network | |
| | +-----------------------------+ | | |
| | | | |
| | +-----------------------------+ | | |
| | | DinD (optional) | | | |
| | | Podman-in-Docker | | | |
| | | unix:///var/sock/... | | | |
| | +-----------------------------+ | | |
| +------------------------------------------------------+--------------+ |
| | |
+---------------------------------------------------------+----------------+
|
v
+-----------------+
| internet |
| (allowed only) |
+-----------------+
```
沙箱容器**没有直接的互联网访问**——所有出站流量都
被强制通过 Squid 代理,该代理仅将连接转发到您配置的
`domains`。每次运行会创建两个 Docker 网络:一个由沙箱、代理和可选 DinD sidecar 共享的仅内部网络,以及一个仅允许代理连接到互联网的出口网络。
## 系统要求
- Linux/macOS
- Docker 或 Podman(通过兼容 Docker 的 `docker` CLI)
- Go 1.25+(从源码构建)
当 `docker` 指向兼容 Podman 的 CLI 时支持 Podman。在 macOS 上,sandboxeed 在关闭时使用 Podman 特有的强制容器移除路径,以避免缓慢的代理拆除。
## 安装
```
go build -o sandboxeed .
```
或者如果可用,使用预构建的二进制文件。
## 快速开始
1. 将您的 Agent 状态复制到 sandboxeed 管理的路径中,这样不安全的沙箱配置(自动批准、绕过权限)可以与您安全的主机配置保持分离:
```
mkdir -p ~/.sandboxeed
cp -a ~/.claude ~/.sandboxeed/.claude
cp -a ~/.claude.json ~/.sandboxeed/.claude.json
```
2. 复制适用于您 Agent 的示例配置:
```
cp examples/claude/Dockerfile.sandbox ./Dockerfile.sandbox
cp examples/claude/sandboxeed.yaml ./sandboxeed.yaml
```
3. 构建沙箱镜像:
```
sandboxeed --build
```
4. 运行您的 Agent:
```
sandboxeed claude
```
查看 [Agent 示例](#agent-examples) 获取所有支持的 Agent。
## 用法
```
# 在 sandbox 中启动交互式 shell
sandboxeed
# 运行 AI agent 工具
sandboxeed claude
# 在 sandbox 内运行任意命令
sandboxeed node script.js
# 构建 sandbox 镜像,然后运行命令
sandboxeed --build claude
# 仅构建 sandbox 镜像(无命令)
sandboxeed --build
# 为此运行跳过 Docker-in-Docker(如果已启用)
sandboxeed --no-docker claude
# 打印当前版本
sandboxeed --version
# 移除残留的 sandboxeed Docker 资源
sandboxeed --cleanup
```
### 标志
| 标志 | 描述 |
|---------------|------------------------------------------------------------------------------------------|
| `--build` | 构建 Docker 镜像(总是 `--no-cache`);如果提供了命令,则在构建后运行它 |
| `--no-docker` | 即使 `sandboxeed.yaml` 中设置了 `docker: true`,也跳过 Docker-in-Docker |
| `--read-only` | 将所有卷(包括项目目录)在沙箱内挂载为只读 |
| `--unsafe` | 以特权模式运行 DinD(不安全,仅用于测试) |
### 内置命令
- `--help` - 显示用法摘要(也可以使用 `-h`)
- `--version` - 打印应用版本
- `--inspect` - 打印有效的合并沙箱配置而不启动容器
- `--cleanup` - 列出 sandboxeed 管理的容器、网络和卷,并在确认后移除它们;镜像不会被移除
Sandboxeed 选项仅在沙箱命令启动前解析。第一个位置参数总是沙箱命令,其后的每个参数都属于沙箱命令。当命令以 `-` 或 `--` 开头时,使用 `--` 强制将下一个参数视为沙箱命令。如果未给出命令,将打开一个交互式 `bash` shell。
### 自动构建
当在 `sandboxeed.yaml` 中设置了 `build.dockerfile` 且配置的镜像在本地不存在时,sandboxeed 会在启动沙箱前自动构建它。使用 `--build` 可强制重新构建。
## 启动序列
当您运行 `sandboxeed` 时:
1. 如果 `github.com` 或 `.github.com` 在您的 `domains` 列表中,将从 GitHub API 获取 GitHub 当前的 SSH 主机密钥,并将其注入到沙箱的 `/etc/ssh/` 中,同时注入一个 SSH 配置,该配置通过 `corkscrew` 经由代理路由 `github.com`。
2. 一个 Squid 代理容器启动,其中包含您配置的域名的允许列表。
3. 沙箱容器启动并连接到内部 Docker 网络——所有出站流量都通过代理,所有不在您域名列表中的内容都会被阻止。
4. 当沙箱退出时,sandboxeed 会移除代理、沙箱、任何 DinD sidecar 以及所有每次运行的网络和卷。
每次运行都使用唯一命名的容器、网络和卷,因此崩溃或中断的运行不会阻塞下一次运行,并且多个沙箱可以从同一目录并发运行。
## 配置
配置按以下顺序加载:
1. 内置默认值
2. `~/.sandboxeed.yaml` 中的可选用户配置
3. `./sandboxeed.yaml` 中的可选项目配置
后面的层覆盖前面的层。环境变量按变量名合并,卷挂载按容器目标路径合并,域名去重。
对于可复用的用户管理沙箱镜像,`sandbox.image` 和 `sandbox.build.dockerfile` 可以放在 `~/.sandboxeed.yaml` 中。项目配置可以原样继承该对,使用自己的构建设置覆盖两者,或仅覆盖 `image` 以使用不同的预构建镜像。
当前目录总是挂载到沙箱内的 `/workspace`。您配置的任何卷都在该默认值之上合并。
在您的项目目录中放置一个 `sandboxeed.yaml`:
```
sandbox:
build:
dockerfile: Dockerfile.sandbox # Dockerfile for --build and auto-build
image: my-custom-image:latest # Image to run (required when this file is present)
volumes:
- ~/.gitconfig:/root/.gitconfig:ro
environment:
- MY_VAR=value
working_dir: /workspace # default: /workspace
docker: true # enable Docker-in-Docker support
memory: 2g # sandbox container memory limit
cpus: "2" # sandbox container CPU limit
pids: 512 # sandbox container PID limit
domains: # whitelisted outbound domains
- github.com
- api.example.com
- .docker.io # matches docker.io and all subdomains
```
### 配置字段
| 字段 | 描述 |
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `build.dockerfile` | Dockerfile 的路径。供 `--build` 使用,以及当本地找不到配置的镜像标签时用于自动构建。当使用 `--build` 但未指定显式路径时,默认为 `Dockerfile`。 |
| `image` | 当存在 `sandboxeed.yaml` 时为必填项。要运行的 Docker 镜像(以及使用 `--build` 时要标记的镜像)。 |
| `volumes` | 在默认的 `.:/workspace` 挂载之后添加的额外卷挂载。支持 `~`、`./` 和 `../` 路径前缀。当多个条目指向同一容器路径时,最后一个配置层胜出。 |
| `environment` | 在代理默认值(`HTTP_PROXY`、`HTTPS_PROXY`、`NO_PROXY`)之后添加的额外环境变量。当同一变量出现多次时,最后一个配置层胜出。 |
| `working_dir` | 容器内的工作目录。默认值:`/workspace`。 |
| `docker` | 设置为 `true` 以启动 Docker-in-Docker sidecar(参见 [Docker-in-Docker](#docker-in-docker))。 |
| `memory` | 沙箱容器的内存限制,传递给 Docker 作为 `--memory`。在项目配置和用户配置中均支持。 |
| `cpus` | 沙箱容器的 CPU 限制,传递给 Docker 作为 `--cpus`。在项目配置和用户配置中均支持。 |
| `pids` | 沙箱容器的 PID 限制,传递给 Docker 作为 `--pids-limit`。在项目配置和用户配置中均支持。 |
| `domains` | 允许沙箱访问的域名。所有其他出站流量都会被阻止。用户和项目域名会合并并去重。 |
### 用户配置
在 `~/.sandboxeed.yaml` 中保留个人默认值。那里仅支持以下字段:
- `sandbox.build.dockerfile`
- `sandbox.image`
- `sandbox.volumes`
- `sandbox.environment`
- `sandbox.domains`
- `sandbox.memory`
- `sandbox.cpus`
- `sandbox.pids`
用户或项目配置中的 `sandbox.build.dockerfile` 要求同一配置层中有 `sandbox.image`,以便构建始终针对可预测的镜像名称。
`working_dir` 和 `docker` 等字段属于项目配置。
如果它们出现在用户配置中,sandboxeed 将报错。
使用它可以避免将与用户相关的更改提交到项目仓库,并避免跨多个项目重复。
例如:为您使用的 AI Agent 挂载配置,设置通用环境变量,或将常用域名列入白名单。
### 分离沙箱和主机配置
**最佳实践:** 从 `~/.sandboxeed/` 挂载 sandboxeed 管理的 Agent 状态副本,而不是
直接挂载您真实的主机路径。
沙箱配置故意不安全——批准提示被禁用,权限被
绕过,工具以完全访问权限运行。如果您将真实的 `~/.claude` 或 `~/.codex` 挂载到
沙箱,那些宽松的设置会被写入您的主机配置。下次您在沙箱外运行
Agent 时,它会继承不安全的沙箱设置。另一方面,
沙箱也可以读取您的主机 API 密钥、会话历史,或注入稍后在主机上执行的内容(MCP 服务器条目、shell 钩子、自定义斜杠命令)。
在 `~/.sandboxeed/` 中保留单独的副本意味着沙箱和主机配置独立演进。
每个 Agent 设置一次:
```
mkdir -p ~/.sandboxeed
# Claude Code
cp -a ~/.claude ~/.sandboxeed/.claude
cp -a ~/.claude.json ~/.sandboxeed/.claude.json
# Codex CLI
cp -a ~/.codex ~/.sandboxeed/.codex
# Gemini CLI
cp -a ~/.gemini ~/.sandboxeed/.gemini
# OpenCode
mkdir -p ~/.sandboxeed/.config ~/.sandboxeed/.local/share
cp -a ~/.config/opencode ~/.sandboxeed/.config/opencode
cp -a ~/.local/share/opencode ~/.sandboxeed/.local/share/opencode
```
然后在您的配置中从 `~/.sandboxeed/` 挂载。每当您想从主机刷新沙箱状态时重新复制(例如,更改 API 密钥或更新设置后)。
用户配置:
```
# ~/.sandboxeed.yaml
sandbox:
build:
dockerfile: ~/.sandboxeed/Dockerfile
image: localhost/sandboxeed:latest
volumes:
- ~/.sandboxeed/.claude:/home/node/.claude
- ~/.sandboxeed/.claude.json:/home/node/.claude.json
- ~/.sandboxeed/.codex:/home/node/.codex
- ~/.gitconfig:/home/node/.gitconfig:ro
environment:
- DISABLE_AUTOUPDATER=1
memory: 2g
cpus: "2"
pids: 512
domains:
- .anthropic.com
- .claude.ai
- .claude.com
- .openai.com
- .chatgpt.com
```
项目配置:
```
# ./sandboxeed.yaml
sandbox:
build:
dockerfile: Dockerfile.sandbox
image: my-custom-image:latest
environment:
- ENV=DEV # overrides the user config value
domains:
- .github.com # combined with api.openai.com from user config
```
在上面的示例中,最终的允许域名列表包括来自用户配置的 `.anthropic.com`、`.claude.ai`、`.claude.com`、`.openai.com`、`.chatgpt.com` 和来自项目配置的 `.github.com`。`ENV` 变量仅由项目配置设置;用户级卷和 `DISABLE_AUTOUPDATER` 保持不变。
### Agent 示例
作为即用型起点,从 [`examples/`](examples/README.md) 复制适用于您 Agent 的文件:
| 文件 | 范围 | 描述 |
|-------------------------------------------------|--------|--------------------------------------------------------------------|
| [`.sandboxeed.yaml`](examples/.sandboxeed.yaml) | 全局 | 用户配置(`~/.sandboxeed.yaml`),包含共享卷和域名 |
| Agent | 项目文件 |
|---------------------------------|----------------------------------------------------------|
| [Claude Code](examples/claude/) | `Dockerfile.sandbox`, `sandboxeed.yaml`, `settings.json` |
| [Codex CLI](examples/codex/) | `Dockerfile.sandbox`, `sandboxeed.yaml`, `config.toml` |
| [Gemini CLI](examples/gemini/) | `Dockerfile.sandbox`, `sandboxeed.yaml` |
| [OpenCode](examples/opencode/) | `Dockerfile.sandbox`, `sandboxeed.yaml` |
将 `.sandboxeed.yaml` 复制到 `~/.sandboxeed.yaml` 作为全局默认值。将您的 Agent 状态复制到 `~/.sandboxeed/`,如 [分离沙箱和主机配置](#separating-sandbox-and-host-config) 中所述,以便沙箱挂载隔离的副本而不是您真实的主机路径。
每个 Agent 的 `Dockerfile.sandbox` 和 `sandboxeed.yaml` 放在您的项目根目录中。
这些是与项目无关的启动器。您通常需要为实际项目添加语言运行时、包管理器、SDK 或额外的允许域名。
### 无配置文件
如果未找到 `sandboxeed.yaml`,sandboxeed 将运行一个 `bash:latest` 容器,具有:
- 当前目录挂载在 `/workspace`
- 设置了代理环境变量(`HTTP_PROXY`、`HTTPS_PROXY`、`NO_PROXY`)
- 无出站互联网访问(所有流量被代理阻止)
## Dockerfile 要求
sandboxeed 在运行时向沙箱注入几项内容——您的 Dockerfile 不需要设置这些:
| 内容 | 位置 | 备注 |
|-------------------|--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| SSH 客户端配置 | `/etc/ssh/ssh_config` | 仅当 `github.com` 或 `.github.com` 在 `domains` 中时。通过 `corkscrew` 经由代理路由 `github.com`;覆盖该路径下的任何现有文件 |
| GitHub 主机密钥 | `/etc/ssh/ssh_known_hosts` | 仅当 `github.com` 或 `.github.com` 在 `domains` 中时。启动时从 GitHub API 获取;`StrictHostKeyChecking yes` |
| 代理环境变量 | `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` | 每次运行设置 |
| Docker socket | `DOCKER_HOST=unix:///var/sock/podman.sock` | 仅当 `docker: true` 时;通过卷从 Podman-in-Docker sidecar 共享 |
| 项目挂载 | `.:/workspace` | 当前目录,总是挂载 |
**您的 Dockerfile 必须提供:**
- **`corkscrew`** - 当 `github.com` 或 `.github.com` 在 `domains` 中时为必选项。用于通过 HTTP 代理隧道传输 SSH;没有它,通过 SSH 的 `git clone` 将失败。
- **`docker` CLI** - 当设置 `docker: true` 时为必选项。DinD sidecar 提供守护进程,但镜像中必须存在客户端。
**推荐模式:**
- 创建 `/workspace` 并将其 `chown` 给运行时用户,以便默认卷挂载可写- 以 root 身份安装您的工具和运行时,然后在 `CMD` 之前使用 `USER` 切换到非 root 用户。注入到 `/etc/ssh/` 的文件只需要可读,因此运行时不需要 root 访问权限。
- 如果工具不支持 `HTTP_PROXY`/`HTTPS_PROXY`,请在 Dockerfile 中显式配置其代理设置(例如,npm 的 `proxy` 配置或工具特定的配置文件)。
## Docker-in-Docker
当配置中设置 `docker: true` 时,sandboxeed 会在内部网络上启动一个 Podman-in-Docker sidecar 容器。沙箱容器通过 `DOCKER_HOST=unix:///var/sock/podman.sock` 自动配置为使用它,Podman socket 通过 Docker 卷在 sidecar 和沙箱之间共享。
与传统的 `docker:dind` 不同,Podman sidecar **不**需要 `--privileged`。它以最小的一组能力运行(用于 fuse-overlayfs 存储的 `SYS_ADMIN`、`seccomp=unconfined` 和 `apparmor=unconfined`)。这阻止了最常见的容器逃逸向量——sidecar 无法访问主机块设备或主机设备 cgroup。
对于需要完全特权访问的测试场景(例如,创建自定义容器网络),使用 `--unsafe` 标志以特权模式运行 DinD sidecar。
启动 DinD sidecar 通常会给沙箱启动时间增加约 10 秒。要跳过单次运行的开销,即使设置了 `docker: true`,也请使用 `sandboxeed --no-docker ...`。
使用 Docker-in-Docker 时,请确保您的 `domains` 列表包含您需要拉取的注册表(例如,Docker Hub 镜像层的 `.docker.io`、`.cloudflarestorage.com`)。
## 网络隔离
沙箱容器**没有直接的互联网访问**。所有流量都通过 Squid 代理路由,该代理仅允许连接到 `domains` 下列出的域名。空的 `domains` 列表会阻止所有出站流量。
每次运行创建两个 Docker 网络:
- `-internal-` - 连接沙箱、代理和可选的 DinD sidecar(内部,无外部路由)
- `-egress-` - 将代理连接到外部世界
当启用 Docker-in-Docker 时,sandboxeed 为 DinD sidecar 内的 `/var/lib/containers`(Podman 存储)和 `/var/sock`(共享 API socket)创建每次运行带标签的卷,以便可以可靠地清理它们。
## 安全注意事项
sandboxeed 减少了运行不受信任或半受信任代码的攻击面,但它**不是完整的安全边界**。请记住以下几点:
- **卷挂载会暴露主机文件。** `volumes` 下列出的任何路径都可以在沙箱内直接访问。挂载到容器中的敏感文件(SSH 密钥、API token、配置文件)可以被读取或泄露到允许的域名。尽可能使用 `:ro` 挂载,避免挂载不需要的凭证。如果您挂载 Git SSH 密钥,请使用具有您能强制执行的最窄仓库或项目范围的专用密钥,而不是更广泛的个人密钥。
示例配置从 `~/.sandboxeed/`(sandboxeed 管理的 Agent 状态副本)挂载,而不是您真实的主机路径。这至关重要,因为沙箱配置使用宽松的自动批准设置,这在主机上是危险的;直接挂载您的真实配置会导致那些不安全的设置泄露回去。有关设置说明,请参阅 [分离沙箱和主机配置](#separating-sandbox-and-host-config)。
您也可以使用 `--read-only` 运行,以强制所有挂载(包括项目目录)为只读模式。
- **示例配置故意是宽松的。** `examples/` 中的文件禁用了批准提示和安全检查,以便在一次性环境中使用方便。
**不要在沙箱外使用它们。**
- **域名过滤并非万无一失。** Squid 代理按域名过滤,而不是按 IP。DNS rebinding、通过允许的域名隧道传输(例如,通过受损的 CDN)或通过 DNS 本身的泄露都无法被阻止。
- **Docker-in-Docker 能力。** Podman-in-Docker sidecar 以 `SYS_ADMIN`、`seccomp=unconfined` 和 `apparmor=unconfined` 运行——足以支持 fuse-overlayfs,但不足以访问主机设备。使用 `--unsafe` 时,sidecar 以完全特权运行,这授予主机内核访问权限,应仅用于测试。
- **无系统调用过滤。** 沙箱容器以 Docker 的默认 seccomp 配置文件运行,但没有额外限制。它不等同于像 gVisor 或 Firecracker 这样的 VM 级隔离边界。
- **代理绕过。** 如果沙箱容器获得操纵其自身网络命名空间或路由的能力(例如,通过 `CAP_NET_ADMIN`),它可能完全绕过代理。默认的 Docker 能力集不授予此权限,但自定义镜像或运行时标志可能会。
标签:AI 编程助手, Claude, Codex, CVE检测, Docker, EVTX分析, NIDS, Sandbox, SOC Prime, Squid 代理, 人工智能, 代理过滤, 安全防御评估, 容器化, 开发工具, 日志审计, 本地开发环境, 沙箱, 环境一致性, 用户模式Hook绕过, 网络安全, 网络隔离, 自动化运维, 请求拦截, 轻量级虚拟化, 隐私保护, 隔离执行