沙箱与凭据

节点在回答其运营人员不信任的对端发来的任务时,可以在沙箱内运行本地 Agent。本页说明沙箱限制什么、节点如何在不交出密钥的情况下让 Agent 访问模型、运营人员如何允许访问选定的主机,以及如何限制运行次数。

概述

任务的文本由请求方撰写。如果提供方将其交给编码 Agent,请求方就影响了一个可以读取文件、运行命令、使用网络并持有模型服务凭据的程序。无论文本写了什么,沙箱都会限制这类程序能做的事情。

沙箱适用于自动回复的 exec 后端(参见 委派与自动回复),并在两种情形下被选用于某一轮对话:

  • 对端不在 trust 名单中,且 auto_reply.untrusted 为 sandbox;

  • 对端在 trust 名单中,且 auto_reply.trusted 为 sandbox。

被选用沙箱的一轮对话绝不会在沙箱之外运行。沙箱不可用时,节点不会启动 Agent。openai 后端不使用沙箱,因为该后端不启动任何程序,但它针对未受信任对端的对话仍计入运行预算。

支持的平台

Linux

沙箱使用带 seccomp 过滤器的 bubblewrap,需要 bubblewrap 软件包。支持的主机为 Debian 12(bubblewrap 0.8)和装有下述 AppArmor profile 的 Ubuntu 24.04。在 Ubuntu 24.04 及更高版本上,默认的 AppArmor 策略会阻止 bubblewrap 创建用户命名空间,节点会报告 userns_blocked,并给出安装窄范围 profile 的命令。

macOS

沙箱通过 /usr/bin/sandbox-exec 使用 Seatbelt。每个回合开始前,节点会在 profile 内运行一次自检。自检失败的主机会把沙箱报告为不可用(失败项的原因),并以 sandbox_unavailable 拒绝需要沙箱的回合。节点从不在没有沙箱的情况下运行这样的回合。该沙箱自 0.3.1 版起可用,并已在 macOS 14 与 macOS 15 上验证;0.3.0 版不含此沙箱(platform_unsupported.pending)。profile 默认拒绝一切,只开放系统库与工具、运行目录、工作目录以及本次运行的出口端点。

其他平台

没有沙箱。在 Windows 上,请在 WSL2 中运行 ANet。

AppArmor profile

anet doctor --apparmor-profile 把 profile 打印到标准输出,不写任何文件。句子 userns_blocked 给出安装它的命令:

$ anet doctor --apparmor-profile | sudo tee /etc/apparmor.d/anet-bwrap >/dev/null \
    && sudo apparmor_parser -r /etc/apparmor.d/anet-bwrap

该 profile 分为两部分。profile anet-bwrap 附加在 /usr/bin/bwrap 上,授予 bubblewrap 创建用户命名空间的权限。bubblewrap 启动的一切,包括沙箱中的 agent,都在叠加的 profile anet-bwrap//&anet-bwrap-unpriv 下运行,后者不持有任何 capability。其结构与 Ubuntu 自 24.10 起提供的 profile bwrap-userns-restrict 相同,且不更改任何 sysctl 设置。可以用 apparmor_parser -R 并删除该文件来移除这个 profile。

沙箱的限制范围

在 Linux 上,节点在新的用户、进程、IPC、主机名、控制组和网络命名空间中启动每次运行。运行具有以下性质。

无网络

运行除自身的回环接口外没有其他网络接口,无法连接本机、本地网络或互联网。唯一的出口是节点为该次运行提供的本地端点(参见 模型访问 与 允许的主机)。

独立的文件系统

运行可以只读地看到系统目录。其主目录和临时目录为该次运行创建,运行结束时删除。其工作目录与未沙箱化的运行的工作目录分开,节点的数据目录、控制令牌、本地套接字目录和其他模块的套接字始终不可见。未沙箱化的运行在 <cache>/anet/work/<aid>/<interaction>/(或 auto_reply.work_dir)下工作,沙箱化的运行在 <cache>/anet/work-sandbox/<aid>/<interaction>/ 下工作。两个根目录从不重叠,这样不受信任的对端的运行所留下的任何东西,都不会出现在无沙箱的 agent 启动的目录里。工作目录在每个构建中都会在保存期内被删除。

