沙箱环境 (Sandboxes)
使用隔离沙箱安全运行代码、安装依赖并执行 Shell 命令
沙箱环境是专门的执行后端,为 Deep Agents 提供完全隔离的文件系统和执行 Shell 命令的 execute 工具。
为什么使用沙箱?
自主智能体在执行真实世界的编码、分析或运维任务时,往往需要运行未受信任的代码、安装临时依赖包或使用命令行工具。 如果直接在宿主机上执行这些操作,存在严重的安全隐患:
- 系统安全风险:智能体可能会误删宿主机文件、覆盖系统配置或执行危险指令。
- 依赖环境污染:频繁安装全局包可能导致开发机依赖冲突和环境腐化。
- 缺乏资源隔离:无法针对单个任务限制 CPU、内存使用配额及网络访问范围。
沙箱为智能体构筑了坚固的安全隔离墙:
- 智能体可以在沙箱内部无拘无束地运行代码、调试错误并安装任意依赖。
- 任何破坏性操作和文件改动都被严格约束在沙箱边界之内。
- 任务结束后,沙箱可以快速销毁或按会话安全重置。
基础用法
通过在创建 deep agent 时向 backend 参数传递沙箱后端实例来配置沙箱:
graph LR
subgraph Agent
LLM --> Tools
Tools --> LLM
end
Agent <-- backend protocol --> Sandbox
subgraph Sandbox
Filesystem
Bash
Dependencies
end
classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
classDef output fill:#EBD0F0,stroke:#885270,stroke-width:2px,color:#441E33
class LLM,Tools process
class Filesystem,Bash,Dependencies output受支持的沙箱提供商
Deep Agents 可以与多种主流云沙箱和本地容器无缝集成:
- LangSmith Sandbox:针对 LangSmith 深度优化的托管沙箱,内置身份代理与凭据安全注入。
- E2B:专为 AI 智能体设计的轻量级云端微型虚拟机沙箱。
- Docker / 本地容器:在自建基础设施上基于 Docker 容器运行隔离环境。
生命周期与作用域 (Lifecycle and scoping)
沙箱生命周期的选择决定了状态如何保存以及何时清理资源:
线程级作用域 (Thread-scoped,默认)
在对话线程创建时动态启动专属沙箱,整个多轮对话期间保持状态(已安装的包、生成的文件均可被后续轮次复用)。会话结束或超时后自动销毁回收。 这是大多数交互式任务推荐的默认策略。
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { ChatAnthropic } from "@langchain/anthropic";
import { SandboxClient } from "langsmith/sandbox";
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();
try {
const agent = createDeepAgent({
model: new ChatAnthropic({ model: "claude-opus-4-8" }),
systemPrompt: "You are a coding assistant with sandbox access.",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
const result = await agent.invoke({
messages: [
{
role: "user",
content: "Create a hello world Python script and run it",
},
],
});
void result;
} finally {
await client.deleteSandbox(lsSandbox.name);
}助手级作用域 (Assistant-scoped)
为某个助手实例维持一个长生命周期的共享沙箱。所有连接到该助手的对话和任务共享同一个沙箱环境。 适用于预装大型依赖、拥有庞大基础代码库或热启动要求极高的专用业务助手。
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const threadId = config.configurable?.thread_id as string;
const sandboxName = `thread-${threadId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
idleTtlSeconds: 3600, // TTL: clean up when idle
}));
return createDeepAgent({
model: "google:gemini-3.6-flash",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}架构集成模式 (Integration patterns)
连接智能体与沙箱主要有两种工程模式:
模式一:智能体在沙箱内运行 (Agent in sandbox)
整个智能体应用程序(包括模型调用循环、中间件和工具代码)直接运行在沙箱容器内部。智能体将沙箱的文件系统视为本地文件系统。
- 优点:极高的原生执行效率,直接调用系统 CLI。
- 代价:沙箱内部需要驻留完整的 Agent 运行时环境,增加了容器镜像的体积。
模式二:沙箱作为外部工具 (Sandbox as tool)
智能体部署在持久的长寿命服务器或云原生无服务器函数中,将远程沙箱当作一个通过网络 API 调用的工具。智能体通过网络发送 execute 命令和文件读写指令。
- 优点:生产级架构解耦,Agent 核心安全受控,沙箱可按需随时弹性扩缩容或快速销毁重建。
- 推荐场景:大规模多租户 SaaS 部署、基于 Webhook 的任务调度系统。
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const threadId = config.configurable?.thread_id as string;
const sandboxName = `thread-${threadId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
idleTtlSeconds: 3600, // TTL: clean up when idle
}));
return createDeepAgent({
model: "openai:gpt-5.5",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}沙箱工作原理解析
隔离边界 (Isolation boundaries)
沙箱基于 Linux 容器命名空间(Namespaces)、控制组(cgroups)或轻量级微虚拟机(MicroVM,如 Firecracker)构建,具备强隔离的进程、网络与存储空间。
execute 方法规范
沙箱后端实现了 SandboxBackendProtocolV2 接口,核心扩展方法为: execute(command: string): Promise<ExecuteResult> 该方法执行原生 Shell 命令,并返回包含标准输出(stdout)、标准错误(stderr)和退出状态码(exit code)的结构化结果。
文件访问的双平面 (Two planes of file access)
沙箱存在两个文件操作平面:
- 工具级访问:智能体通过
read_file、write_file等内置工具进行精确的单文件读写与行级编辑。 - 命令级访问:智能体通过
execute运行 Shell 脚本、编译器或构建工具(如npm install、git clone)批量操纵文件系统。
文件交互与管理
种子数据灌入 (Seeding the sandbox)
在智能体启动前,通过沙箱文件 API 将项目源码、配置文件或数据集提前上传到沙箱指定目录:
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const threadId = config.configurable?.thread_id as string;
const sandboxName = `thread-${threadId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
idleTtlSeconds: 3600, // TTL: clean up when idle
}));
return createDeepAgent({
model: "anthropic:claude-sonnet-5",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}提取构建产物 (Retrieving artifacts)
在智能体完成代码编写、报表生成或测试运行后,通过文件传输 API 将生成的文件或编译产物下载保存到宿主机或对象存储中:
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const threadId = config.configurable?.thread_id as string;
const sandboxName = `thread-${threadId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
idleTtlSeconds: 3600, // TTL: clean up when idle
}));
return createDeepAgent({
model: "openrouter:z-ai/glm-5.2",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}安全考量与最佳实践
安全处理密钥凭据
- 切勿直接硬编码主密钥:不要将生产级云账户的 Root 密钥直接注入沙箱环境变量。
- 使用鉴权代理:利用 LangSmith Sandbox 提供的 Auth Proxy,将外部 API 请求转发至宿主机代理层附带凭据,沙箱内部智能体仅能看到受限的局部令牌。
- 受限临时令牌:尽可能为沙箱配置具名、有时效且只读的临时访问凭证。
通用最佳实践
- 配置网络白名单:严格限制沙箱的外部出向网络访问,仅放行必要的依赖下载镜像源和 API 域名。
- 设置执行超时:为每一个 Shell 命令设置合理的超时上限(例如 300 秒),防止死循环或挂起进程耗尽计算资源。
- 资源配额限制:为沙箱分配明确的内存上限(如 2GB)和 CPU 核心数,防止单个任务引发宿主机资源饥饿。
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const threadId = config.configurable?.thread_id as string;
const sandboxName = `thread-${threadId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
idleTtlSeconds: 3600, // TTL: clean up when idle
}));
return createDeepAgent({
model: "fireworks:accounts/fireworks/models/glm-5p2",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const threadId = config.configurable?.thread_id as string;
const sandboxName = `thread-${threadId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
idleTtlSeconds: 3600, // TTL: clean up when idle
}));
return createDeepAgent({
model: "baseten:zai-org/GLM-5.2",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const threadId = config.configurable?.thread_id as string;
const sandboxName = `thread-${threadId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
idleTtlSeconds: 3600, // TTL: clean up when idle
}));
return createDeepAgent({
model: "ollama:north-mini-code-1.0",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}Assistant-scoped
Every thread on the same assistant reuses one sandbox. Files, installed packages, and cloned repositories persist across conversations.
WARNING
Assistant-scoped sandboxes accumulate in-sandbox state over time. Configure a TTL with your sandbox provider, use snapshots to reset periodically, or implement cleanup logic so disk and memory do not grow without bound.
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const assistantId = config.configurable?.assistant_id as string;
const sandboxName = `assistant-${assistantId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
}));
return createDeepAgent({
model: "google:gemini-3.6-flash",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const assistantId = config.configurable?.assistant_id as string;
const sandboxName = `assistant-${assistantId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
}));
return createDeepAgent({
model: "openai:gpt-5.5",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const assistantId = config.configurable?.assistant_id as string;
const sandboxName = `assistant-${assistantId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
}));
return createDeepAgent({
model: "anthropic:claude-sonnet-5",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const assistantId = config.configurable?.assistant_id as string;
const sandboxName = `assistant-${assistantId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
}));
return createDeepAgent({
model: "openrouter:z-ai/glm-5.2",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const assistantId = config.configurable?.assistant_id as string;
const sandboxName = `assistant-${assistantId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
}));
return createDeepAgent({
model: "fireworks:accounts/fireworks/models/glm-5p2",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const assistantId = config.configurable?.assistant_id as string;
const sandboxName = `assistant-${assistantId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
}));
return createDeepAgent({
model: "baseten:zai-org/GLM-5.2",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";
const client = new SandboxClient();
export async function agent(config: LangGraphRunnableConfig) {
const assistantId = config.configurable?.assistant_id as string;
const sandboxName = `assistant-${assistantId}`;
const existing = (await client.listSandboxes()).filter(
(sb) => sb.name === sandboxName,
);
const lsSandbox =
existing[0] ??
(await client.createSandbox({
name: sandboxName,
}));
return createDeepAgent({
model: "ollama:north-mini-code-1.0",
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
});
}For manual create, execute, and teardown outside a graph factory, see Basic usage and sandbox integrations for provider-specific APIs.
Integration patterns
There are two architecture patterns for integrating agents with sandboxes, based on where the agent runs.
Agent in sandbox pattern
The agent runs inside the sandbox and you communicate with it over the network. You build a Docker or VM image with your agent framework pre-installed, run it inside the sandbox, and connect from outside to send messages.
Benefits:
- ✅ Mirrors local development closely.
- ✅ Tight coupling between agent and environment.
Trade-offs:
- 🔴 API keys must live inside the sandbox (security risk).
- 🔴 Updates require rebuilding images.
- 🔴 Requires infrastructure for communication (WebSocket or HTTP layer).
To run an agent in a sandbox, build an image and install deepagents on it.
FROM python:3.11
RUN pip install deepagents-codeThen run the agent inside the sandbox. To use the agent inside the sandbox you have to add additional infrastructure to handle communication between your application and the agent inside the sandbox.
Sandbox as tool pattern
The agent runs on your machine or server. When it needs to execute code, it calls sandbox tools (such as execute, read_file, or write_file) which invoke the provider's APIs to run operations in a remote sandbox.
Benefits:
- ✅ Update agent code instantly without rebuilding images.
- ✅ Cleaner separation between agent state and execution.
- API keys stay outside the sandbox.
- Sandbox failures don't lose agent state.
- Option to run tasks in multiple sandboxes in parallel.
- ✅ Pay only for execution time.
Trade-offs:
- 🔴 Network latency on each execution call.
import "dotenv/config";
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
// Can also do this with Deno, Daytona, E2B, Modal, or Runloop
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();
const agent = createDeepAgent({
backend: new LangSmithSandbox({ sandbox: lsSandbox }),
systemPrompt:
"You are a coding assistant with sandbox access. You can create and run code in the sandbox.",
});
try {
const result = await agent.invoke({
messages: [
{
role: "user",
content: "Create a hello world Python script and run it",
},
],
});
const lastMessage = result.messages[result.messages.length - 1];
console.log(
typeof lastMessage.content === "string"
? lastMessage.content
: String(lastMessage.content),
);
} finally {
await client.deleteSandbox(lsSandbox.name);
}Open a public LangSmith run for this example.
The examples in this doc use the sandbox as a tool pattern. Choose the agent in sandbox pattern when your provider's SDK handles the communication layer and you want production to mirror local development. Choose the sandbox as tool pattern when you need to iterate quickly on agent logic, keep API keys outside the sandbox, or prefer cleaner separation of concerns.
How sandboxes work
Isolation boundaries
All sandbox providers protect your host system from the agent's filesystem and shell operations. The agent cannot read your local files, access environment variables on your machine, or interfere with other processes. However, sandboxes alone do not protect against:
- Context injection: An attacker who controls part of the agent's input can instruct it to run arbitrary commands inside the sandbox. The sandbox is isolated, but the agent has full control within it.
- Network exfiltration: Unless network access is blocked, a context-injected agent can send data out of the sandbox over HTTP or DNS. Some providers support blocking network access (e.g.,
blockNetwork: trueon Modal).
See security considerations for how to handle secrets and mitigate these risks.
The execute method
Sandbox backends have a simple architecture: the only method a provider must implement is execute(), which runs a shell command and returns its output.
Every other filesystem operation (read, write, edit, ls, glob, grep) is built on top of execute() by the BaseSandbox base class, which constructs scripts and runs them inside the sandbox via execute().
graph TB
subgraph "Agent tools"
Tools["ls, read_file, ..."]
execute
end
BaseSandbox["BaseSandbox<br/>(uses execute)"] --> Tools
execute_method["execute()"] --> BaseSandbox
execute_method --> execute
Provider["Provider SDK"] --> execute_method
classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900
class Tools,execute process
class BaseSandbox,execute_method process
class Provider triggerThis design means:
- Adding a new provider is straightforward. Implement
execute()—the base class handles everything else. - The
executetool is conditionally available. On every model call, the harness checks whether the backend implementsSandboxBackendProtocol. If not, the tool is filtered out and the agent never sees it.
When the agent calls the execute tool, it provides a command string and gets back the combined stdout/stderr, exit code, and a truncation notice if the output was too large.
You can also call the backend execute() method directly in your application code.
For example:
4
[Command succeeded with exit code 0]bash: foobar: command not found
[Command failed with exit code 127]If a command produces very large output, the result is automatically saved to a file and the agent is instructed to use read_file to access it incrementally. This prevents context window overflow.
Two planes of file access
There are two distinct ways files move in and out of a sandbox, and it's important to understand when to use each:
Agent filesystem tools: read_file, write_file, edit_file, ls, glob, grep, execute are the tools the LLM calls during its execution. These go through execute() inside the sandbox. The agent uses them to read code, write files, and run commands as part of its task.
File transfer APIs: the uploadFiles() and downloadFiles() methods that your application code calls. These use the provider's native file transfer APIs (not shell commands) and are designed for moving files between your host environment and the sandbox. Use these to:
- Seed the sandbox with source code, configuration, or data before the agent runs
- Retrieve artifacts (generated code, build outputs, reports) after the agent finishes
- Pre-populate dependencies that the agent will need
graph LR
subgraph "Your application"
App[Application code]
end
subgraph "Agent"
LLM --> Tools["read_file, write_file, ..."]
Tools --> LLM
end
subgraph "Sandbox"
FS[Filesystem]
end
App -- "Provider API" --> FS
Tools -- "execute()" --> FS
classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900
classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
classDef output fill:#EBD0F0,stroke:#885270,stroke-width:2px,color:#441E33
class App trigger
class LLM,Tools process
class FS outputWorking with files
Seeding the sandbox
Use uploadFiles() to populate the sandbox before the agent runs. File contents are provided as Uint8Array:
const encoder = new TextEncoder();
const responses = await sandbox.uploadFiles([
["src/index.js", encoder.encode("console.log('Hello')")],
["package.json", encoder.encode('{"name": "my-app"}')],
]);
// Each response indicates success or failure
for (const res of responses) {
if (res.error) {
console.error(`Failed to upload ${res.path}: ${res.error}`);
}
}Retrieving artifacts
Use downloadFiles() to retrieve files from the sandbox after the agent finishes:
const results = await sandbox.downloadFiles(["src/index.js", "output.txt"]);
const decoder = new TextDecoder();
for (const result of results) {
if (result.content) {
console.log(`${result.path}: ${decoder.decode(result.content)}`);
} else {
console.error(`Failed to download ${result.path}: ${result.error}`);
}
}NOTE
Inside the sandbox, the agent uses its own filesystem tools (read_file, write_file): not uploadFiles or downloadFiles. Those methods are for your application code to move files across the boundary between your host and the sandbox.
Security considerations
Sandboxes isolate code execution from your host system, but they don't protect against context injection. An attacker who controls part of the agent's input can instruct it to read files, run commands, or exfiltrate data from within the sandbox. This makes credentials inside the sandbox especially dangerous.
WARNING
Never put secrets inside a sandbox. API keys, tokens, database credentials, and other secrets injected into a sandbox (via environment variables, mounted files, or the secrets option) can be read and exfiltrated by a context-injected agent. This applies even to short-lived or scoped credentials—if an agent can access them, so can an attacker.
Handling secrets safely
If your agent needs to call authenticated APIs or access protected resources, you have two options:
Keep secrets in tools outside the sandbox. Define tools that run in your host environment (not inside the sandbox) and handle authentication there. The agent calls these tools by name, but never sees the credentials. This is the recommended approach.
Use a network proxy that injects credentials. Some sandbox providers support proxies that intercept outgoing HTTP requests from the sandbox and attach credentials (e.g.,
Authorizationheaders) before forwarding them. The agent never sees the secret—it just makes plain requests to a URL. This approach is not yet widely available across providers.
WARNING
If you must inject secrets into a sandbox (not recommended), take these precautions:
- Enable human-in-the-loop approval for all tool calls, not just sensitive ones
- Block or restrict network access from the sandbox to limit exfiltration paths
- Use the narrowest possible credential scope and shortest possible lifetime
- Monitor sandbox network traffic for unexpected outbound requests
Even with these safeguards, this remains an unsafe workaround. A sufficiently creative enough context injection attack can bypass output filtering and HITL review.
General best practices
- Review sandbox outputs before acting on them in your application
- Block sandbox network access when not needed
- Use middleware to filter or redact sensitive patterns in tool outputs
- Treat everything produced inside the sandbox as untrusted input
Connect these docs to your agent of choice via MCP for real-time answers.
