沙箱与策略引擎
Gemini CLI — 沙箱与策略引擎
学习目标
- 理解 Policy Engine 的三层决策架构(规则 → 安全检查器 → 钩子检查器)
- 掌握 Shell 命令解析和路径提取的实现
- 分析文件夹信任机制和通配符策略规则
前置知识
本章涉及安全沙箱的通用原理,建议先阅读:
下文假设你已理解多层防御的概念,直接聚焦 Gemini CLI 的具体实现。
项目实践
PolicyDecision 三值
Policy Engine 对每个工具调用返回三种决策之一:
| 决策 | 含义 | 后续行为 |
|---|---|---|
| ALLOW | 允许执行 | Scheduler 将工具标记为 Scheduled |
| DENY | 拒绝执行 | Scheduler 创建 ErroredToolCall |
| ASK_USER | 需要用户确认 | 等待用户同意或拒绝 |
三层检查器架构
PolicyEngine.check(toolCall): ├── 1. PolicyRule(规则匹配) │ ├── 工具名称匹配(精确 + 通配符) │ ├── 审批模式过滤 │ └── Shell 命令解析 + 路径匹配 ├── 2. SafetyCheckerRule(安全检查器) │ ├── 动态安全评估 │ └── 上下文依赖决策 └── 3. HookCheckerRule(钩子检查器) ├── 运行时动态决策 └── 可扩展策略决策合并:如果多个规则匹配,取最严格的决策(DENY > ASK_USER > ALLOW)。
Shell 命令解析
Shell 工具的策略检查需要解析命令内容:
parseShellCommand(command): 1. shellQuote.parse(command) // 使用 shell-quote 库 2. 提取命令名称(如 cat, grep, rm) 3. 提取文件路径参数 4. 检测重定向(>, >>, |, 2>&1) 5. 剥离外壳包装(如 cd dir && cmd)Shell 工具分类(SHELL_TOOL_NAMES):
shell:执行任意 Shell 命令bash:执行 Bash 脚本- 重定向工具:
write_file等间接通过 Shell 操作文件的工具
重定向检测:
hasRedirection(parsedCommand): return parsedCommand.some(entry => entry.op === 'redirect' || entry.op === 'pipe' )重定向命令即使操作的是安全路径,也可能写入其他位置,因此策略更严格。
通配符策略规则
{ "rules": [ { "name": "read_file", "action": "allow" }, { "name": "mcp_github_*", "action": "allow" }, { "name": "shell", "action": "ask_user" }, { "name": "*", "action": "ask_user" } ]}匹配逻辑:
| 模式 | 匹配范围 |
|---|---|
* | 所有工具 |
mcp_* | 所有 MCP 工具 |
mcp_{server}_* | 指定 MCP 服务器的所有工具 |
| 精确名称 | 单个工具 |
文件夹信任机制
checkPathTrust() 检查路径是否可信:
信任来源优先级: 1. 环境变量: GEMINI_CLI_TRUST_WORKSPACE=true → 全部信任 2. IDE 信任: VS Code 等 IDE 标记为信任 → 信任 3. 配置文件: .gemini/trusted.json → 按配置信任 4. 默认: 不信任TrustLevel 三级:
TRUST_FOLDER:信任当前文件夹及其子目录TRUST_PARENT:信任父文件夹(允许读取但不允许写入)DO_NOT_TRUST:不信任,所有操作需用户确认
信任配置文件(.gemini/trusted.json):
{ "trustedFolders": ["/path/to/project"], "trustLevel": "TRUST_FOLDER"}沙箱管理器
SandboxManager 接口抽象:
interface SandboxManager { prepareCommand(cmd: { command, args, cwd, env }): Promise<PreparedCommand>; isSandboxed(): boolean;}实现类型:
| 实现 | 平台 | 说明 |
|---|---|---|
NoopSandboxManager | 全平台 | 无沙箱,直接执行 |
MacSeatbelt | macOS | 使用 Seatbelt 沙箱策略 |
| Container 沙箱 | Linux | 使用容器隔离(如 gVisor) |
命令准备:
SandboxManager.prepareCommand(): 1. 规范化命令和路径 2. 检查路径是否在允许的范围内 3. 注入沙箱环境变量 4. 返回 { finalCommand, finalArgs, finalEnv, cleanupFunc }陷阱与对策
Shell 命令解析的局限性
问题:shell-quote 库可能无法处理所有 Shell 语法(如进程替换 <()、复杂的引号嵌套)。
对策:
- 对无法解析的命令使用保守策略(ASK_USER 或 DENY)
parseCommandDetails提取已知模式,未知模式降级处理- 策略规则支持按命令名称精确匹配,不依赖完整解析
路径遍历攻击
问题:工具调用中的路径参数可能使用 ../ 遍历到允许目录外。
对策:
isSubpath()验证路径是否在允许的根目录下- 路径规范化(resolve + normalize)消除
../后再检查 - 沙箱管理器在
prepareCommand中再次验证
策略规则的隐性冲突
问题:多条规则可能匹配同一工具调用,决策可能不一致。
对策:
- 决策合并取最严格的结果(DENY > ASK_USER > ALLOW)
- 规则按优先级排序,高优先级先评估
- 调试日志记录所有匹配的规则和最终决策
设计取舍
三层检查器 vs 单一检查器
| 方案 | 优势 | 代价 |
|---|---|---|
| 三层检查器 | 静态规则快速匹配、动态检查器处理上下文、钩子检查器可扩展 | 实现复杂、需要合并多层决策 |
| 单一检查器 | 简单直接 | 难以兼顾性能和灵活性 |
Gemini CLI 的选择:三层检查器。静态规则处理 80% 的常见情况(快速),安全检查器处理 Shell 命令等动态场景,钩子检查器允许运行时扩展策略。
文件级信任 vs 全局信任
| 方案 | 优势 | 代价 |
|---|---|---|
| 文件级信任 | 不同项目不同策略、恶意工作区不影响其他项目 | 配置复杂、用户需要管理信任列表 |
| 全局信任 | 简单、无需配置 | 一个恶意项目可能影响全局 |
Gemini CLI 的选择:文件级信任。安全敏感的工具(如 Shell、文件写入)需要在可信环境中执行,文件夹级信任是最小化的信任范围。