云沙箱的文件通道:Agent 真正操作的是 Workspace

0 阅读23分钟

最近把 Agent 接到云沙箱上以后,我反复碰到同一个别扭的地方。

命令已经能跑了。stdout 也能流回来了。沙箱也能按会话存活一段时间了。

但真正让工作流卡住的,往往不是下一条 ls,而是:

文件怎么进去,改完以后又怎么出来。

这个问题看起来很土。

土到很多人会下意识用三种办法糊过去:先打一个 zip 丢进去;再不行就 base64 塞进命令;再不行就让 Agent 自己 curl 一张预签名链接。

这三种办法都能“看起来能用”。

也正是因为看起来能用,文件通道才最容易被当成附件功能,而不是 Runtime 的一部分。

上一篇文章里我写过:对 Agent 来说,云沙箱更像一台临时电脑,而不是一次函数调用。那篇文章把问题停在 Runtime。这篇文章想把 Runtime 再往里拆一层:

如果沙箱是 Agent 的临时电脑,那么文件通道就是这块电脑上的磁盘接口。

磁盘接口一旦设计错了,Agent 并不会变笨。

它只会开始用更危险、更贵、更不稳定的方式自己“发明”磁盘。


一. 为什么执行通道不够

1. Agent 真正碰的不是进程,是树

一个 Coding Agent 接到任务后,脑子里通常不是:

运行命令 A
运行命令 B
运行命令 C

而是:

/workspace
├── README.md
├── src/
│   └── main.py
├── tests/
└── output/

它要做的事情更接近: 在这里插入图片描述 命令只是手段。

被反复读写、反复观察的,是 Workspace 这棵树。

所以,如果一个沙箱产品只提供:

创建环境
执行命令
销毁环境

它其实只覆盖了 Agent 工作流的中间那一截。

两端的文件,仍然是调用方自己需要处理的负担。


2. 整包进、整包出,适合开始和结束,不适合中间

云沙箱里最经典的文件模型,是归档:

input_workspace_url
        ↓
解压到 /workspace
        ↓
执行命令
        ↓
把 /workspace 再打成包
        ↓
output_workspace_url

这个模型非常干净。

一次性任务尤其适合它。因为一次性任务的生命周期就是:

准备 → 执行 → 销毁

输入在开始前就应该准备好,输出在结束时一次性拿走。中间不需要随机插入一个 PDF,也不需要中途把某个日志文件单独抽出来。

会话型沙箱不一样。

会话会活几分钟到几小时。Agent 可能:

  • 先创建空环境
  • 再塞进用户刚刚上传的简历
  • 跑完一轮解析
  • 再补一份补充材料
  • 改一个中间 JSON
  • 把某一页预览图拿出来给人看
  • 最后才销毁并导出整棵树

如果你强迫它每次都重新打包、重新创建、重新 hydrate,表面上还是“有文件能力”,实际上你把会话退回成了一次性任务。

状态没了。

已安装的依赖没了。

正在听的端口没了。

Agent 刚才建立的工作记忆,也一起没了。

所以归档通道解决的是:

任务边界上的世界迁移。

它不解决:

世界还活着的时候,怎么处理其中的一个文件。


3. 用命令通道传文件,是最常见的“假文件接口”

没有单文件 API 时,工程师会很快发明这一套:

把文件变成文本
        ↓
塞进命令的 stdin 或参数
        ↓
在沙箱里重定向到磁盘
        ↓
需要读回来时再 cat / base64

短文本勉强能活。

一到真实文件,问题会成串出现:

  • 二进制会坏
  • 特殊字节会和 shell 解析打架
  • 大文件会打满命令输出上限
  • 参数里可能出现密钥、签名、路径
  • 写文件和正在跑的命令抢同一个执行槽
  • Agent 会把“传文件”理解成又一次工具调用,于是开始自己拼命令

更麻烦的是安全语义。

命令通道的本意是:在隔离环境里执行用户意图。

文件通道的本意是:在隔离环境的指定位置放置或取出字节。

两件事混在一起后,平台很难再回答这些基本问题:

  • 这次调用到底有没有执行任意命令?
  • 文件最终落在了哪?
  • 有没有经过路径校验?
  • 失败时有没有留下半截文件?
  • 输出截断到底是命令失败,还是文件传输失败?

一旦这几个问题说不清,文件就不再是一等能力,而只是命令的副作用。

Agent Runtime 最怕的就是副作用。

因为 Agent 会把副作用当成可组合的工具,继续往上叠。


4. 让沙箱自己去下载,会把文件问题变成网络问题

还有一种更“聪明”的办法:

文件已经在对象存储里了,给沙箱一个 URL,让它自己拉。

对调用方很省事。

对平台很危险。

因为这一步会立刻碰到另一类经典问题:

  • URL 指向哪里?
  • DNS 解析到了哪些地址?
  • 重定向会不会把请求带进内网?
  • 鉴权头会不会跟着跳?
  • 失败时临时文件清不清理?
  • 沙箱出网策略能不能覆盖这次下载?

也就是说,文件传输一旦改成“沙箱主动取”,安全边界就从路径穿越,变成了 SSRF、出网策略和凭证泄漏。

这不是不能做。很多平台的 input_workspace_url 就是这条路,而且必须做得很严。

但它仍然不适合充当会话中途的随机文件口。

原因很具体:

  1. 调用方先要把文件变成一个可下载地址,等于又引入一轮对象存储协议。
  2. 每次中途补文件都要走“生成地址 → 沙箱拉取 → 解压或落盘”,延迟和失败模式都更重。
  3. Agent 如果被允许自己拼 URL,平台很难区分“用户给的输入”和“模型临时编出来的地址”。

所以我会把下载 URL 看成:

受控的文件导入。

而不是:

活着的磁盘 API。


二. 文件通道到底在解决什么

如果把云沙箱看成一台临时电脑,文件相关的能力其实有三层,不该混成一个按钮。

在这里插入图片描述

三层的职责完全不同。

通道解决的问题典型时机不该拿来做的事
导入归档把一个已有世界搬进去创建时中途塞一个小文件
单文件读写在活着的世界里放/取单个对象会话中途当对象存储用
导出归档把世界打包带走并释放占用销毁/到期每次命令后都打一次包

很多人觉得“有文件功能了”。

真正要问的是:

你提供的是哪一层,以及你有没有把三层的失败模式分开。


1. 单文件通道的产品形状,其实非常克制

一个适合 Agent 的会话文件接口,通常长这样:

创建会话
   ↓
按路径写入单个文件
   ↓
执行命令
   ↓
再读回某个结果文件
   ↓
必要时继续写、继续跑
   ↓
销毁会话并导出整棵 /workspace

注意几个细节点。

第一,它是 单文件,不是网盘。

一次请求对应一个路径、一份字节。不在这个接口上做列表、批量、秒传、断点续传、目录同步。那些是对象存储或工作区协议的事。

第二,它只打在 还活着的会话 上。

一次性任务没有“中途”。文件应该在创建前准备好,结束后从归档取。把单文件读写挂到一次性执行上,会把两种生命周期揉在一起。

第三,路径是工作区相对路径,不是沙箱任意路径。

resume.pdf 的意思是 /workspace/resume.pdf

不是 /etc/passwd,不是 /home/sandbox/.ssh,也不是 ../../proc

第四,读成功时返回的是文件字节,不是又包一层业务 JSON。

写成功可以回一个很薄的条目信息:绝对路径、名字、类型。读成功如果还套 envelope,浏览器、SDK、Agent 工具都会很难把它当文件用。

这些克制看起来像“功能少”。

但对 Agent Runtime 来说,少即是边界。


2. 控制面和数据面为什么不该拆到沙箱域名上

行业里有一种很直观的做法:

每个沙箱有自己的地址,文件请求直接打到沙箱里的小服务。

优点是像一台真电脑。

缺点也非常像一台真电脑:

  • 每个沙箱都要暴露一个文件代理
  • 鉴权容易变成 URL 上的短时签名
  • 调用方要同时记住 API 域名和沙箱域名
  • 沙箱网络策略、证书、生命周期都要为这个小服务让路

另一种做法是反过来:

调用方
  ↓
统一的 API 入口(Token / 网关身份)
  ↓
调度到持有该会话的执行节点
  ↓
在该会话的 /workspace 上落盘或读盘

文件请求仍然是会话的一部分,而不是沙箱自己开的后门。

对调用方,这意味着:

  • 鉴权模型和创建会话、执行命令是同一套
  • 路径里的会话 ID 就是业务主键,不是执行节点内部的实例号
  • 不需要理解沙箱内部有没有文件代理进程

对平台,这意味着:

  • 文件通道可以复用租户隔离、会话存在性检查、忙闲状态
  • 不把数据面权限下放到“谁拿到沙箱地址谁就能读盘”
  • 执行节点可以升级文件能力,而不必在用户环境里再跑一个常驻文件服务

我越来越倾向于后一种。

不是因为它更“云原生”,而是因为 Agent 已经够容易把环境搞乱了。文件通道如果再变成沙箱内的另一个服务,攻击面和心智负担会一起加大。


三. 路径、大小、并发:文件通道的三条硬边界

文件接口一旦公开,攻击者和 Agent 都会立刻开始试探边界。

而且 Agent 的试探往往不是恶意的。

它只是在完成任务。

所以这三条边界必须写进模型,而不是写进“请调用方注意”。


1. 路径边界:只开放工作区,而且必须在解析后仍然如此

沙箱里通常有好几块可写面,职责并不一样:

/workspace     任务世界,导入导出都围绕它
/home/sandbox  进程的 HOME,配置和缓存
/tmp           临时文件,有容量上限
rootfs         只读

单文件通道如果把这三块可写面全打开,表面上更像 Linux。

实际上会把平台推进两个坑:

  • Agent 把依赖、密钥、缓存写进 HOME,销毁时你却只导出 /workspace
  • 路径一旦允许绝对路径,符号链接和 .. 会迅速变成逃逸题

更稳妥的第一期模型是:

HTTP/OpenAPI/MCP 的文件口,只服务于 /workspace。

相对路径拼到工作区根。

绝对路径必须规范化后仍落在工作区里。

.. 直接拒绝。

符号链接不能成为逃出工作区的跳板。

目录不能当文件读。

不存在的路径不能读。

覆盖已有文件可以允许,因为 Agent 改文件就是这个动作;但覆盖仍然必须落在同一个安全根下面。

父目录可以隐式创建。

这对 Agent 很重要。否则它每次写 src/app/main.py 之前,都得先发明一轮 mkdir -p

那又会把文件通道打回命令通道。

有一个很容易忽略的点:

对外错误里不要回显完整内部路径、查询参数或存储地址。

调用方只需要知道:路径不合法、文件不存在、太大、会话忙。不需要知道执行节点把文件落在了哪台机器的哪个目录。


2. 大小边界:传输上限不是磁盘上限

这是我见过最容易写错文档的地方。

至少有三种完全不同的“大”:

1. 单次 API 读写允许传输多少字节
2. 沙箱里用命令生成的单个文件可以多大
3. 整棵 /workspace 最终导出时可以多大

三者不该用同一个数字,也不该用同一个错误码糊弄过去。

一个比较清楚的切法是:

  • 单次全量读写有上限。例如几十 MiB。这是控制面内存、网关超时、同步 RPC 的保护,不是磁盘配额。
  • 沙箱内部用命令生成更大文件,可以允许。Agent 跑构建、导出视频、写数据集,本来就会超过 API 同步口。
  • 整棵工作区另有总量上限。销毁导出时如果超了,应该明确失败,而不是静默截断。

于是调用方的策略也变得清楚:

在这里插入图片描述

MCP 场景还要再拆一层。

模型上下文装不下几十兆原始字节。即使平台允许 HTTP 一次拉 64MiB,给模型的读工具也通常应该按 1MiB 左右分块,用偏移量续读。

写也一样。

让模型一次生成整份大文件,既不经济,也不稳定。更自然的方式是:大文件走归档或多次分块,小文件走单次写入。

这里有一个产品上的原则:

拒绝,并且告诉调用方这是传输上限,而不是“沙箱太弱写不下”。

否则 Agent 会改用命令通道硬写,边界等于没设。


3. 并发边界:文件和命令抢的是同一块活世界

会话型沙箱通常同一时刻只允许一条命令。

文件通道必须回答:命令正在跑时,能不能写文件?

有两种诱惑:

  • 让写文件走另一条旁路,显得更“实时”
  • 让写文件排队,等命令结束自动落盘

第一期更安全的答案往往是失败得干脆:

会话忙,就拒绝这次文件请求。

原因不是偷懒。

而是这两件事会在同一棵目录树上交错:

命令正在写 output.json
        ↕
文件接口同时覆盖 output.json

谁先谁后,对 Agent 都是不可观测的。

更糟的是半截写入:命令读到一半文件,或导出时打到一半文件。

所以忙时 fail-fast,其实是把时序交给调用方。

对 Agent 来说,正确姿势通常是:

等命令结束
  ↓
写/读文件
  ↓
再发起下一条命令

这和人类用 IDE 的直觉也不远:编译正在跑的时候,你不会同时用另一只手覆盖源文件,还期望结果可解释。


四. 读和写的不对称,是有意的

文件上传和下载看起来对称,实现约束并不对称。


1. 写:字节进得去,语义要停在工作区

写请求需要明确几件事:

  • 这是哪一个会话
  • 要写到工作区的哪一个相对路径
  • 字节从哪来:原始流,或表单里的文件字段
  • 写完以后文件的身份是什么

成功响应不必把文件再回传一遍。

回一个条目就够了:

path: /workspace/resume.pdf
name: resume.pdf
type: file

失败则应该是稳定类别,而不是存储系统原话。

常见类别大致是:

  • 未认证
  • 会话不存在或不属于当前身份
  • 会话不是可写状态
  • 路径不合法
  • 文件太大
  • 会话正忙
  • 下游暂时不可用

“覆盖已有文件”建议允许。

Agent 的工作方式就是反复改同一份文件。禁止覆盖会逼它先删后写,而删除如果还要走命令,边界又花了。

“自动创建父目录”也建议允许。

否则 src/a/b/c.py 这种正常项目结构,会退化成一串准备工作。

不建议在第一期做的,是把写入做成公开可分享的上传地址。

因为那会把“谁能往这个会话里塞文件”从 Token 鉴权,变成“谁拿到链接谁就能写”。会话还活着的时候,这个权限模型通常过宽。


2. 读:成功是裸字节,失败才是 JSON

读是下载,不是查询。

所以成功响应更像:

Content-Type: application/octet-stream
Body: 文件本身

失败才回到统一错误包。

这个不对称会让接口文档看起来“不优雅”。

但对调用方极重要。

Agent、浏览器、脚本都可以把一次成功读取直接存盘。如果成功时还套 code/message/data,每个人都要写一个“从 JSON 里把文件抠出来”的解析器,还得处理编码和截断。

读还有几条必须拒绝的对象:

  • 目录
  • 符号链接
  • 工作区以外的路径
  • 不存在的文件
  • 超过单次传输上限的文件

最后一条特别容易被理解成产品缺陷。

其实它在保护同步读取这条路。超大文件的正确出口是:

  • 工具层分块读
  • 或者会话结束后的归档下载

不要把文件接口理解成对象存储的 GetObject

它没有桶,没有 key 列表,没有生命周期规则,也没有公开 CDN。它只是:

在某个仍属于你的临时电脑上,读取工作区里的一个普通文件。


3. 导入导出:文件通道的两端,仍然是归档

单文件接口再好,也不该取代归档。

创建会话时,input_workspace_url 解决的是“把已有项目一次性搬进来”。它面对的是 zip / tar.gz,而不是单个简历。

销毁会话时,导出解决的是“把 Agent 留下的世界带回去”。命令结束并不自动导出,这是会话和一次性任务的关键差别。

在这里插入图片描述

这里有几个原则值得单独说清。

第一,导出的是 /workspace,不是整台沙箱磁盘。HOME 和 /tmp 里的东西,默认不该被当成任务产物。

第二,工作区太大时,主动销毁可以拒绝并保持会话仍在。这样调用方还能进去删文件再试。到期回收则必须释放占用,能导出就导出,不能导出也要结束计费。

第三,下载链接必须是临时的。它是结果的领取凭证,不是又一个长期文件服务。文档、日志、示例里都不该出现真实密钥和长期签名。

第四,从上一轮产物继续工作,最干净的方式是把上一轮的输出归档,作为下一轮的输入归档。单文件通道负责中途,归档通道负责世代。


五. 安全:文件通道最容易被低估的地方

云沙箱的安全宣传经常停在“代码不在你的机器上跑”。

文件通道会重新打开几扇门。


1. 路径穿越不是理论题

只要接受 path,就会出现:

../../somewhere
/workspace/../...
看起来在工作区里的符号链接

原则只有一句:

以规范化之后、解析符号链接之后的最终落点为准,而不是以用户传入的字符串为准。

字符串看起来安全,不代表最终打开的是工作区文件。

这也是为什么文件通道不该复用命令通道。命令通道很难对“最终落点”做同样严格的断言。


2. 文件下载不是 SSRF,工作区导入才是

这两件事经常被写成同一个“下载”。

它们不是。

会话文件读取:平台把已经在沙箱工作区里的字节,交给已经鉴权的调用方。危险主要是路径逃逸和越权读别人的会话。

工作区导入:平台或沙箱去拉一个外部 URL。危险主要是把请求打到元数据地址、内网、链路本地,或把鉴权头带到不可信主机。

所以导入 URL 必须走另一套规则:只接受绝对 HTTP/HTTPS,拒绝用户名密码,校验所有解析结果是不是公网单播,重定向每次重验,限制跳数和大小。配置里的对象存储走 SDK,而不是再当普通 URL 去抓。

单文件通道反而应该避开这套复杂性。

它不接 URL。

它接字节。

谁能把字节送进来,只取决于会话鉴权和会话状态。


3. 鉴权发生在统一入口,而不是文件名里

文件路径里不要放身份。

不要让 path 能覆盖 Token 解析出来的用户或团队。

不要用查询参数当长期密钥。

OpenAPI 用 Token,控制台用网关身份,两条路可以对称暴露同一套文件能力,但谁也不能用请求体“我是谁”改写鉴权结果。

一个实用的检查是:

换一个 Token,去读别人的会话文件,应当得到“不存在”,而不是“没权限但文件确实在”。

不泄露归属,本身就是隔离的一部分。


4. 日志里不能出现文件内容

文件通道会经过 API、调度、执行节点。

沿途日志如果图方便把路径、URL、请求体打出来,等于把用户文件复制到了日志系统。

原则仍然土:

  • 记会话 ID、大小、成功或失败类别
  • 不记 Token、Cookie、签名查询
  • 不记文件内容
  • 不把存储内部地址返回给调用方

文件能力越方便,这条越重要。因为方便意味着调用频率会上升。


六. 对 Agent 工作流意味着什么

把上面的边界合在一起,Agent 的文件使用方式会从“想办法把字节弄进去”,变成一套稳定动作。


1. 推荐工作流

1. 创建会话,拿到会话 ID
2. 小文件直接写入 /workspace
3. 大项目用归档在创建时导入
4. 执行命令,观察输出
5. 按需读回某个结果文件,决定下一步
6. 需要时延长会话,而不是复制环境
7. 结束后销毁,领取整棵工作区

对应到工具层,Agent 不需要理解容器、存储和调度。

它只需要会用几个高层动作:

  • 创建环境
  • 写文件
  • 读文件
  • 执行命令
  • 观察输出
  • 销毁环境

这和 MCP 把沙箱本身变成 Tool 的方向是一致的。

上一篇写过:Agent 应该使用能力,而不是基础设施。文件通道补上的,正是当时那张能力清单里最空的两格。


2. 一次性任务仍然不该假装自己是会话

有了文件通道之后,会有一种新的误用:

既然能传文件了,干脆所有任务都开会话。

这通常更贵,也更容易忘了销毁。

判断标准还是状态。

没有中间状态、不需要反复改同一棵树,就走一次性执行:导入、运行、拿归档、自动释放。

需要连续改文件、装依赖、看结果、再改文件,才开会话,并用单文件通道作为中途的手。

文件能力不应该抹平这两种计算形态。它只是让会话形态终于完整。


3. 调用方最容易踩的几个坑

这些坑和聪明程度无关,和模型有关。

把 Worker 内部实例 ID 当成会话 ID。
后续读写文件、执行命令、销毁,用的都是创建结果里的业务 ID。

命令还在跑就写文件。
应等待命令结束。忙就是忙,不要靠重试把交错写入变成“偶尔成功”。

用文件接口读目录。
目录不是文件。需要看树,用命令;需要带走树,用导出。

把单次传输上限当成磁盘坏了。
超限后换分块或归档,而不是改用 cat 绕过。

不销毁。
会话按开着的时间计费。文件再方便,占用也还在。用完必须关电脑。

把临时下载链接写进文档当永久地址。
链接过期是特性。产物要持久化,就在拿到的那一刻存到自己的对象存储里。


七. 这类能力现在长什么样

把文件通道补齐以后,云沙箱更接近一个完整的临时 Runtime:

隔离的计算
  +
可续期的生命周期
  +
命令与实时输出
  +
活着的工作区
  +
进入和离开沙箱的归档

缺了文件通道时,它只是“能跑命令的隔离环境”。

有了文件通道,它才开始像 Agent 的电脑:盘还在,文件还能继续改,人(或模型)可以中途塞资料、中途抽结果。

我看过的几类产品,大致都在逼近这个模型,只是接口哲学不同。

有的方案把文件服务放进沙箱内部,让每个环境更像一台带小网关的主机。

有的方案把一次性函数做强,文件主要靠对象存储和归档周转。

还有一种更轻的切法:不要求你先换掉整个 Agent 框架,只把 Runtime 这一层交出去,用统一 API 和 MCP 工具来创建会话、读写工作区文件、执行命令、最后销毁。

百智云目前把 Cloud Sandbox 放在 Isolated Agent Runtime 这条线上,接入是 API 与 MCP。会话文件也落在统一入口下,而不是再给每个沙箱一个独立文件域名。

想对照公开约定的话,可以直接看控制台生成的接口说明:Huskbox API 集成文档 产品形态概览在 百智云云沙箱

这些链接适合当对照,不适合当答案

真正值得带走的仍然是设计模型:

归档负责世界的世代,单文件负责世界还活着的时候,命令负责改变这个世界。


八. 总结

Agent 的工作对象是 Workspace,不是某一条命令。
命令是手,文件树才是桌子上的稿纸。

整包导入导出,解决的是任务开始和结束。
会话中途再靠打包重建,等于把临时电脑反复关机。

不要用命令通道或随意 URL 冒充磁盘接口。
前者把文件变成副作用,后者把文件变成网络攻击面。

单文件通道必须又窄又硬。
只对存活会话、只对 /workspace、只对普通文件、忙则拒绝、超限则拒绝。

传输上限不是磁盘上限。
小文件走同步读写,大文件走分块或归档,整棵树走销毁导出。

读成功就给字节,写成功就给条目,错误给稳定类别。
文件接口首先是文件,其次才是 API。

上一篇的结论是:Agent 需要的不是更聪明的下一跳,而是一个临时存在、有明确边界的计算世界。

这一篇不过是把那句话写具体一点:

没有可靠的文件通道,所谓 Runtime 仍然只是一个会执行命令的空房间。

房间可以很安全。

但 Agent 进不去自己的书桌,也就很难真正把活干完


参考阅读

百智云云沙箱|Agent 云端运行环境
定位为独立的 Isolated Agent Runtime,支持 API 与 MCP。会话文件约定见控制台生成的开发者文档。
产品页 · API 集成文档

阿里云 FC Agent Sandbox
面向 AI Agent 和代码执行的云端隔离运行环境。
查看文档

腾讯云 Agent 沙箱服务 AGSX
面向智能体的沙箱执行环境。
查看产品