一个为 AI Agent 设计的跨平台 OS 级沙箱:在操作系统层面强制施加文件系统与进程限制,无需容器运行时。当前支持 Linux(Landlock + seccomp + namespaces)与 macOS(Seatbelt)。
设计目标:让 Agent 的命令执行默认受限,用可声明的策略建立安全边界。
- 跨平台:平台无关的
Sandboxtrait,各平台用原生机制实现(Linux = Landlock + seccomp + namespaces;macOS = Seatbelt / SBPL +sandbox-exec) - 文件系统:Landlock ACL(路径级读/写/执行,声明式、零挂载)
- 进程隔离:7 种 namespace(user/ipc/mnt/pid/net/uts/cgroup)
- syscall 过滤:动态 seccomp(精确拦截 + USER_NOTIF 结构化诊断)
- 超时控制,防止命令无限挂起
- CLI + Crate API 双集成,结构化退出原因
名字取自 CodeWhale(鲸鱼)的栖息地 sea + sandbox 的 box——装着鲸鱼的那片海。
核心理念:Agent 每次执行命令都应默认受限。用户不需要对每个命令弹窗做判断——配置一次策略,Agent 自动遵守。
Agent 会代表用户频繁执行 shell 命令、修改文件、运行构建。传统权限弹窗有两个根本缺陷:
- 疲劳决策:用户面对连续弹窗,最终形成"看见就点允许"的肌肉记忆
- 粒度过粗:要么全允许、要么全拒绝,没有中间态
内核级沙箱用策略驱动解决:用户事先声明文件系统权限(--landlock /:ro --landlock /tmp:rw)和 syscall 黑名单(--seccomp-deny-nr 165),Agent 在策略范围内自动放行,越界时精确拦截并给出结构化诊断。
- 🛡️ Landlock 文件系统 ACL(5.13+,ABI v1-v7):grant-only 白名单模型,路径级读/写/执行控制
- 🚫 动态 seccomp:
--seccomp-deny-nr <NR>用 USER_NOTIF 精确拦截指定 syscall(拒绝时携带 syscall 名/分类/号/架构的富诊断消息);--seccomp-filter-fd <FD>追加外部原始 cBPF - 🧱 7 种 namespace 隔离:User / IPC / Mount / PID / Net / UTS / Cgroup,非 root 下 user ns 自动启用
- 🔗 网络隔离与 loopback 控制:
--unshare-net完全断网,--allow-network放通 127.0.0.1 - 📦 mount namespace:
--bind/--ro-bind/--tmpfs,无需 Docker 即可做只读根 + 可写目录 - ⏱️ 超时控制:
--timeout/--timeout-max防止命令无限挂起 - 🌐 环境控制:
--env/--unsetenv/--clearenv - 🔌 两种集成方式:CLI 二进制 +
pub trait Sandboxcrate API(一一对应) - 🍎 macOS Seatbelt 后端:三种预设 + 12 个
--mac-*旗标,SBPL profile 生成,fd 清扫防继承逃逸 - 🧪 171 项测试:按能力维度分文件,内核能力缺失时自动跳过(不是 fail)
| 平台 | 状态 | 机制 |
|---|---|---|
| Linux 5.13+ | ✅ | Landlock + seccomp + namespaces |
| Linux 3.5 – 5.12 | seccomp(无 Landlock 文件系统限制) | |
| macOS | ✅ 已在 27.0.1 / arm64 实测 | Seatbelt(SBPL + sandbox-exec) |
Seatbelt /
sandbox-exec自 macOS 10.5 起就存在,但本项目仅在 27.0.1 / arm64 上实测过; 更旧版本未验证,不在此承诺。
两个后端的配置面双向互斥,误用会硬错误而不是静默忽略:macOS 上 --landlock / --unshare-* /
--seccomp-* / --cap-* / --bind 均报错并提示改用 --mac-*;Linux 上 --mac-* 报错并提示改用 --landlock。
# 三种预设
seabox run --mac-preset read-only -- ls /etc # 读全盘,仅 TMPDIR 可写
seabox run --mac-preset workspace-write -- cargo build # 可写 cwd + TMPDIR
seabox run --mac-preset full-access -- make # 不限制(仍有硬限制,见下)
# 自定义:预设只填默认值,字段总能覆盖或追加
seabox run --mac-preset workspace-write --mac-write ~/.cargo -- cargo build
seabox run --mac-write ./target --mac-write ~/.cargo -- cargo build # 无预设,走安全基线
# 读写隔离
seabox run --mac-read /usr --mac-read /System --mac-read $PWD -- ls--mac-read PATH 出现即切到 Only 模式(真正的读隔离,仅 macOS 可用)。
预设默认是 All(读全盘 + 凭据黑名单),只有显式给 --mac-read 才收窄。
注意 --mac-write 是递归的,含整棵子树与删除权。
| 限制 | 细节 |
|---|---|
| setuid 程序不可用 | ps top su atq crontab newgrp 全部被拒 |
| SIP 路径不可写 | /etc /System 等仍受系统保护 |
seabox check 会单独列出这两条。也正因如此,「不配预设」实现为不进入 sandbox-exec,
以免白白损失 ps 这类排障工具的能力。
cargo install seabox当前版本:0.1.0(首个支持 macOS 的版本,含 breaking 变更, 升级前请读 CHANGELOG.md)。
curl -L https://github.com/zsdfbb/seabox/releases/latest/download/seabox-x86_64-linux-musl.tar.gz | tar xz
sudo mv seabox /usr/local/bin/git clone https://github.com/zsdfbb/seabox
cd seabox
cargo build --release
./target/release/seabox --help[dependencies]
seabox = "0.1"0.1.0 有两处 breaking:
DenyMechanism新增Seatbelt变体、SandboxConfig新增macos字段(两者均无#[non_exhaustive])。 对ExitReason做穷尽match时加_ => ..;构造SandboxConfig时用..Default::default()。详见 CHANGELOG.md。
# 1. 检查当前系统沙箱能力
seabox check
# Capability Status
# ----------------------------- --------------------
# Landlock available (ABI v7)
# Seccomp available
# User namespace available
# ...
# 2. 在只读沙箱里跑一个命令
seabox run --landlock '/:ro' -- cat /etc/passwd
# 3. 工作区模式(/tmp 可写,其余只读)
seabox run --landlock '/:ro' --landlock '/tmp:rw' -- sh -c 'echo data > /tmp/out.txt'
# 4. 完整隔离(所有 namespace + 断网)
seabox run --unshare-all -- ls -laseabox check # Seatbelt (sandbox-exec) available (/usr/bin/sandbox-exec)
# 只读:读全盘,仅 TMPDIR 可写
seabox run --mac-preset read-only -- ls /etc
# 工作区:可写 cwd + TMPDIR
seabox run --mac-preset workspace-write -- cargo build| 子命令 | 说明 |
|---|---|
run |
在沙箱中执行命令 |
check |
打印当前系统可用的沙箱能力(Linux:Landlock / seccomp / namespace;macOS:Seatbelt) |
serve |
HTTP API 服务器(尚未实现,该方向已暂缓,见路线图) |
文件系统
| 选项 | 说明 |
|---|---|
--landlock <PATH:PERM> |
Landlock 路径权限规则(可重复)。权限预设:ro(读+执行)/ rw(读写)/ rwx(+设备创建)/ all。示例:--landlock '/:ro' --landlock '/tmp:rw' |
命名空间(非 root 下 --unshare-pid / --unshare-net 自动叠加 user ns)
| 选项 | 说明 |
|---|---|
--unshare-all |
隔离全部 7 种 namespace(user/ipc/mnt/pid/net/uts/cgroup) |
--unshare-user / --unshare-ipc / --unshare-mnt / --unshare-pid / --unshare-net / --unshare-uts / --unshare-cgroup |
逐类隔离 |
--unshare-user-try / --unshare-cgroup-try |
软性版本:内核不支持时静默回退 |
网络(语义:--share-net / --allow-network 会抑制 --unshare-net)
| 选项 | 说明 |
|---|---|
--unshare-net |
隔离网络 namespace,lo DOWN,子进程无网络访问 |
-n, --allow-network |
与 --unshare-net 组合时放通 loopback(127.0.0.1/8);单独使用时等价于共享宿主机网络 |
--share-net |
bwrap 兼容别名,等价于 --allow-network |
mount namespace(需配合 --unshare-mnt,非 root 下还需 --unshare-user)
| 选项 | 说明 |
|---|---|
--bind <SRC> <DST> |
可写递归 bind mount |
--ro-bind <SRC> <DST> |
只读递归 bind mount |
--tmpfs <DST> |
在 DST 挂载空的内存文件系统 |
用户与主机名
| 选项 | 说明 |
|---|---|
--uid <UID> / --gid <GID> |
在 user ns 中映射的 uid/gid(需 --unshare-user) |
--hostname <NAME> |
在 UTS ns 中设置主机名(需 --unshare-uts) |
环境变量
| 选项 | 说明 |
|---|---|
--env <KEY=VALUE> |
设置或覆盖环境变量(可重复) |
--unsetenv <KEY> |
删除环境变量(可重复) |
--clearenv |
清空环境(不从父进程继承) |
超时
| 选项 | 说明 |
|---|---|
--timeout <SECS> |
命令超时秒数(默认 30) |
--timeout-max <SECS> |
超时上限秒数(默认 300,影响默认值上限) |
seccomp(动态策略,无默认黑名单——显式指定要拦截的 syscall)
| 选项 | 说明 |
|---|---|
--seccomp-deny-nr <NR> |
用 USER_NOTIF 拦截指定 syscall 号(可重复)。常见号(x86_64):165=mount、97=unshare、173=ptrace |
--seccomp-filter-fd <FD> |
从 fd 读取原始 cBPF 字节码并追加到 filter 链 |
macOS Seatbelt(仅 macOS;在 Linux 上使用会硬错误)
| 选项 | 说明 |
|---|---|
--mac-preset <NAME> |
预设:read-only / workspace-write / full-access。只填默认值,任何 --mac-* 都可覆盖对应项 |
--mac-write <PATH> |
可写目录(可重复)。递归含整棵子树与删除权 |
--mac-deny-write <PATH> |
写拒绝目录(可重复;叠加在 --mac-write 之上,后写赢) |
--mac-workspace <DIR> |
workspace-write 预设的写根(默认 cwd)。语义是替换而非追加 |
--mac-read <PATH> |
读白名单(可重复)。出现即切到 Only 模式,实现真正读隔离 |
--mac-read-all |
读全开(默认;显式覆盖更早的 --mac-read) |
--mac-deny-read <PATH> |
读拒绝目录(可重复;叠加在内置凭据清单之上) |
--mac-net <SPEC> |
deny / loopback / all / 端口列表如 443,80 |
--mac-net-inbound |
放通 loopback 入站连接 |
--mac-no-credential-deny |
关闭内置凭据拒绝清单(~/.ssh、~/.aws 等;默认开启) |
--mac-no-git-write |
不在 workspace 内放行 .git 写(默认放行;关闭后 git add 会卡在 index.lock) |
--mac-no-tmp-writable |
TMPDIR 不可写(默认在预设下可写) |
不指定任何
--mac-*且无预设 ⇒ 不施加 Seatbelt(保留ps等 setuid 工具能力)。 详见docs/macos-sandbox.md。
其他
| 选项 | 说明 |
|---|---|
--chdir <DIR> |
覆盖工作目录 |
-d, --debug |
调试输出 |
-V, --version |
打印版本 |
-- <COMMAND>... |
要执行的命令(-- 分隔选项与命令) |
| 退出码 | 含义 |
|---|---|
0 |
命令成功执行 |
124 |
超时被强制杀死(timeout(1) 惯例) |
125 |
沙箱内部错误 |
126 |
沙箱拒绝(Landlock / seccomp / namespace) |
| 命令原始退出码 | 命令自行以非零状态退出(与沙箱无关) |
128+N |
命令被信号 N 杀死(signal-death 不退化为 1) |
拒绝时 stderr 输出结构化诊断,如 Blocked by seccomp filter (SIGSYS): syscall='mount' category='mount' nr=165 arch=0xc000003e。
⚠️ macOS 例外:CLI 走流式spawn(Inherit)拿不到 stderr,因此 Seatbelt 拒绝 退化为命令原始非零退出(通常1)而非126。要结构化的Denied { mechanism: Seatbelt, .. }请用库 APISandbox::execute()。
seabox 通过 execve 直接执行单个程序,不经过 shell。>、>>、|、*、&& 等元字符会原样传给 execve。
# ❌ 失败:找不到名为 "echo 'hello' >> /tmp/out" 的程序
seabox run -- "echo 'hello' >> /tmp/out"
# ✅ 正确:整条 shell 命令作为 sh -c 的参数
seabox run -- sh -c "echo 'hello' >> /tmp/out"作为库嵌入时,通过 SandboxConfig + CommandSpec + Sandbox trait 三件套。每个 CLI flag 都有对应的 with_* 方法。
use seabox::config::SandboxConfig;
use seabox::{CommandSpec, ExitReason, Sandbox};
use std::path::PathBuf;
use std::time::Duration;
// ── 构建配置(等价于 CLI: --unshare-all --landlock '/:ro' --landlock '/tmp:rw')──
let config = SandboxConfig::default()
.with_unshare_all()
.with_landlock("/:ro")?
.with_landlock("/tmp:rw")?
.with_timeout(10, 60);
// ── 创建沙箱(Linux 用 Landlock + seccomp + namespace;macOS 用 Seatbelt)──
let sandbox = Sandbox::from_config(config)?;
// ── 命令规格 ──
let spec = CommandSpec::default()
.with_program("sh")
.with_args(["-c", "echo $MSG"])
.with_cwd(PathBuf::from("/"))
.with_clearenv()
.with_env("MSG", "hello from seabox")
.with_timeout(Duration::from_secs(5));
// ── 执行并检查结果 ──
let (output, reason) = sandbox.execute(&spec)?;
println!("exit_code: {}", output.exit_code);
match reason {
ExitReason::Ok => println!("命令成功执行"),
ExitReason::Denied { mechanism, message } => eprintln!("沙箱拒绝 ({mechanism:?}): {message}"),
ExitReason::Program(code) => eprintln!("命令以非零退出码 {code} 退出"),
ExitReason::InternalError(msg) => eprintln!("内部错误: {msg}"),
}更多示例见 examples/:
cli_basic.rs:基础用法(Landlock 规则 + 执行 + 结果处理)cli_from_toml.rs:TOML 配置 →SandboxConfig→ 执行crate_api.rs:crate API 用法
测试按"能力维度"分文件,共 171 项(全平台 21 + 仅 Linux 127 + 仅 macOS 23):
| 文件 | 验证内容 | 平台 |
|---|---|---|
tests/config_test.rs |
SandboxConfig 的 TOML / Builder / 展开行为 |
Linux |
tests/landlock_test.rs |
Landlock 实际 ACL 行为(需要 5.13+) | Linux |
tests/seccomp_test.rs |
--seccomp-deny-nr 逐 syscall 拦截 + 拒绝诊断 |
Linux |
tests/namespace_test.rs |
7 种 namespace 隔离 + uid/gid/hostname + chdir + env | Linux |
tests/mount_test.rs |
--unshare-mnt / --bind / --ro-bind / --tmpfs |
Linux |
tests/network_test.rs |
netns 隔离 + --allow-network loopback 放通 |
Linux |
tests/capability_test.rs |
--cap-add / --cap-drop / --uid 0 的 CapEff 实测 |
Linux |
tests/spawn_test.rs |
Sandbox::spawn + business-pid 回传 |
Linux |
tests/profile_test.rs |
@file argfile 预处理(profile) |
Linux |
tests/leak_test.rs |
fd / 内存泄漏回归 | Linux |
tests/concurrent_fork_test.rs |
fork 后子进程零堆操作(多线程安全) | Linux |
tests/deny_detect_test.rs |
classify_exit() 退出原因分类 |
Linux |
tests/macos_config_test.rs |
12 个 --mac-* 解析/映射 + 双向硬错误 |
全平台 |
tests/macos_test.rs |
Seatbelt 预设语义 + 凭据拒绝 + fd 逃逸(库 API) | macOS |
tests/macos_cli_e2e_test.rs |
CLI 端到端:退出码 / 参数切分 / 管道 / fd 逃逸 | macOS |
tests/cli_meta_test.rs |
--version / --help / 无子命令行为 |
全平台 |
cargo test # 全部
cargo test --test namespace_test # 指定文件
cargo test -- --nocapture # 显示 stdout内核能力(Landlock / seccomp / namespace)或 /usr/bin/sandbox-exec 缺失时,
测试自动跳过并打印 skipping test,不是 fail。
| Phase | 内容 | 状态 |
|---|---|---|
| 1 | Core + Linux 文件系统隔离(Landlock + seccomp + CLI) | ✅ 已完成 |
| 2 | Linux 完整进程隔离(7 种 namespace + mount ns + 动态 seccomp + 网络隔离) | ✅ 已完成 |
| 2b | IP 级网络过滤(eBPF / nftables) | ⏸️ 已搁置(待 Phase 4 评估) |
| 3 | macOS 支持(Seatbelt) | ✅ 已完成 |
| 4 | CodeWhale 集成 + HTTP API | 💡 计划中(HTTP API 经调研后暂缓,倾向本地 helper / Crate API 接入) |
详见 docs/development-phases.md。
serve子命令未实现:HTTP API 方向经调研后暂缓——HTTP sandbox API 是"远程 microVM/容器沙箱"市场的标配,与本地 OS 沙箱的定位不符。- macOS 后端在纵深防御上弱于 Linux:Seatbelt 无 syscall 过滤、无 namespace、无 capability,
且读侧默认零隔离(
MacReadMode::All)。文件系统写隔离与凭据保护两边都完整。 - macOS 的网络策略不支持域名与具体 IP:SBPL 语法层只接受
*与localhost, 端口可以精确到号。按域名放行需旁路方案(本地代理),本轮不实现。 - macOS 上 CLI 的拒绝归因只能看退出码:CLI 走流式
spawn(Inherit)拿不到 stderr, 被 Seatbelt 拒绝的命令会显示为普通非零退出。用库 APISandbox::execute()才能拿到 结构化的Denied { mechanism: Seatbelt, .. }。这是已知能力降级。 - 无高层策略预设(Linux):Linux 侧仍需用户逐条指定
--landlock规则;macOS 侧已有三种预设。 - 网络仅 namespace 级隔离:只有全断 / loopback 二态,无 IP 级过滤(Phase 2b 搁置)。
- seccomp 无默认黑名单:需显式用
--seccomp-deny-nr指定要拦截的 syscall。
MIT — 详见 LICENSE。