DSec 的 aether 和 chronus:一条命令经过哪些组件
DeepSeek DSec 论文(§2、§3.3)说,sandbox 运行后的操作依次经过 apiserver、edge、aether、chronus。aether 是每个 sandbox 一个的代理,chronus 是一个 shell 会话。论文讲清了它们的分工,但具体协议和异常处理留给了实现。这篇记录 OpenDsec 的做法,不代表 DSec 内部也这样做。
请求链
一次 await sb.run_shell("pip install -e ."):
SDK
│ gRPC + TLS
▼
apiserver 鉴权 sandbox.operate,按 sandbox id 找到 edge
│ gRPC
▼
edge 暂停中就先恢复,刷新空闲超时,调用期间不做空闲回收
│ unix socket(容器)或 vsock(VM)
▼
aether sandbox 内的 PID 1,按会话 id 找到或启动 chronus
│ unix socket
▼
chronus 持有一个常驻 shell,执行命令,记录输出三段内部通信用同一组 gRPC 消息:对外的接口带 sandbox_id,edge 转发,aether 和 chronus 实现同一个 Runtime 服务。所以容器和 VM 的差别只在 edge 到 aether 这一段的传输方式上,上层不用管。这也是论文要的效果:同一套接口覆盖容器和 VM。
每一层为什么存在
apiserver 不保存状态。sandbox id 里编码了它创建时所在的 edge,不用查表。暂停的 microVM 换过节点后,edge 的状态里会列出迁入的 id,原节点也会回复新位置。
edge 是唯一和 sandbox 通信的控制面组件。透明恢复放在这里,因为只有它知道 sandbox 当前的状态。当前节点放不下时,由 apiserver 把暂停的 microVM 迁到别的节点。
aether 还负责存活检测。edge 和它保持一条 Watch 流,流断了就算 sandbox 丢失,这一点和论文相同。会话关闭时,aether 杀掉 chronus 的整棵进程树。
chronus 让 cd、export 的状态在命令之间保留。每个会话一个进程,同一会话里的命令串行执行,要并发就开多个会话。
论文没细说、实现时要决定的事
命令比连接活得久。 输出写进文件,调用方断开后可以按偏移量继续取(GetOutput),也可以流式接收。每条命令有请求 id,SDK 重试时能找到已经在跑的命令,不会执行第二遍。这对 pip install 这种有副作用的命令很重要。
输出不能无限记。 论文记录过智能体无限输出写满存储。命令的输出先写进命名管道,由 chronus 读出来记录,每条命令每路最多记 64 MB,超出的部分读出后丢弃,命令不会因此阻塞。每个会话最多保留 512 MB,超出就删最早的记录。
后台进程不能漏掉。 chronus 是子进程收割者(subreaper),命令里放到后台的进程也留在会话的进程树里。杀进程树时要先反复 SIGSTOP,直到再也找不到新进程,再统一发信号;只扫描一次会漏掉扫描之后 fork 出来的进程。
不是所有调用都适合走 shell。 文件操作由 chronus 直接执行,不经过 shell。要按 pid 寻址、写 stdin、用 PTY 的独立进程,由 aether 管理,不经过会话,E2B 网关就用这个接口。
智能体会攻击这条链
论文记录了智能体直接向 chronus 的 socket 发伪造请求,以及读 chronus 的日志找答案。DSec 用 AppArmor 限制。OpenDsec 没有用 AppArmor,因为它挡不住 microVM guest 里的 root,改成让 agent 会话用普通用户:
| 攻击 | OpenDsec 的处理 |
|---|---|
| 向其他会话的 chronus socket 发请求 | socket 权限 0600,普通用户连不上 |
| 读其他会话的命令记录 | 记录目录属 root,输出文件只有 chronus 能读写 |
覆盖 /bin/bash | 普通用户改不了系统文件 |
| 在记录目录放符号链接,让 chronus 以 root 写别的文件 | 目录一直属 root,普通用户不能往里放文件(2026-10-27 修复) |
| 从 microVM guest 经 vsock 连 aether | guest 内核没有开 vsock loopback,连不上 |
这些都有一个前提:调用方要给 agent 指定普通用户(run_shell(..., user=...))。agent 跑在 root 会话里时,这张表前三行都不成立。
和论文不同的地方
- FnCall:论文的 FnCall 不用 aether 和 chronus,直接在预建容器里执行。OpenDsec 的 fncall 是预建池里 aether 已经就绪的容器,走同一条链。
- PID 1:在 OpenDsec 里 aether 通常是 sandbox 的 PID 1。论文没有说明。
- 容器位置:论文的容器跑在 VM 里,OpenDsec 的容器直接跑在宿主机上,所以 edge 到 aether 是 unix socket。
相关阅读
OpenDsec 与 DeepSeek 没有关联。文中关于 DSec 的内容均来自上述论文。