脚本执行与沙箱设计
让 AI 执行系统命令是 MCP 中风险最高的能力。本节介绍如何安全地给 AI 脚本执行权限:从最简单的受限命令列表,到完整的沙箱容器方案。
脚本执行 MCP 的风险等级
graph LR
A[脚本执行能力] --> B["低风险:
只允许预定义命令
(只读操作)"] A --> C["中风险:
允许运行用户脚本
(限制目录)"] A --> D["高风险:
完整 shell 访问
(生产系统禁用)"] B --> B1["适合场景:
数据分析、文件处理"] C --> C1["适合场景:
开发辅助、自动化测试"] D --> D1["适合场景:
本地开发环境
(需要高度信任 LLM)"] style D fill:#E74C3C,color:#fff style B fill:#27AE60,color:#fff style C fill:#F39C12,color:#fff
只允许预定义命令
(只读操作)"] A --> C["中风险:
允许运行用户脚本
(限制目录)"] A --> D["高风险:
完整 shell 访问
(生产系统禁用)"] B --> B1["适合场景:
数据分析、文件处理"] C --> C1["适合场景:
开发辅助、自动化测试"] D --> D1["适合场景:
本地开发环境
(需要高度信任 LLM)"] style D fill:#E74C3C,color:#fff style B fill:#27AE60,color:#fff style C fill:#F39C12,color:#fff
方案一:白名单命令 MCP(最安全)
自建一个只暴露特定命令的 MCP Server,是最安全的脚本执行方案:
# allowed_commands_server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
import subprocess
import shlex
app = Server("allowed-commands")
# 白名单:只允许这些命令
ALLOWED_COMMANDS = {
"python3": ["/usr/bin/python3"],
"jq": ["/usr/local/bin/jq"],
"csvkit": ["/usr/local/bin/csvstat"],
"wc": ["/usr/bin/wc"],
"sort": ["/usr/bin/sort"],
"uniq": ["/usr/bin/uniq"],
}
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="run_allowed_command",
description=f"运行预批准的命令。可用命令: {', '.join(ALLOWED_COMMANDS.keys())}",
inputSchema={
"type": "object",
"properties": {
"command": {
"type": "string",
"enum": list(ALLOWED_COMMANDS.keys()),
"description": "要执行的命令名"
},
"args": {
"type": "array",
"items": {"type": "string"},
"description": "命令参数列表"
}
},
"required": ["command"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name != "run_allowed_command":
raise ValueError(f"未知工具: {name}")
cmd_name = arguments["command"]
args = arguments.get("args", [])
if cmd_name not in ALLOWED_COMMANDS:
return [types.TextContent(type="text", text=f"错误:命令 '{cmd_name}' 不在白名单中")]
# 使用完整路径,防止 PATH 劫持
full_cmd = ALLOWED_COMMANDS[cmd_name] + args
try:
result = subprocess.run(
full_cmd,
capture_output=True,
text=True,
timeout=30, # 30秒超时
cwd="/Users/yourname/ai-workspace/workspace" # 限制工作目录
)
output = result.stdout if result.returncode == 0 else result.stderr
return [types.TextContent(type="text", text=output[:10000])] # 限制输出大小
except subprocess.TimeoutExpired:
return [types.TextContent(type="text", text="错误:命令执行超时(30秒)")]
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__":
import asyncio
asyncio.run(main())
配置:
{
"mcpServers": {
"allowed-commands": {
"command": "python3",
"args": ["/Users/yourname/mcp-servers/allowed_commands_server.py"]
}
}
}
方案二:Docker 沙箱执行
对于需要运行任意代码的场景,Docker 提供了最强的隔离:
# docker_sandbox_server.py
import docker
from mcp.server import Server
from mcp import types
app = Server("docker-sandbox")
docker_client = docker.from_env()
@app.list_tools()
async def list_tools():
return [
types.Tool(
name="run_python_sandbox",
description="在 Docker 沙箱中运行 Python 代码",
inputSchema={
"type": "object",
"properties": {
"code": {"type": "string", "description": "Python 代码"},
"timeout": {"type": "integer", "default": 30}
},
"required": ["code"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
code = arguments["code"]
timeout = min(arguments.get("timeout", 30), 60) # 最大60秒
try:
container = docker_client.containers.run(
"python:3.11-slim", # 最小化 Python 镜像
["python3", "-c", code],
mem_limit="256m", # 内存限制
cpu_period=100000,
cpu_quota=50000, # CPU 限制:50%
network_mode="none", # 禁用网络访问
read_only=True, # 只读文件系统
remove=True, # 运行后自动删除
timeout=timeout
)
return [types.TextContent(type="text", text=container.decode())]
except docker.errors.ContainerError as e:
return [types.TextContent(type="text", text=f"错误:{e.stderr.decode()}")]
Docker 沙箱的关键安全参数:
| 参数 | 值 | 说明 |
|---|---|---|
network_mode | "none" | 禁止网络访问,防止数据外泄 |
mem_limit | "256m" | 防止内存耗尽攻击 |
read_only | True | 防止修改容器内文件系统 |
remove | True | 运行后自动删除,不留痕迹 |
timeout | 30-60 | 防止无限循环 |
方案三:使用现成的 Code Execution MCP
如果不想自己实现沙箱,几个现成方案可以直接使用:
# E2B Code Interpreter MCP(推荐,云端沙箱)
npm install -g @e2b/mcp-server
# 配置(需要 E2B API key)
{
"mcpServers": {
"e2b": {
"command": "npx",
"args": ["-y", "@e2b/mcp-server"],
"env": {
"E2B_API_KEY": "your-e2b-key"
}
}
}
}
E2B 在云端运行代码,本地没有任何副作用,适合需要 AI 执行复杂数据分析的场景。
各方案对比
| 方案 | 隔离性 | 灵活性 | 复杂度 | 成本 |
|---|---|---|---|---|
| 白名单命令 | ⭐⭐⭐ | ⭐ | ⭐ | 免费 |
| Docker 沙箱 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | 免费 |
| E2B 云沙箱 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ | 付费 |
| 原生 shell MCP | ❌ | ⭐⭐⭐⭐⭐ | ⭐ | 免费 |
本节执行清单
- [ ] 根据使用场景选择合适的方案(白名单/Docker/E2B)
- [ ] 白名单方案:明确列出允许的命令,使用完整路径
- [ ] Docker 方案:设置内存、CPU、网络和文件系统限制
- [ ] 所有方案:设置执行超时(30-60 秒),防止无限运行
- [ ] 生产环境:不使用原生 shell MCP,选择有隔离的方案
下一节:本地数据处理工作流实战