无特权

所有 Linux capability 均被丢弃。seccomp 过滤器拒绝创建用户命名空间、挂载文件系统、跟踪其他进程、加载内核模块、使用 io_uring、bpf 或内核密钥存储的系统调用,并拒绝其他系统调用接口。节点结束时,运行随之结束。

干净的环境

环境从空集构建,包含区域设置、终端类型、精简的 PATH,以及指向节点端点的变量。其中没有运营人员的任何密钥,也没有守护进程的代理设置。

资源限制

用户级 systemd 可用时,一次运行被限制为 2 GiB 内存和 256 个任务,anet status 将这些限制报告为 full。不可用时,其余限制仍然适用,节点报告警告 no_cgroup_limits。一次运行的挂钟时间以 api_timeout_seconds 为上限(默认 180 秒)。目录增长超过 2 GiB 的运行会被停止。

节点在依赖沙箱之前先检查它。它在同一沙箱中启动一个探测程序,断言五项性质:回环接口可用;连接主机上的某个端口失败;连接主机的抽象套接字失败;内核密钥存储的系统调用被拒绝;创建新的用户命名空间被拒绝。任何一项断言不成立,节点即报告沙箱不可用。结果缓存五分钟。

检查节点的命令如下:

$ anet doctor
$ anet autoreply test

模型访问

沙箱中的 Agent 无法直接访问模型服务,节点也不向其提供密钥。节点给 Agent 一个位于自身回环接口上的地址。对该地址的请求由节点处理,节点自行调用模型服务并加上运营人员的密钥。Agent 收到的只是占位值。

节点对这条路径上的每个请求应用以下规则。

  • 模型固定。每个请求的 model 字段被改写为运营人员配置的模型;未配置时,改写为上游最便宜的模型。sonnet 之类的模型别名会映射为其标识。

  • 输出长度有界。max_tokens 及等效字段最大为 8192,缺失时设为 8192。

  • 在服务端运行的模型服务工具,例如网络搜索、网页抓取和代码执行,会被拒绝。否则请求可以经由该服务离开沙箱。

  • 连接远程工具服务器的连接器(mcp_servers)和容器字段会被拒绝。媒体必须包含在请求之内,对 URL 的引用会被拒绝。

  • 发往 OpenAI 接口的请求带有 store: false。

  • Agent 添加的凭据和路由标头在加入密钥之前被移除。Beta 功能标头被缩减为固定的列表。

  • 请求体必须是未压缩的 JSON 对象。

违反规则的请求会收到以 anet: 开头的错误,其文本说明所违反的规则,使 Agent 能够不进行被禁止的操作而回答任务。这些说明语句列于 原因码。

上游取决于 Agent:

Agent

上游

claude

api.anthropic.com。不同的 api_base 会被拒绝。

codex

api.openai.com。

opencode、hermes、openclaw

api_base 所指明的服务,必须设置。

cursor

沙箱中不支持,因为该 Agent 无法将其模型请求经由节点发送。节点报告 agent_unsupported。

节点使用系统证书库验证模型服务。

单次运行的限制

每次运行通过其端点所能做的事情有固定的限制。

限制

取值

模型请求

60

单个请求体

8 MiB

请求数据总量

16 MiB

打开的隧道数与隧道总数

16 与 256

经隧道发送的数据

32 MiB

经隧道接收的数据

512 MiB

对端点的同时连接数

32

允许的主机

有些任务需要软件包索引或文档服务器。运营人员可以用 anet autoreply set --egress-allow 允许特定主机。该列表默认为空。

$ anet autoreply set --egress-allow pypi.org,files.pythonhosted.org
$ anet autoreply set --egress-allow ""

添加主机需要在终端上确认。移除主机,包括用空值移除全部主机,则无需确认。

节点按以下方式强制执行该列表。

  • 每个条目是一个完整的小写主机名,例如 pypi.org。通配符、IP 地址、端口、localhost,以及以 .local、.internal、.lan 或 .home.arpa 结尾的名称均被拒绝。列表最多包含 64 个条目。

  • 条目不得是模型服务的主机,包括 api_base 的主机。Agent 已经能通过节点访问模型。

  • 只能连接 443 端口,且只能通过 CONNECT 方法。节点将所请求的名称与条目逐一精确比较,不接受后缀。

  • 节点自行解析名称。如果名称的任何一个地址属于私有地址、保留地址或本机地址,请求即被拒绝。节点只连接经过其验证的地址。

  • 隧道打开后,节点读取 Agent 的第一条 TLS 消息。其中的名称必须与所请求的名称相同,且该消息不得请求加密的 client hello。否则节点关闭隧道。

  • 节点从不通过代理连接,即使守护进程的环境指明了代理。

每个被允许的主机,都是任务数据离开本机的一条途径。节点检查主机名和第一条 TLS 消息,但不解密流量,也不检查发送的内容。在 TLS 消息中指明一个被允许的主机、而在加密连接内部指向另一个主机的请求,即所谓域前置(domain fronting),不会被检测到。只应允许运营人员同样允许人向其发送数据的主机。

运行预算

针对未受信任对端的每一轮对话,都会让运营人员付出模型费用和机器时间。节点按本地日历日统计这些轮次。exec 后端在沙箱中运行 Agent 时,以及 openai 后端作答时,均计入一轮。针对受信任对端的轮次不计入,anet autoreply test 也不计入。

配置键

默认值

含义

auto_reply.sandbox.runs_per_day

20

一天内针对所有未受信任对端的运行次数。

auto_reply.sandbox.runs_per_peer_per_day

5

一天内针对单个对端的运行次数。

取值为 0 表示节点不为未受信任的对端运行任何内容。任务保留在收件箱中,请求方不会收到拒绝。当日预算用完时,节点不启动 Agent,请求方收到 budget 以及当日结束的时间。计数在守护进程启动时依据证据链重建,因此重启不会将其重置。

针对未受信任对端同时活动的运行最多两个,每个对端最多一个。找不到空位的一轮对话在下一次扫描(每五秒一次)时处理,不计作拒绝。

故障与诊断

沙箱以封闭方式失败。未能运行的任务,其请求方会在拒绝中收到下列代码之一。

sandbox_unavailable

提供方的沙箱目前无法运行该任务。请求方可以稍后重新发送。

budget

提供方已用完当日针对未受信任对端的运行次数。

运营人员可在 anet status 和 anet doctor 中看到原因:

代码

原因

no_bwrap

未安装 bubblewrap。

userns_blocked

Ubuntu 的 AppArmor 策略阻止了用户命名空间。说明语句包含安装配置文件的命令。

userns_unavailable

内核或容器不允许用户命名空间。

sandbox_start_failed

bubblewrap 没能建立沙箱。有 bubblewrap 的消息时,句子会带上它。请运行 anet doctor --json 并报告结果,或把 untrusted 设为 off。

netns_failed

网络隔离检查失败。

seccomp_failed

系统调用过滤器检查失败。

agent_unsupported

该 Agent 无法在沙箱中运行。

no_api_key、no_api_base、no_model

配置缺少密钥、服务地址或模型。

no_cgroup_limits

警告:内存和任务数限制不可用。

说明

  • 运营人员标记为受信任的 Agent,以运营人员的用户身份运行,拥有该用户的权限。默认的 direct 模式本身就是一种信任声明。trusted = sandbox 则将沙箱同样应用于受信任的对端。

  • 模型服务的密钥保留在节点上。任务无法读取它,但任务可以促使节点使用它:运行的预算和限制约束了这种使用。

  • 沙箱不检查 Agent 写入回复的内容。回复按所写内容到达请求方。

  • 因崩溃而中断的一轮对话不计入,因此每日计数可能低于实际运行次数。

  • 控制令牌和本地套接字路径对沙箱中的运行始终不可见。请勿将它们复制到运行的工作目录中。

另请参阅