Show HN:Corral —— 杀掉你的 AI agent 启动的所有命令
摘要
Corral 是一个 Linux 命令行工具,给命令设定时间限制,并保证其衍生的任何进程都不会存活下来。它使用 cgroup v2(`cgroup.kill`),或退而求其次采用 subreaper + 扫描 `/proc` + pidfds 的方案。它面向的是那些会泄漏守护进程、僵死管道以及无视 SIGTERM 的 AI 编码 agent 和 CI 任务。
查看缓存全文
缓存时间: 2026/09/30 20:04
Cardinal44/corral
来源:https://github.com/Cardinal44/corral
corral
corral 用于在时间限制下运行命令。当 corral 返回时,该命令启动的所有进程都已不复存在。corral 会在返回前验证这一点。如果无法证明这一点,corral 将以退出码 120 退出。
AI 编码代理和 CI 任务运行的往往是它们并未编写的 shell 命令。典型的运行器会向其子进程或子进程组发送信号,然后等待输出管道关闭。这种模型在以下三种常见场景中会失效:
- 守护进程 fork 两次并调用
setsid()。此时它处于不同的进程组和会话中,其父进程是 init,因此信号无法送达。 - 后台进程持续持有 stdout 或 stderr。运行器永远不会读到文件结束符,因此在命令退出后它会挂起。
- 某进程忽略 SIGTERM 并继续运行。
残留进程会占用端口、文件锁和 CPU,可能导致下一次运行失败。
corral 控制的是完整的进程树,而不仅仅是其直接子进程。在强制模式下,命令运行在自己的 cgroup v2 组中,向 cgroup.kill 写入一次即可停止该组中的所有进程。在没有 cgroup 的情况下,corral 作为子进程的 subreaper 运行,孤立进程会成为它的子进程。corral 通过 /proc 找到剩余的进程,并通过 pidfd 向它们发送信号。在这两种模式下,运行都在命令退出时结束,而不是在管道关闭时结束。
corral 不是安全沙箱。它不限制文件访问、网络访问或权限。
安装
corral 需要 Linux 5.11 或更高版本,强制模式需要 5.14 或更高版本。
预编译二进制文件适用于带 glibc 2.36 或更高版本的 x86-64 系统(例如 Ubuntu 22.10、Debian 12、Fedora 37 或更新的发行版):
curl -LO https://github.com/Cardinal44/corral/releases/latest/download/corral-linux-x86_64.tar.gz
tar -xzf corral-linux-x86_64.tar.gz
sudo cp corral-linux-x86_64/corral corral-linux-x86_64/corral-enforced /usr/local/bin/
从源码构建需要 glibc 2.36、CMake 3.22、Ninja 以及 GCC 11 或 Clang 14。测试需要 Python 3.10。
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure
使用
corral --wall 30s -- ./build.sh
corral --wall 5m --max-output 1M --json run.json -- make test
corral-enforced --wall 30s --mem 512M --pids 64 -- ./build.sh
corral 有两种模式:
- 强制模式将命令放入自己的 cgroup v2 组中。内核会将所有后代进程保留在该组内,向
cgroup.kill写入一次即可停止所有进程。此模式还会应用--mem和--pids。它需要一个已委托的 cgroup。scripts/corral-enforced使用systemd-run启动一个。 - 回退模式不需要 cgroup。corral 通过父进程、进程组和会话找到进程,并扫描
/proc直到没有残留进程。
默认的 --cgroup-mode=auto 在条件允许时使用强制模式。
| 选项 | 默认值 | 含义 |
|---|---|---|
--wall DURATION | 无 | 运行的时间限制。 |
--grace DURATION | 2s | SIGTERM 与 SIGKILL 之间的间隔时间。 |
--verify-timeout DURATION | 2s | 最终检查的时间限制。 |
--mem SIZE | 无 | 内存限制(强制模式)。 |
--pids COUNT | 无 | 进程数量限制(强制模式)。 |
--max-output SIZE | 无 | stdout 和 stderr 合计的输出限制。 |
--cgroup-mode MODE | auto | auto、enforced 或 fallback。 |
--cgroup-parent PATH | 无 | 用于 corral 创建的组的已委托 cgroup。 |
--stdin POLICY | null | null 或 inherit。 |
--json PATH | 无 | 将审计记录写入 PATH。 |
--quiet | 关闭 | 不打印摘要行。 |
DURATION 是带有 ms、s、m 或 h 后缀的数字。SIZE 是以字节为单位的数字,可带有可选的 K、M 或 G 后缀。
使用 --json 时,corral 为每次运行写入一条 JSON 记录。该记录包含模式、限制条件以及运行结束的原因,还包含每个步骤的时间以及任何未停止进程的 pid。
退出码
| 退出码 | 含义 |
|---|---|
| 命令的退出码 | 命令正常退出。 |
| 128 + N | 信号 N 终止了命令,或 corral 收到了信号 N(Ctrl-C 为 130)。 |
| 124 | 时间限制到期。 |
| 121 | 达到内存限制。 |
| 122 | 达到输出限制。 |
| 126、127 | corral 无法启动命令(127:未找到)。 |
| 125 | 设置失败或某个选项不正确。命令未启动。 |
| 120 | corral 无法证明所有进程都已停止。此退出码优先于其他所有退出码。 |
工作原理
- 命令在新会话中启动,因此发送给其进程组的信号永远不会到达 corral。
- corral 是子进程的 subreaper。孤立进程会成为 corral 的子进程,因此 corral 可以停止它们。
- corral 只有在再次确认进程后,才通过 pidfd 向不是其子进程的进程发送信号。被复用的 pid 永远不会收到信号。
- 运行在命令退出时结束,而不是在其输出管道关闭时结束。
- 每次运行结束后,corral 会持续停止并回收进程,直到没有残留为止。在强制模式下,内核还必须报告该组为空,且对该组的
rmdir操作必须成功。
结果
bench/compare.py 使用四种运行器运行了 faults/ 中的十个测试程序:
- CE:强制模式下的 corral
- CF:回退模式下的 corral
- TO:
timeout -k 1s 2s - NV:一个只终止直接子进程的 Python 运行器
每次运行的时间限制为 2 秒。每个单元格显示 5 次运行中的退出状态和仍然存活的最大进程数。
| 测试程序 | CE | CF | TO | NV |
|---|---|---|---|---|
| F1 每 100 毫秒打印一行 | 124 / 0 | 124 / 0 | 124 / 0 | 137 / 0 |
| F2 等待 stdin | 0 / 0 | 0 / 0 | 124 / 0 | 137 / 0 |
| F3 启动后台作业 | 0 / 0 | 0 / 0 | 0 / 1 | 挂起 / 1 |
| F4 启动守护进程并退出 | 0 / 0 | 0 / 0 | 0 / 1 | 挂起 / 1 |
| F5 启动守护进程并保持运行 | 124 / 0 | 124 / 0 | 124 / 1 | 挂起 / 1 |
| F6 忽略 SIGTERM | 124 / 0 | 124 / 0 | 137 / 0 | 137 / 0 |
| F7 fork 200 个子进程 | 124 / 0 | 124 / 0 | 124 / 1 | 挂起 / 192 |
| F8 子进程持有 stdout | 0 / 0 | 0 / 0 | 0 / 1 | 挂起 / 1 |
| F9 使用 256 MiB | 121 / 0 | 124 / 0 | 124 / 0 | 137 / 0 |
| F10 在收到 SIGTERM 时清理 | 124 / 0 | 124 / 0 | 124 / 0 | 137 / 0 |
corral 在所有测试中都没有留下任何存活的进程。CE 还在 0.12 秒内按 64 MiB 限制终止了 F9。TO 在 5 次运行中有 1 次在 F7 中留下了存活进程。在这台机器上,timeout 来自 uutils coreutils 0.8.0,而非 GNU coreutils。
bench/bench.py 测量了以下耗时(P50 / P95):
| 测量项 | 强制模式 | 回退模式 |
|---|---|---|
| 停止 F5:从时间限制到验证完成 | 9 / 12 ms | 10 / 12 ms |
运行 /bin/true(直接调用:0.83 / 1.88 ms) | 11.33 / 19.47 ms | 6.64 / 10.01 ms |
测试机器:Hetzner 云服务器,2 个共享 vCPU(Intel Xeon,Skylake),Ubuntu 26.04,内核 7.0。完整数据和 scripts/demo.sh 的终端录制位于 bench/results/ 中。
局限
- corral 无法看到命令在其进程树之外启动的工作,例如通过
systemd-run、D-Bus 或at启动的进程。 - 在强制模式下,如果进程自己向 cgroupfs 写入,它就可以脱离该组。
- 在回退模式下,corral 无法向 setuid 子进程发送信号。此时它会以 120 退出。
- 如果使用 SIGKILL 停止 corral,只有直接子进程能确定被终止。
- corral 不支持 PTY,也没有 CPU 时间限制,且仅能在 Linux 上运行。
许可证
MIT。参见 LICENSE。
相似文章
Show HN: Claw Patrol,面向AI Agent的安全防火墙
Claw Patrol 是一款面向AI Agent的安全防火墙,位于Agent与生产环境之间,通过HCL规则解析流量并管控行为,可拦截破坏性SQL操作,或对kubectl delete pod等风险命令要求人工审批。
Show HN: Clawk – 为编程代理提供一次性Linux虚拟机,而非你的笔记本电脑
Clawk是一个开源工具,为Claude Code等编程代理提供一次性Linux虚拟机,使其能自由运行而不危及宿主机。它通过网络白名单和文件隔离来保护密钥和系统文件安全。
Show HN: Capn-hook —— 编码代理的持久记忆工具 —— 不再重复grep同一谜题
Capn-hook 是一个 CLI 工具,它为编码代理提供持久记忆,保存回答代码库问题的文件,使得代理不再跨会话重复探索同一谜题,将重复问题的 Token 使用量减少 77%。
Show HN: Offrun – 从一个工作区管理所有编程智能体
Offrun 是一款免费的 macOS 应用,可从单一工作区管理多个编程智能体 CLI,为每个智能体分配独立的 git worktree,支持基于智能体的 diff 同行评审、账号负载均衡、项目记忆文件以及本地语音听写——全部在本地运行,使用用户自己的智能体账号。
Show HN: 为你的Claude Code代理打造的警察部门
agent-pd 是一个开源的日志和监控工具,用于 Claude Code 代理,它记录所有工具和权限事件,并通过确定性检测器重放这些事件以捕获违规行为,而不会阻止任何操作。