状态:已退役的 Python gRPC 历史设计。 内置提供方现使用直接 HTTP,目录 Node 插件使用独立的 Node 引擎。下文示例是历史记录,不再是受支持的命令。
状态:方案。2026-10-14 由三方对抗得出。试验切片见 provider-engine.md。英文对照:provider-engine-plan.md。
那次评审当时已经落地的行为和下面的方案不一样:gRPC 引擎只创建 alicloud-express。OpenSandbox 当时是目录插件 awesome-providers/opensandbox,HTTP 留在插件进程里。目录提供方现在作为 node 模块跑在 Node 引擎上。
三方:
| 角色 | 结论 |
|---|---|
| 方案 | 一个本机引擎。SDK 只拨 Unix socket 上的 gRPC。引擎握着 HTTP、SSE、WebSocket。内置阿里云极速版和 OpenSandbox 都是引擎内适配器。 |
| 攻击 | 现有切片有 4 个 blocker:空凭证会借用引擎进程的密钥、守护线程加无条件 unlink、任意 URL 的 SSRF、创建之后命令和文件仍绕过引擎。 |
| 实测 | pytest 13 通过。Exec/Terminal 会连调用方给的地址。umask 0 时套接字是 0777,nobody 能调用 Exec。第二个进程会把正在监听的套接字 inode 换掉。 |
判定:现在的切片不能当作阿里云极速版的生产路径。 下面是收掉这些失败之后的方案。
SDK(Python、CLI,以及以后的其他语言)只认识三种调用:一次请求、一条服务端流、一条双向流。后端的 HTTP、SSE、WebSocket 只出现在引擎里。
exec 插件的标准输入输出不再承载文件、SSE 或终端。rest 只作为引擎内部的 HTTP 适配器,给协议已经对得上的后端。OpenSandbox 写成引擎内适配器,不再启动 node ./provider.mjs。
沙箱内的 server SDK 不选择 provider,不启动引擎。
- 一个套接字一个进程。默认
~/.abx/engine.sock。ABX_ENGINE_SOCKET必须是绝对路径。拒绝host:port。路径超过 108 字节就失败,不改去听 TCP。 ~/.abx为0700。bind 前umask 077,bind 后套接字chmod 0600。客户端看到 group/other 可访问就拒绝连接。- 先
Health。通了就附着,不删除套接字。不通才在文件锁里拉起python -m agent_sandbox.engine。禁止先 unlink 再 bind。 - 生产路径是脱离的子进程,不是随父进程死亡的守护线程。pytest 或
ABX_ENGINE_INPROCESS=1才在进程内启动,以便现有单测继续 patchSandboxProtocol。 - 空闲
ABX_ENGINE_IDLE_SECONDS(默认 900)后退出。崩溃后只对幂等 RPC 重试一次(Health、Get、List、Kill)。Create和所有流不自动重试。 Health.protocol必须是abx.engine.v1。对不上就拒绝,并提示重启引擎。
SDK 把已经解析好的配置放进 Creds。引擎不读 config.toml,也不读自己的环境变量来补密钥。缺 api_key 或后端地址就失败。
凭证只在 RPC 内存和这条套接字上。envd_access_token 与 execd 的 header 由引擎保存,不放进返回给 SDK 的状态里。之后的数据面 RPC 只带 sandbox_id。
一元:Health、Create、Connect、Get、List、Kill、SetTimeout、Pause、Resume、PortUrl、ExecRun、文件元数据、ProcessSignal、CodeExecute。
服务端流:ExecStart(原始字节,不是 base64)、FileRead(64 KiB 一块)、WatchDir。
双向流:FileWrite(第一帧带路径,之后是数据,写完一块再读下一块)、Terminal(OPEN / DATA / RESIZE / SIGNAL / CLOSE / EXIT)。
取消 RPC 时引擎关掉对应的 HTTP 流或 WebSocket。队列有上限(4),写满就停住上游读取。
错误带 trailer abx-error-code,Python 映射回原来的异常(401 → E1001,沙箱 404 → E2009,超时 → E3001)。不用 or 把 0 当成「没填」。零和缺省用 optional 分开。
现有的 Exec(sse_url) 和「第一帧自带 websocket_url 的 Terminal」只保留到迁移最后一步,并且只接受回环地址。生产调用不准传 URL。
适配器调用现有的 SandboxProtocol、ProcessProtocol、FilesystemProtocol,不新写一套 JSON。
- 创建仍是
POST /sandboxes,body 仍由SandboxConfig.to_create_payload()生成。 - 命令流走 envd 的 Connect(
application/connect+json)。引擎解码一次,SDK 收到原始字节。 - 文件字节走 envd 的
/files,64 KiB 一块,不进 JSON。 - 终端把
envd_url换成wss,路径/terminal。引擎握着这条 WebSocket。
Sandbox.create / connect 在内置 provider 上只向引擎发 RPC。返回的对象不再自己建一条绕过引擎的 HttpClient 去跑命令和文件。直接注入 mock protocol 的单测不经过引擎。
模块在引擎内。不启动已安装的 Node 插件,文件内容不以 content_b64 离开引擎。
生命周期走 OpenSandbox 的 HTTP。命令走 execd 的 SSE。文件走 execd 的上传下载。终端走 execd 的 WebSocket,数据帧带 0x00 前缀,由引擎剥掉后再交给 SDK。
第一刀明确返回 E8004 的:文件 watch、签名 URL、stdin、code、模板构建、按名字列进程。
~/.abx/providers 保留。rest 仍可安装,引擎读 manifest 后发 HTTP,不执行插件目录里的文件。
新的 exec 安装直接失败,错误码 E8010,消息固定为:exec providers cannot carry files, SSE or WebSocket; install refused。已经装上的标 disabled,选中时失败,不启动其进程。opensandbox 是内置 id,同名安装目录被忽略。
每一步之后,现有三个引擎测试保持通过,云上创建请求的 body 仍由 to_create_payload() 生成。
Health、文件锁、0700/0600、禁止 unlink 活着的套接字。旧 RPC 行为不变。- 旧的
Exec/Terminal只允许回环 URL。 - 加上新 RPC,旧字段保持默认值。
- 内置生命周期全部进引擎。
- 内置命令、文件、终端进引擎。
tests/test_protocol仍直接测协议类。 - CLI 改拨同一个套接字。
- OpenSandbox 适配器。单测断言没有
content_b64。 - 拒绝新的 exec 安装。
- 删掉按 URL 出站的旧 RPC,把三个测试改到新 RPC 上,断言保持原意。
不实现 Go/Java SDK,不交付非 Python 的引擎二进制,不做 Windows 命名管道。不把沙箱内 9000 端口的 server SDK 代理进引擎。不自动恢复断开的终端流。不让引擎读配置文件。不在这一刀删除 EPP 源文件,只停止调用并拒绝安装。