Skip to content
zsdfbbPublic

About

A rust lightweight sandboxing tool for enforcing filesystem and network restrictions on arbitrary processes at the OS level, without requiring a container.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

seabox

一个为 AI Agent 设计的跨平台 OS 级沙箱:在操作系统层面强制施加文件系统与进程限制,无需容器运行时。当前支持 Linux(Landlock + seccomp + namespaces)与 macOS(Seatbelt)。

设计目标:让 Agent 的命令执行默认受限,用可声明的策略建立安全边界。

  • 跨平台:平台无关的 Sandbox trait,各平台用原生机制实现(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 命令、修改文件、运行构建。传统权限弹窗有两个根本缺陷:

  1. 疲劳决策:用户面对连续弹窗,最终形成"看见就点允许"的肌肉记忆
  2. 粒度过粗:要么全允许、要么全拒绝,没有中间态

内核级沙箱用策略驱动解决:用户事先声明文件系统权限(--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 Sandbox crate 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。

macOS 快速上手

# 三种预设
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 是递归的,含整棵子树与删除权。

macOS 的硬限制(任何 profile 都放不通)

限制 细节
setuid 程序不可用 ps top su atq crontab newgrp 全部被拒
SIP 路径不可写 /etc /System 等仍受系统保护

seabox check 会单独列出这两条。也正因如此,「不配预设」实现为不进入 sandbox-exec, 以免白白损失 ps 这类排障工具的能力。

安装

从 crates.io

cargo install seabox

当前版本:0.1.0(首个支持 macOS 的版本,含 breaking 变更, 升级前请读 CHANGELOG.md)。

预编译二进制(musl 静态)

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。

快速开始

Linux

# 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 -la

macOS

seabox 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

CLI 使用

子命令

子命令 说明
run 在沙箱中执行命令
check 打印当前系统可用的沙箱能力(Linux:Landlock / seccomp / namespace;macOS:Seatbelt)
serve HTTP API 服务器(尚未实现,该方向已暂缓,见路线图)

run 选项

文件系统

选项 说明
--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, .. } 请用库 API Sandbox::execute()。

⚠️ 沙箱不解释 shell 元字符

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"

Crate API 使用

作为库嵌入时,通过 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 拒绝的命令会显示为普通非零退出。用库 API Sandbox::execute() 才能拿到 结构化的 Denied { mechanism: Seatbelt, .. }。这是已知能力降级。
  • 无高层策略预设(Linux):Linux 侧仍需用户逐条指定 --landlock 规则;macOS 侧已有三种预设。
  • 网络仅 namespace 级隔离:只有全断 / loopback 二态,无 IP 级过滤(Phase 2b 搁置)。
  • seccomp 无默认黑名单:需显式用 --seccomp-deny-nr 指定要拦截的 syscall。

许可

MIT — 详见 LICENSE。

About

A rust lightweight sandboxing tool for enforcing filesystem and network restrictions on arbitrary processes at the OS level, without requiring a container.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages