学习笔记 · Obsidian
Smith API:Sandbox 命令流、文件检索与隧道传输
Sandbox 数据面提供同步命令、SSE 可恢复命令流、WebSocket 交互、文件上传下载、服务器端 glob/grep 和 TCP tunnel。它们都能触达运行时内部数据,应使用 sandboxes:exec 等最小权限、隔离的 sandbox 身份和严格输入边界,不能因为运行环境“是临时的”就省略安全控制。
同步命令执行
POST /api/v2/sandboxes/{sandbox_id}/execute 接收 command、cwd、env、shell、timeout_seconds,返回 stdout、stderr 与 exit_code。页面描述说 command 可为 shell 字符串或 argv 数组,但当前 OpenAPI schema 将它定义为 array<string>。这是文档文字与机器契约的差异:生成客户端应以 argv 数组为稳定基线;只有在目标环境实测并固定兼容性后才发送 string。
同步接口适合输出有明确上限的短命令。HTTP 200 只表示执行请求成功,业务成功仍要检查 exit_code;stderr 非空也不一定表示失败。timeout、进程退出和网络断开是三类状态,调用方要分别记录,避免把超时解释为命令必定没有产生副作用。
shell 模式会引入 shell expansion 与注入面。来自用户或模型的参数优先作为 argv 元素传递,不拼接成一条命令;cwd 限制在工作目录;env 采用 allowlist,禁止继承控制面 secret。标准输出也可能包含 secret 或个人数据,进入 trace 前应截断与脱敏。
可恢复 SSE 命令流
POST .../execute/stream/start 要求 v2 runtime,响应是 text/event-stream。stdout/stderr payload 以 base64 传输,调用方需要按 event type 分流并在 decode 后按字节处理,不能假设每个 chunk 都是完整 UTF-8 行。
请求字段:
- command、cwd、env、shell;
command_id:幂等键;已知 ID 会 attach 到正在运行的 command,而不是再启动一份;stdin:整段 base64 一次写入,然后 stdin 关闭并让进程看到 EOF,不支持持续 stdin streaming;timeout_seconds=0表示无总超时;idle_timeout_seconds=0使用默认值,-1永不因空闲终止;ttl_seconds=0使用默认值,-1永久保留 command 状态。
调用方应始终生成并持久化一个业务请求到 command_id 的映射。SSE 连接断开时,用同一 command_id/offset 恢复;换新 ID 会重复执行可能有副作用的命令。-1 会产生无界运行或状态保留风险,只应对受控运维任务开放,并有平台外 watchdog。
当 sandbox 输出 buffer 需要确认时,start/resume 流以 ack_required 结束并给出 stdout/stderr offsets。POST .../stream/resume 的 offsets 同时表示:
- 从该字节位置继续发送;
- 确认此前字节已被调用方持久处理;
- 允许 sandbox 释放对应 buffer 并唤醒因 buffer 满而暂停的命令。
offset=0 会从头 replay,适合连接在任何输出前断开。消费方必须按 (command_id, stream, byte_offset) 去重,先持久化输出再推进 ack;否则 crash 后可能丢日志。Resume 只 attach,不会把 sandbox 已丢失的 command 重跑,找不到返回 404;状态冲突可返回 409。
WebSocket 执行与 TCP Tunnel
GET .../execute/ws 以 101 升级到 WebSocket,适合需要双向实时交互的命令。OpenAPI 页面只承诺升级,没有形式化 frame schema;客户端必须固定与目标运行时版本匹配的协议实现,设置最大 frame/消息、心跳、总时限和关闭码处理。
GET .../tunnel 同样用 WebSocket 建立到 sandbox TCP port 的隧道,目标端口放在 X-Sandbox-Port header。它是认证隧道,不是把端口公开到互联网。要限制可访问 port、连接时长、并发与字节数;禁止借 tunnel 扫描内部控制面、metadata service 或挂载网络。
长任务选择:
| 需求 | 接口 | 特征 |
|---|---|---|
| 短命令、有限输出 | sync execute | 一次 JSON 响应,无恢复协议 |
| 长命令、单次 stdin、可恢复输出 | SSE start/resume | command_id 幂等,offset ack/backpressure |
| 持续双向交互 | execute WebSocket | 协议需客户端与 runtime 配套 |
| 任意内部 TCP 协议 | tunnel WebSocket | 端口在 header,需更严格网络策略 |
文件上传与下载
Upload 使用 multipart/form-data,文件字段名为 file,目标 path 是必填 query 参数;返回实际 path 与写入字节数。Download 把文件 path 放在 query 并直接返回文件内容,不是 JSON。
这些页面没有给出最大文件、覆盖/原子替换、symlink 或 path traversal 的完整保证。客户端侧仍要:
- 规范化 path 并限制在工作根目录,拒绝
..、NUL 与意外绝对路径; - 对上传设置本地大小、类型与压缩包展开限制;
- 大文件用流式 I/O、校验长度/哈希,不将内容整体放入 trace;
- 如业务要求原子替换,先上传临时文件并在 sandbox 内做受控 rename;
- 下载响应按二进制处理,不根据文件名猜测安全 MIME 并在浏览器内联执行。
网络超时后,upload 是否已完整落盘未知,应查询文件/hash 后再决定重传。Download、glob、grep 是读取,可有界重试;upload 与 command 可能有副作用,必须先核对状态或使用业务幂等设计。
Glob 与 Grep
POST .../glob 在给定 path 下按 pattern 查找,支持 **,结果按 path 字典序返回。每项包含 path、是否目录、size 与 modified_at;达到 limit 时 truncated=true,没有 cursor,因此完整扫描需要缩小 root/pattern 分片,而不是反复发送同一请求。
POST .../grep 搜索的是 literal text,不是正则表达式;可用 glob 限定文件,返回 path、line、text,并同样用 truncated 指示截断。不要把用户输入当 regex 转义后再推断服务端行为。对于二进制、大文件、生成目录和 secret 目录,先用 glob 限定集合,避免无界扫描和敏感内容进入结果。
执行可靠性与审计
command accepted
→ process spawned
→ output streamed / buffered
→ exit event observed
→ output persisted
→ business result committed
只有最后一步完成才算业务成功。断线可能发生在任意阶段;同步执行没有 command_id,因此副作用命令应在 command 自身实现幂等键。SSE 模式把 command_id 与 offset checkpoint 一起持久化。
审计记录 sandbox ID、command_id、调用者、经过规范化的可执行程序名、cwd、开始/结束时间、exit/timeout/close 状态和输出字节数。不要记录 raw stdin、env secrets、service tokens 或未脱敏 stdout/stderr。400/403/404 通常是请求、权限或资源状态问题;500 只能对明确幂等读取/attach 做退避重试,不能据此自动重跑未知状态的命令。