Cerberus 配置指南
cerberus 的策略文件本质上是 cerberus-core::policy::Policy 的 TOML 表达。本文档只描述当前代码里已经实现并可被解析的字段,不扩展任何“未来可能支持”的配置。
配置方式
Cerberus 的策略来源按以下优先级解析:
--policy-file <PATH>:显式指定文件,优先级最高config/cerberus-policies/<name>.toml:按 profile 名称向上查找项目配置目录- 内置 profile:
workspace-write-network-on、workspace-write-network-off、workspace-write-network-on-dev-env
这意味着:
- 如果你传了
--policy-file,就不会再看--profile - 如果
config/cerberus-policies/workspace-write-network-off.toml存在,它会覆盖同名内置 profile - 如果文件存在但解析失败,Cerberus 会直接报错,不会悄悄退回内置 profile
当前仓库还额外签入了三份 repo-root 作用域的 file-backed profile:
repo-root-write-network-offrepo-root-write-network-onrepo-root-write-network-on-dev-env
它们不是内置 profile,而是 config/cerberus-policies/ 下的普通 TOML 文件,因此:
profile list会把它们显示为[file: ...]- 它们的行为完全由文件字面内容决定
- 它们的名字带
repo-root,是为了明确表示这些规则写死了当前仓库根路径,不是通用的 workspace 预设
其中 repo-root-write-network-off 也比内置 workspace-write-network-off 更宽:它同样禁网,但保留了 repo-root 的 readwrite,以及 /dev、/proc、/mnt/wsl 的访问。
CLI 默认行为(当前实现)
cerberus exec在未显式指定--profile时,默认使用workspace-write-network-on-dev-env- 对内置 profile(
workspace-write-network-on、workspace-write-network-off、workspace-write-network-on-dev-env),CLI 会在exec和profile show时自动把当前工作目录追加为一条readwrite的custom_paths - 这种自动注入只作用于内置 profile 的 CLI 体验,不会改写
--policy-file指向的文件,也不会改写config/cerberus-policies/*.toml里的静态内容 - 旧名字
minimal、strict、llm-safe仍然可用,但文档与profile list默认展示新的显式名称
最小可用示例
下面这个示例描述的是纯策略文件本身。如果你直接写 TOML 文件,业务目录仍然要自己通过 [[custom_paths]] 显式声明;只有 CLI 使用内置 profile 执行 exec 或查看 profile show 时,才会自动注入当前 workspace。
landlock_optional = false
mount_isolation_fallback = false
[path_groups]
system_binaries = true
system_libraries = true
temp_directories = true
device_files = false
proc_filesystem = false
wsl_paths = false
[namespaces]
mount = true
pid = true
network = true
user = true
[resources]
timeout_secs = 30
max_memory_bytes = 268435456
# max_processes 省略表示不限数量
[environment]
whitelist = ["PATH", "LANG", "HOME"]
[[custom_paths]]
path = "/workspace"
permission = "readwrite"
顶层字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
landlock_optional |
bool |
false |
Landlock 不可用时,是否允许继续执行 |
mount_isolation_fallback |
bool |
false |
Landlock 不可用时,是否允许退化到 mount 隔离 |
seccomp_optional |
bool |
false |
seccomp 不可用时(如模拟/跨架构容器),是否把失败降级为告警而继续执行,而不是中止 |
file_access_audit |
bool |
false |
是否为本次执行采集文件访问事件(openat 等)。需启用 ebpf feature 且运行在 Linux 上;事件随 ExecResult.file_accesses 返回 |
network_audit |
bool |
false |
是否为本次执行采集网络连接事件。需启用 ebpf feature 且运行在 Linux 上;独立于 network_policy:配了策略则记录带裁决,否则为纯观测。事件随 ExecResult.network_accesses 返回 |
path_groups |
table | 全部为 false |
预定义文件系统访问组 |
custom_paths |
array |
空数组 | 自定义文件系统路径规则 |
namespaces |
table | 全部为 true |
Linux namespace 隔离开关 |
resources |
table | timeout_secs=30、max_memory_bytes=268435456、max_processes 省略 |
资源限制 |
environment |
table | 见下文 | 允许透传到子进程的环境变量白名单 |
network_policy |
table | 不启用 | 细粒度网络规则对象。默认构建下会先做校验并在缺少 backend 时 fail-closed;启用 ebpf feature 时会接入真实监控/拦截路径 |
path_groups:预定义路径组
[path_groups]
system_binaries = true
system_libraries = true
temp_directories = true
device_files = false
proc_filesystem = false
wsl_paths = false
| 字段 | true 时允许的访问 |
实际路径 |
|---|---|---|
system_binaries |
读 + 执行 | /usr/bin |
system_libraries |
读 / 读+执行 | /usr/lib、/usr/lib64、/lib、/lib64、/usr/share、/etc/ld.so.cache、/etc/localtime、/etc/locale.alias |
temp_directories |
读 + 写 | /tmp |
device_files |
读 + 写 | /dev |
proc_filesystem |
只读 | /proc |
wsl_paths |
只读 | /mnt/wsl |
什么时候用它
- 想快速打开一类常见系统路径时,用
path_groups - 想补充业务路径时,再配合
[[custom_paths]]
重要提醒
当前实现没有“打开整个 /etc”的 path_groups 开关。像 DNS 解析、/etc/hosts、TLS trust store 这种需求,应该通过 [[custom_paths]] 显式列出真正需要的只读路径。
custom_paths:自定义路径规则
[[custom_paths]]
path = "/workspace"
permission = "readwrite"
[[custom_paths]]
path = "/usr/local/bin/my-tool"
permission = "readexecute"
[[custom_paths]]
path = "/workspace/generated-tool"
permission = "readwriteexecute"
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
path |
string |
是 | 规则对应的绝对路径;在 file-backed policy 中也可以写相对路径 |
permission |
string |
是 | readonly、readwrite、readexecute、readwriteexecute |
| 权限值 | 含义 |
|---|---|
readonly |
可读,不可写,不可执行 |
readwrite |
可读可写,可创建/替换/删除,不可执行 |
readexecute |
可读可执行,不可写 |
readwriteexecute |
可读可写可执行,也可创建/替换/删除 |
建议
- 优先写绝对路径
- 只给真正需要的目录,不要直接开放过大的父目录
- 如果只是想让命令可执行,通常应该用
readexecute而不是readwrite readwriteexecute只应用在确实需要“生成后立刻执行”这类路径上,不要把它当成默认值- 联网命令如果需要 resolver / hosts / CA bundle,应显式补充这些只读路径,而不是依赖某个隐式的“network”文件系统组
file-backed policy 的相对路径规则
对 file-backed policy(包括 --policy-file 和 config/cerberus-policies/*.toml),custom_paths.path 里的相对路径会在 CLI 解析期 按启动 cerberus 时的当前工作目录展开。
例如,如果你在 /work/project 下运行:
cerberus --policy-file ./policy.toml exec -- ...
那么:
path = "."→/work/projectpath = "./plugins"→/work/project/pluginspath = "plugins"→/work/project/pluginspath = "../shared"→/work/shared
这里要特别区分两层语义:
- 这是 file-backed policy 的当前目录相对路径语义
- 它不等于 built-in profile 的自动 workspace 注入
cerberus-core仍然只看到展开后的绝对路径
CLI 对内置 profile 的自动 workspace 注入
当前 CLI 会对内置 profile(workspace-write-network-on、workspace-write-network-off、workspace-write-network-on-dev-env)自动追加一条以当前工作目录为目标的 readwrite 规则,用来支持常见的工作区读取与写入操作,例如在仓库根目录执行 ls、读取源码、写入构建产物等。
这里要特别区分两层语义:
- 这是 CLI 对内置 profile 的运行时补充,不是 TOML 文件里的隐式默认值
- 注入的是
readwrite,不是readexecute,所以它不等价于允许直接执行 workspace 里的./script或本地二进制
namespaces:隔离语义
[namespaces]
mount = true
pid = true
network = true
user = true
| 字段 | true 的含义 |
false 的含义 |
|---|---|---|
mount |
启用挂载隔离 | 与宿主共享挂载视图 |
pid |
启用 PID 隔离 | 可见宿主进程视图 |
network |
允许网络访问,不创建隔离网络视图 | 阻止网络访问,运行时会走禁网隔离路径 |
user |
启用 user namespace | 使用真实 UID/GID |
namespaces.network 的公开语义
这里的 network 现在就是字面意思,表示“是否允许网络访问”:
network = true:允许联网network = false:禁止联网
CLI 里的 profile show 也按这个公开语义展示,例如 workspace-write-network-off 会显示 network: false (network blocked),workspace-write-network-on-dev-env 会显示 network: true (network allowed)。
与 user 的关系
当前仓库里的允许联网预设,workspace-write-network-on 与 workspace-write-network-on-dev-env 都使用 user = false。这不是 network = true 的语义前提,只是当前内置 profile 的已验证组合。如果你自定义策略,可以按需求单独设置 user,但要确认目标运行环境真的支持你要求的 namespace 组合。
fail-closed 行为
在 Linux 上,pid、user、network 这些被代码视为关键 namespace:
- 如果策略要求它们开启,但当前运行环境不支持,Cerberus 会直接拒绝执行
mount缺失则可能进入degraded,不一定立刻失败
resources:资源限制
[resources]
timeout_secs = 60
max_memory_bytes = 536870912
max_processes = 100
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
timeout_secs |
u64 |
30 |
最大执行时长,超时后进程会被终止 |
max_memory_bytes |
u64 / 省略 |
268435456 |
地址空间上限,单位字节 |
max_processes |
u32 / 省略 |
省略 | 最大进程数,用于限制 fork bomb |
不限资源的写法
当前实现已经修复 0 的歧义:
- 最推荐的写法仍然是省略字段,例如省略
max_processes - 如果旧文件里写了
0,解析时会被规范化为“不限”,不会再把0直接当成运行时硬限制传给setrlimit
换句话说,max_memory_bytes 和 max_processes 要表达“不限”,优先用“省略字段”这个写法。
environment:环境变量白名单
[environment]
whitelist = ["PATH", "LANG", "HOME", "USER"]
当前默认白名单来自 EnvironmentConfig::default():
| 默认透传变量 |
|---|
PATH |
LANG |
HOME |
USER |
HTTP_PROXY |
HTTPS_PROXY |
http_proxy |
https_proxy |
ALL_PROXY |
all_proxy |
NO_PROXY |
no_proxy |
行为说明
- 只有在白名单里的变量才会传给沙箱子进程
- 如果你把白名单收得过窄,像
HOME、TERM、SHELL这类工具常用变量会消失 workspace-write-network-off/workspace-write-network-on/workspace-write-network-on-dev-env三个内置 profile 都会覆盖这里的默认白名单
network_policy:网络规则结构
[network_policy]
enabled = true
mode = "monitor"
default_action = "deny"
[[network_policy.rules]]
action = "allow"
direction = "outbound"
protocol = "tcp"
hosts = ["example.com"]
cidrs = ["192.168.1.0/24"]
ports = [[80, 80], [443, 443]]
顶层字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
bool |
true |
是否启用该网络策略对象 |
mode |
"monitor" | "enforce" |
monitor |
监控还是强制执行 |
default_action |
"allow" | "deny" |
deny |
没有命中规则时的默认动作 |
rules |
array |
空数组 | 具体规则列表 |
单条规则字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
action |
"allow" | "deny" |
是 | 命中规则后的动作 |
direction |
"inbound" | "outbound" |
否 | 方向,不写表示都匹配 |
protocol |
"tcp" | "udp" |
否 | 协议,不写表示都匹配 |
hosts |
string[] |
否 | 域名列表 |
cidrs |
string[] |
否 | CIDR 网段列表 |
ports |
[[u16, u16]] |
否 | 端口范围,闭区间 |
校验规则
- 端口不能是
0 - 端口范围必须满足
start <= end cidrs必须带/,前缀长度必须在0..=32direction只能是inbound或outboundprotocol只能是tcp或udp- 空规则是合法的,含义接近“匹配全部”
当前代码里的实际地位
从当前仓库代码路径看,network_policy 目前已经具备:
- TOML 解析
- 字段校验
profile show输出展示- 与
namespaces.network的冲突校验 - 运行前能力门禁
ebpffeature 下的 matcher / enforcer 初始化与事件处理路径
但它的真实运行状态要分开理解:
-
总开关仍然是
namespaces.networknetwork = false时,策略已经是禁网态- 这时如果还配置
network_policy,解析会直接报错,因为“先禁网,再配细粒度联网规则”本身就是自相矛盾
-
默认构建与
ebpf构建的行为不同- 只要
network_policy.enabled = true,Cerberus 就会在执行前检查 eBPF backend 是否可用 - 只要
network_policy.enabled = true,运行时也会强制走带 sandbox/runtime gating 的执行路径,不会因为策略里没开其他 namespace / 资源限制就退回 direct execution - 默认构建 / backend 不可用:
monitor和enforce都会 fail-closed,拒绝执行;mode = "enforce"的报错会更明确,直接说明缺少 eBPF enforcement backend - 启用
ebpffeature 的构建:存在真实的 matcher / enforcer / audit backend 路径,monitor与enforce不再只是展示字段,而会进入实际网络事件处理流程
- 只要
-
enabled = false时只是保留配置对象- 这种情况下对象会被解析和展示
- 但不会进入真正的运行时拦截路径
所以,今天应把 network_policy 理解成一条带 feature gate 的真实能力链路:
- 没开
ebpf时,它体现为严格的 fail-closed 门禁 - 开了
ebpf时,它才进入真实的 host / CIDR / port 匹配与 monitor / enforce 流程
如果你在默认构建里看到 network_policy 被拒绝,不是“文档保守”,而是当前 build 本身就没有打开那条 backend。
网络访问审计输出
启用 ebpf feature 并把 network_audit = true 时,Cerberus 会在执行期间收集每个网络连接事件,随 ExecResult.network_accesses 返回(与 file_access_audit 的 ExecResult.file_accesses 是两条独立的 list)。network_audit 是独立开关,与 network_policy 解耦:
- 配了
network_policy:记录里带上策略裁决结果(allowed/denied_by_policy/monitored)。monitor模式下连接照常放行但仍被记录,因此mode = "monitor"+network_audit = true就是“带裁决的纯审计”。 - 没配
network_policy:记录的是内核观察到的原始结果,纯观测、不做任何 enforcement。
单条记录(NetworkAccessRecord)形如:
{
"direction": "outbound",
"protocol": "tcp",
"address": "8.8.8.8",
"port": 443,
"result": "allowed",
"pid": 41234,
"timestamp_nanos": 1717000000000000000
}
direction:outbound/inboundprotocol:tcp/udp/other(<n>)result:allowed/denied_by_policy/monitored(配了network_policy时是策略 enforcement 的裁决;未配时是内核观察到的原始结果)
CLI 输出通道与 file access 对称,由环境变量 CERBERUS_NETWORK_AUDIT_PATH 决定:
- 已设置(推荐):每次调用以一行 JSONL 追加到该 sidecar 文件,stderr 只留一行摘要。
- 未设置:完整审计以 JSON 打印到 stderr,仅适合交互式调试。
file_access_audit与network_audit是两个互相独立的开关,可任意组合开启,各自产出一条 list / 一个 sidecar 文件;网络审计是否带策略裁决,取决于是否另外配了network_policy。
file_access_audit:文件访问审计
file_access_audit = true 时,Cerberus 在每次执行期间通过 eBPF 采集子进程的文件访问事件(openat 系列系统调用),并把记录通过 ExecResult.file_accesses 返回;CLI 还会把它落地成审计输出。该能力与 network_policy 一样是 feature gate 的:只有用 ebpf feature 构建、且运行在 Linux 上才会真正采集,默认构建下配置 file_access_audit = true 会直接报错(fail-closed)。
构建要求
eBPF 相关 crate 在上游 workspace 中默认是注释停泊的,构建前需取消注释 Cargo.toml 里的三个成员(缺一不可,cerberus-core 的 build.rs 会用 cargo build --package audit-bpf --features eBPF 编译 BPF 字节码,要求它在 workspace 内):
[workspace]
members = [
# ...
"crates/cerberus/cerberus-core",
"crates/cerberus/cerberus-cli",
"crates/cerberus/cerberus-core/bpf",
]
然后用 ebpf feature 构建(需要 eBPF 工具链:nightly + bpf-linker):
cargo build -p cerberus-cli --features ebpf
启用与运行
在策略 TOML 里打开开关(没有对应的命令行参数,只能通过策略文件启用):
# minimal-audit.toml
landlock_optional = true
file_access_audit = true
[path_groups]
system_binaries = true
system_libraries = true
[namespaces]
mount = true
pid = true
network = false
user = true
[[custom_paths]]
path = "."
permission = "readwrite"
cerberus --policy-file minimal-audit.toml exec -- cat /etc/hostname
输出通道
单条记录的结构(FileAccessRecord):
{
"path": "/etc/hostname",
"operation": "read",
"result": "allowed",
"pid": 41234,
"timestamp_nanos": 1717000000000000000
}
operation:read/write/executeresult:allowed/denied_by_landlock/denied_by_path_not_found
输出走哪条通道取决于环境变量 CERBERUS_FILE_ACCESS_AUDIT_PATH:
- 未设置:把完整审计以 pretty JSON 数组打印到 stderr(便于交互式查看)。
- 已设置:把本次调用写成一行 JSONL追加到该 sidecar 文件,stderr 只留一行摘要。这样当上层 agent 捕获子命令 stderr 回喂模型时,广命令(如
find /触及成千上万文件)不会撑爆上下文。每行形如:
{"command": "cat /etc/hostname", "events": [{"path": "/etc/hostname", "operation": "read", "result": "allowed", "pid": 41234, "timestamp_nanos": 1717000000000000000}]}
CERBERUS_FILE_ACCESS_AUDIT_PATH=/tmp/file_access.jsonl \
cerberus --policy-file minimal-audit.toml exec -- cat /etc/hostname
# stderr: [cerberus] file_access_audit: 1 event(s) -> /tmp/file_access.jsonl
写 sidecar 文件失败时只告警、不影响命令结果(sandbox 执行结果才是权威)。
内置 profile 对照
| Profile | 适用场景 | 超时 | 内存 | 进程数 | 联网 | fallback |
|---|---|---|---|---|---|---|
workspace-write-network-on |
本地可信命令,工作区可写、允许联网、偏开发友好 | 120s | 1 GiB | 不限 | 允许 | 仅放宽 Landlock 缺失和 mount 降级路径,不能绕过 namespace 或 network_policy 门禁 |
workspace-write-network-off |
工作区可写、禁止联网、强调 fail-closed | 30s | 256 MiB | 50 | 禁止 | 全部关闭 |
workspace-write-network-on-dev-env |
工作区可写、允许联网、保留开发环境变量 | 60s | 512 MiB | 100 | 允许 | 全部关闭 |
CLI 对内置 profile 的额外运行时语义
cerberus exec默认使用workspace-write-network-on-dev-env- 对内置 profile 执行
exec或profile show时,CLI 会额外注入当前 workspace 的readwrite访问 - 这意味着
workspace-write-network-off在 CLI 的“有效执行策略”里,仍然会保留它原本的禁网、收紧环境变量、强 namespace/fail-closed 倾向,但不会再默认拒绝当前 workspace 的读写 - 如果你需要一个完全由文件决定、没有 CLI 自动 workspace 注入的策略边界,请使用
--policy-file或显式的config/cerberus-policies/*.toml
内置 profile 的环境变量差异
workspace-write-network-on:PATH、LANG、HOME、USER、TERM、SHELL、PWDworkspace-write-network-off:PATH、LANG、TERMworkspace-write-network-on-dev-env:在常规变量之外,还会加入LC_ALL、EDITOR、VISUAL、RUST_BACKTRACE、RUST_LOG、CARGO_HOME、RUSTUP_HOME、NODE_PATH、NPM_CONFIG、PYTHONPATH、GOPATH、GOBIN
常见陷阱
1. 继续按旧语义理解 network
错。当前公开语义已经改成“是否允许联网”。
true= 允许联网false= 禁止联网
2. 在配置里写不存在的字段
当前实现会直接报错,不会悄悄忽略。
3. 以为配置文件解析失败会自动回退
不会。文件存在但有误时,Cerberus 会直接报错。
4. 把 0 写成资源“不限”
当前实现下不安全。更稳妥的做法是直接省略该字段。
5. 把默认构建下的 fail-closed 门禁,误解成“network_policy 完全没实现”
也不对。当前实现是分 build 的:
- 默认构建下,启用
network_policy会因为缺少 eBPF backend 而 fail-closed - 启用
ebpffeature 后,存在真实的监控 / 强制执行路径
6. 把“内置 profile 会自动注入 workspace”理解成“所有策略都会自动注入 workspace”
也不对。当前自动注入只发生在 CLI 使用内置 profile 时。
cerberus --profile workspace-write-network-off exec -- ...:会注入当前 workspace 的readwritecerberus --policy-file ./my-policy.toml exec -- ...:不会注入,行为完全由文件内容决定
7. 把 workspace 的 readwrite 注入理解成“可以执行 workspace 里的文件”
也不对。readwrite 只表示可读可写,不包含执行权限。
- 想让工作区里的脚本或二进制可执行,仍然要显式给对应路径
readexecute - 内置 profile 的自动注入只解决“当前目录可读写”的问题,不覆盖“当前目录可执行”
自定义策略示例
1. 类似 workspace-write-network-off 的自定义策略
landlock_optional = false
mount_isolation_fallback = false
[path_groups]
system_binaries = true
system_libraries = true
temp_directories = true
device_files = true
proc_filesystem = true
wsl_paths = false
[namespaces]
mount = true
pid = true
network = false
user = true
[resources]
timeout_secs = 30
max_memory_bytes = 268435456
max_processes = 50
[environment]
whitelist = ["PATH", "LANG", "TERM"]
2. 类似 workspace-write-network-on-dev-env 的联网开发策略
landlock_optional = false
mount_isolation_fallback = false
[path_groups]
system_binaries = true
system_libraries = true
temp_directories = true
device_files = true
proc_filesystem = true
wsl_paths = true
[[custom_paths]]
path = "/etc/resolv.conf"
permission = "readonly"
[[custom_paths]]
path = "/etc/hosts"
permission = "readonly"
[[custom_paths]]
path = "/etc/nsswitch.conf"
permission = "readonly"
[[custom_paths]]
path = "/etc/ssl/certs"
permission = "readonly"
[namespaces]
mount = true
pid = true
network = true
user = false
[resources]
timeout_secs = 60
max_memory_bytes = 536870912
max_processes = 100
[environment]
whitelist = [
"PATH", "LANG", "LC_ALL", "HOME", "USER", "TERM", "SHELL", "PWD",
"EDITOR", "VISUAL", "RUST_BACKTRACE", "RUST_LOG", "CARGO_HOME", "RUSTUP_HOME",
"NODE_PATH", "NPM_CONFIG", "PYTHONPATH", "GOPATH", "GOBIN"
]
相关命令
# 查看可用 profile
cerberus profile list
# 查看某个 profile 的展开结果、运行时能力与 enforcement level
cerberus profile show workspace-write-network-off
# 使用显式配置文件执行
cerberus --policy-file ./config/cerberus-policies/my-policy.toml exec -- ls -la
# 查看已记录的执行历史
cerberus history
历史记录与数据库位置
cerberus CLI 会把执行历史写入本地 SQLite 数据库。
cerberus exec -- ...:执行完成后写入一条记录cerberus history:从同一个数据库读取记录
当前实现下,数据库路径不是策略文件配置项,而是 CLI 自己按平台默认 data directory 解析出来的:
<data-dir>/cerberus/cerberus.db
常见情况:
- Linux:
$XDG_DATA_HOME/cerberus/cerberus.db - Linux 未设置
XDG_DATA_HOME:~/.local/share/cerberus/cerberus.db - macOS:
~/Library/Application Support/cerberus/cerberus.db - Windows:
%LOCALAPPDATA%\cerberus\cerberus.db
它和 policy TOML 无关:path_groups、custom_paths、namespaces 等字段都不会改变这个数据库位置。
更完整的说明见下文档: