
sandbox-runtime v0.0.74
一个轻量级沙箱工具,用于在操作系统层面强制执行任意进程的文件系统和网络限制,而无需使用容器。
Anthropic 沙箱运行时(srt)
一个轻量级沙箱工具,用于在操作系统层面强制对任意进程实施文件系统和网络限制,无需容器。
srt 使用原生操作系统沙箱原语(macOS 上的 sandbox-exec,Linux 上的 bubblewrap)以及基于代理的网络过滤。它可用于沙箱化智能体、本地 MCP 服务器、bash 命令和任意进程的行为。
Beta 研究预览版
沙箱运行时是为 Claude Code 开发的研究预览版,旨在实现更安全的 AI 智能体。它作为早期开源预览版发布,以帮助更广泛的生态系统构建更安全的智能体系统。由于这是早期研究预览版,API 和配置格式可能会发生变化。我们欢迎反馈和贡献,以默认让 AI 智能体更安全!
安装
npm install -g @anthropic-ai/sandbox-runtime
基本用法
# Network restrictions
$ srt "curl anthropic.com"
Running: curl anthropic.com
<html>...</html> # Request succeeds
$ srt "curl example.com"
Running: curl example.com
Connection blocked by network allowlist # Request blocked
# Filesystem restrictions
$ srt "cat README.md"
Running: cat README.md
# Anthropic Sandb... # Current directory access allowed
$ srt "cat ~/.ssh/id_rsa"
Running: cat ~/.ssh/id_rsa
cat: /Users/ollie/.ssh/id_rsa: Operation not permitted # Specific file blocked
概述
此包提供了一个独立的沙箱实现,既可作为 CLI 工具使用,也可作为库使用。它遵循默认安全的设计理念,针对常见的开发者使用场景量身定制:进程以最小权限启动,您只需显式地打开所需的权限缺口。
核心能力:
- 网络限制:控制可通过 HTTP/HTTPS 及其他协议访问的主机/域名
- 文件系统限制:控制可读取/写入的文件/目录
- Unix 套接字限制:控制对本地 IPC 套接字的访问
- 违规监控:在 macOS 上,接入系统的沙箱违规日志存储以获取实时警报
使用案例:沙箱化 MCP 服务器
一个关键用例是对模型上下文协议(MCP)服务器进行沙箱化,以限制其能力。例如,要对文件系统 MCP 服务器进行沙箱化:
不使用沙箱(.mcp.json):
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem"]
}
}
}
使用沙箱(.mcp.json):
{
"mcpServers": {
"filesystem": {
"command": "srt",
"args": ["npx", "-y", "@modelcontextprotocol/server-filesystem"]
}
}
}
然后在 ~/.srt-settings.json 中配置限制:
{
"filesystem": {
"denyRead": [],
"allowWrite": ["."],
"denyWrite": ["~/sensitive-folder"]
},
"network": {
"allowedDomains": [],
"deniedDomains": []
}
}
现在 MCP 服务器将被阻止写入被拒绝的路径:
> Write a file to ~/sensitive-folder
✗ Error: EPERM: operation not permitted, open '/Users/ollie/sensitive-folder/test.txt'
工作原理
该沙箱使用操作系统级原语来强制执行适用于整个进程树的限制:
- macOS:使用
sandbox-exec配合动态生成的 Seatbelt 配置文件 - Linux:使用 bubblewrap 进行容器化,并实现网络命名空间隔离
- Windows:在专用的
srt-sandbox本地用户账户下运行沙箱进程,并使用以该账户的 SID 为键的 Windows 筛选平台 出站防护,以及在工作树上的每会话显式 ACE
0d1c612947c798aef48e6ab4beb7e8544da9d41a-4096x2305
双重隔离模型
有效的沙箱化需要同时实现文件系统和网络隔离。如果没有文件隔离,被攻陷的进程可能会窃取 SSH 密钥或其他敏感文件。如果没有网络隔离,进程可能逃逸沙箱并获得不受限制的网络访问。
文件系统隔离 强制执行读和写限制:
- 读(先拒绝后允许模式):默认情况下,允许在所有位置进行读取访问。你可以拒绝大范围区域(例如
/Users),然后重新允许其中的特定路径(例如.)。allowRead优先于denyRead——这与写操作相反,在写操作中denyWrite优先于allowWrite。比其所在的allowRead区域更具体的denyRead条目(例如denyRead: ["**/.env"]或["./secrets"]配合allowRead: ["."])仍然保持拒绝状态。 - 写(仅允许模式):默认情况下,拒绝在所有位置进行写入访问。你必须显式允许路径(例如
.、/tmp)。空的允许列表意味着没有写入访问权限。
网络隔离(仅允许模式):默认情况下,拒绝所有网络访问。你必须显式允许域名。空的 allowedDomains 列表意味着没有网络访问权限。网络流量通过运行在主机上的代理服务器进行路由:
-
Linux:请求通过 Unix 域套接字经由文件系统进行路由。沙箱进程的网络命名空间被完全移除,因此所有网络流量必须通过运行在主机上的代理(监听绑定挂载到沙箱中的 Unix 套接字)
-
macOS:Seatbelt 配置文件仅允许与特定 localhost 端口通信。代理监听此端口,为所有网络访问创建受控通道
-
Windows:机器范围的 WFP 筛选器集阻止源自
srt-sandbox账户的所有出站连接,但发往代理端口范围的环回连接除外。代理在该范围内监听,为所有网络访问创建受控通道
HTTP/HTTPS(通过 HTTP 代理)和其他 TCP 流量(通过 SOCKS5 代理)均由这些代理进行中介,这些代理强制执行你的域名允许列表和拒绝列表。
有关 Claude Code 中沙箱化的更多详细信息,请参阅:
架构
src/
├── index.ts # Library exports
├── cli.ts # CLI entrypoint (srt command)
├── utils/ # Shared utilities
│ ├── debug.ts # Debug logging
│ ├── settings.ts # Settings reader (permissions + sandbox config)
│ ├── platform.ts # Platform detection
│ └── exec.ts # Command execution utilities
└── sandbox/ # Sandbox implementation
├── sandbox-manager.ts # Main sandbox manager
├── sandbox-schemas.ts # Zod schemas for validation
├── sandbox-violation-store.ts # Violation tracking
├── sandbox-utils.ts # Shared sandbox utilities
├── http-proxy.ts # HTTP/HTTPS proxy for network filtering
├── socks-proxy.ts # SOCKS5 proxy for network filtering
├── linux-sandbox-utils.ts # Linux bubblewrap sandboxing
├── macos-sandbox-utils.ts # macOS sandbox-exec sandboxing
└── windows-sandbox-utils.ts # Windows srt-win sandboxing
使用方法
作为 CLI 工具
srt 命令(Anthropic Sandbox Runtime)将任何命令包装在安全边界内:
# Run a command in the sandbox
srt echo "hello world"
# With debug logging
srt --debug curl https://example.com
# Specify custom settings file
srt --settings /path/to/srt-settings.json npm install
设置文件是可选的——如果 ~/.srt-settings.json 处没有文件,srt 会使用内置默认值运行:无网络访问、不在默认写入路径之外写入、读取不受限制。如果设置文件_确实_存在但为空、无法读取或未通过校验,则视为错误:srt 会报告该错误并退出,而不是回退到那些默认值,因为那些默认值是一种不同的配置,而非更弱的配置——回退会丢弃该文件中的 denyRead、allowRead 和凭据规则以及它所述的其他所有内容。通过 --settings 指定的文件也是如此,该文件也必须存在。
在命令运行期间更新配置:--control-fd
--control-fd <fd> 从调用方已打开的描述符读取配置更新,每行一个 JSON 对象,其结构与设置文件相同。每一行都会替换整个配置,但只有网络列表(allowedDomains / deniedDomains)会改变已在运行的内容:代理会按请求查询它们。文件系统规则在包装时被编译进沙箱,因此更改它们的行对当前运行中的任何内容都不适用。
# fd 3 is the read end of a pipe the caller writes lines to
srt --control-fd 3 -- npm test
- 描述符必须是3 或以上的整数且可读——
0-2是标准流。当 srt 无法读取给定的描述符时,它会报错退出而不是运行命令,因此死通道永远不会被误认为活通道。在传递任何更新之前就死掉的通道会连带终止命令;在传递更新之后才死掉的通道会报告这一情况,并让命令在最后应用的配置下继续运行。 - 不是有效配置的行会在 stderr 上报告并被丢弃;之前的配置继续生效。
- srt 随被包装的命令一起退出,不会等待写入方关闭描述符。输入结束也不是错误:命令会在最后应用的配置下继续运行。
- 给 srt 一个专用的、只读的端。srt 会把管道或套接字置于非阻塞模式,而该标志存在于打开的文件描述上,因此任何其他持有同一描述的东西——shell 的
exec 3<fifo、父进程继续使用的描述符的pass_fds——从此以后它自己的阻塞读取都会得到EAGAIN。 - 在 macOS 和 Linux 上,沙箱化的命令不会获得该描述符:srt 会把那个槽位指向
/dev/null供命令使用,因此沙箱内的任何东西都无法读取更新或写入自己的配置。
作为库使用
import {
SandboxManager,
type SandboxRuntimeConfig,
} from '@anthropic-ai/sandbox-runtime'
import { spawn } from 'child_process'
// Define your sandbox configuration
const config: SandboxRuntimeConfig = {
network: {
allowedDomains: ['example.com', 'api.github.com'],
deniedDomains: [],
},
filesystem: {
denyRead: ['~/.ssh'],
allowWrite: ['.', '/tmp'],
denyWrite: ['.env'],
},
}
// Initialize the sandbox (starts proxy servers, etc.)
await SandboxManager.initialize(config)
// Wrap a command with sandbox restrictions
const sandboxedCommand = await SandboxManager.wrapWithSandbox(
'curl https://example.com',
)
// Execute the sandboxed command
const child = spawn(sandboxedCommand, { shell: true, stdio: 'inherit' })
// Handle exit and cleanup after child process completes
child.on('exit', async code => {
console.log(`Command exited with code ${code}`)
// Cleanup when done (optional, happens automatically on process exit)
await SandboxManager.reset()
})
违规归因(commandId / commandText)。 在包装命令运行期间观察到的违规(seatbelt 日志行、seccomp 事件、代理拒绝)会存储在一个归因键下,而 annotateStderrWithSandboxFailures(key, stderr) / getViolationsForCommand(key) 会通过同一个键来查找它们。默认情况下,该键就是被包装的字符串本身。传入一个不透明的每次调用 commandId(例如工具使用 id)来改为以它作为键——推荐这样做:键会比较其前 100 个字符,因此共享前缀的长命令否则会交叉归因,并且同一文本的重新运行会继承先前运行的事件。如果你_执行_的字符串不是该调用_所代表_的命令(例如你包装了一个组装而成的 source <snapshot> && eval '<cmd>'),还要传入 commandText: '<cmd>':它是 ignoreViolations 命令模式所匹配的对象,也是每个违规报告为其 command 的内容。你传给 wrapWithSandbox 的 commandId 必须与你随后传给 annotateStderrWithSandboxFailures / getViolationsForCommand 的是同一个非空字符串;空字符串会被视为完全没有 commandId,因此键就是该命令。
只有键会被截断到 100 个字符。自 v0.0.76 起,所报告的 command——以及 ignoreViolations 命令模式所匹配的文本——对于未带 commandId 包装的调用而言是整个命令,而不是其前 100 个字符;因此一个模式只能比以前抑制得更多,绝不会更少。一个本进程的任何调用都未注册的归因键(这些载体可从沙箱内部写入)会被以净化后的形式报告,并截断到相同的键长度。