安全设计理念:策略引擎、沙箱机制与安全检查器
前置知识
- ›第 3 篇:工具与调度器
- ›了解安全沙箱的基本概念
- ›熟悉 TOML 配置格式
安全设计理念:策略引擎、沙箱机制与安全检查器
一个能够在本机执行 shell 命令、编辑文件的智能编程助手,必须具备健壮的安全保障。Gemini CLI 为此构建了三层防御体系:基于规则的策略引擎(控制工具能做什么)、平台级沙箱(限制工具在哪里执行)、以及可插拔的安全检查器(引入上下文感知的判断逻辑)。本文将逐层剖析这套架构。
PolicyEngine:基于规则的访问控制
PolicyEngine 是第一道防线。它将工具调用与一组按优先级排序的规则逐一比对,每条规则会产生三种决策之一:ALLOW、DENY 或 ASK_USER。
规则从 TOML 策略文件加载,并按优先级从高到低排序。当工具调用到来时,引擎找到第一条匹配的规则并返回其决策。配置结构如下:
[[rules]]
toolName = "shell"
decision = "allow"
commandPrefix = "git status"
priority = 100
[[rules]]
toolName = "shell"
decision = "deny"
commandPrefix = "git push --force"
priority = 200
[[rules]]
toolName = "mcp_myserver_*"
decision = "ask_user"
priority = 50
入口方法 check() 位于第 488 行,处理以下几类复杂情况:
- MCP 服务器名称解析 — 从工具注解中提取服务器名称,或解析
mcp_serverName_toolName格式的完全限定名称(FQN) - 参数模式匹配 — 使用键名排序后的稳定 JSON 序列化结果来匹配
argsPattern正则表达式 - 工具别名解析 — 通过
getToolAliases()同时检查当前名称和历史遗留名称 - shell 命令特殊处理 — 识别 shell 工具调用,并将其转交给命令级解析逻辑处理
flowchart TD
A[PolicyEngine.check(toolCall)] --> B[Resolve serverName]
B --> C[Compute stringified args]
C --> D{Is shell command?}
D -- Yes --> E[Parse command for<br/>per-command policy]
D -- No --> F[Match against rules]
E --> F
F --> G{First matching rule?}
G -- Found --> H[Return rule.decision]
G -- None --> I{Safety checkers match?}
I -- Yes --> J[Run checker]
I -- No --> K[Return default decision]
J --> L[Return checker result]
ruleMatches() 方法位于第 79 行,支持以下匹配维度:
- 审批模式过滤 — 规则可以指定适用的模式(
DEFAULT、YOLO、AUTO_EDIT、PLAN) - 子智能体作用域 — 规则可以针对特定的子智能体生效
- MCP 服务器匹配 — 通过
mcpName字段或通配符模式进行匹配 - 注解匹配 — 规则可以要求工具具备特定注解
- 交互模式过滤 — 针对管道输入和终端交互分别应用不同规则
针对 shell 命令的参数级策略解析
shell 命令需要特殊对待,因为 git status 和 git push --force 虽然调用的是同一个工具(shell),但风险等级却天差地别。策略引擎会解析 shell 命令,将策略评估细化到参数层面。
当检测到 shell 工具调用时,引擎会:
- 从工具调用中提取
command参数 - 通过
splitCommands()拆分复合命令(如cmd1 && cmd2) - 使用
shell-quote库的shellParse()解析每条子命令 - 将每条子命令与带有
commandPrefix模式的规则进行匹配
这样就能实现精细化控制,例如"允许 npm test,但禁止 npm publish",而无需对所有 shell 命令一刀切地放行或拦截。规则中的 commandPrefix 字段支持单个字符串或数组,可同时匹配多个前缀。
flowchart TD
A["shell tool call:<br/>npm test && npm publish"] --> B[splitCommands]
B --> C["Sub-command 1: npm test"]
B --> D["Sub-command 2: npm publish"]
C --> E{Match rules}
D --> F{Match rules}
E --> G["Rule: allow npm test → ALLOW"]
F --> H["Rule: deny npm publish → DENY"]
G --> I{Both sub-commands}
H --> I
I --> J["Final: DENY<br/>(most restrictive wins)"]
提示: 在为 shell 命令编写 TOML 策略规则时,请优先使用
commandPrefix而非argsPattern进行命令匹配。引擎能够正确处理管道链、&&和||运算符,但对序列化参数字符串直接使用原始正则表达式在面对复杂 shell 语法时会出错。
平台级沙箱机制
沙箱层在操作系统层面运行,限制整个进程可访问的资源范围。SandboxManager 接口定义了统一契约,由各平台提供具体实现。
MacOsSandboxManager 利用 macOS 的沙箱隔离机制(sandbox-exec 系统),限制文件系统访问、网络连接和系统调用。其核心方法包括:
isKnownSafeCommand(args)— 对已知无害命令(如ls、cat)走快速放行通道,同时检查模式配置中的approvedTools列表。isDangerousCommand(args)— 识别明显危险的操作。prepareCommand(req)— 核心方法。负责清理环境变量、根据权限配置(只读模式、工作区写入权限、网络访问权限)构建沙箱配置文件,并用sandbox-exec包装命令。
graph TD
subgraph "Platform Sandbox Managers"
MAC[MacOsSandboxManager<br/>seatbelt profiles]
DOCKER[Docker/Podman<br/>container isolation]
WIN[WindowsSandboxManager<br/>Windows sandbox]
NOOP[NoopSandboxManager<br/>no restrictions]
end
SI[SandboxManager Interface]
SI --> MAC
SI --> DOCKER
SI --> WIN
SI --> NOOP
POLICY[SandboxPolicyManager<br/>persistent permissions]
MAC --> POLICY
DOCKER --> POLICY
macOS 实现还支持虚拟命令转换:__read 会被替换为 /bin/cat,__write 则被替换为 cat > "$1" 管道,通过受控的原语实现沙箱内的文件访问。
在 Linux 上,Docker 或 Podman 容器提供隔离环境。沙箱决策发生在启动序列的早期(如第 1 篇所述):如果启用了沙箱且当前进程不在沙箱中,则进程会将自身重新启动到容器内部。
安全检查器:CheckerRunner 与 Conseca
在基于规则的策略之外,Gemini CLI 还支持可插拔的安全检查器,能够对工具调用作出更细致的判断。CheckerRunner 负责协调两类检查器:
进程内检查器 — 直接在 Node.js 进程中运行。CheckerRegistry 按名称解析检查器,ContextBuilder 组装所需上下文,检查器返回 ALLOW、DENY 或 ASK_USER。
外部检查器 — 作为独立进程运行,具有超时限制(默认 5 秒)。工具调用详情通过 stdin 以 JSON 格式传入,检查器的 stdout 输出通过 Zod schema 解析为结果。
const SafetyCheckResultSchema = z.discriminatedUnion('decision', [
z.object({ decision: z.literal(SafetyCheckDecision.ALLOW), reason: z.string().optional() }),
z.object({ decision: z.literal(SafetyCheckDecision.DENY), reason: z.string().min(1) }),
z.object({ decision: z.literal(SafetyCheckDecision.ASK_USER), reason: z.string().min(1) }),
]);
安全检查器规则与策略规则一同定义在 TOML 配置中。它们有独立的优先级排序和匹配逻辑(包括工具名称通配符和参数模式),产生的 SafetyCheckDecision 值会回流至策略处理流水线。
ConsecaSafetyChecker 是一个进程内实现,通过启发式方法和对话上下文来评估工具调用。其启用标志(enableConseca)来自 Config 配置。
审批模式与确认流程
Gemini CLI 支持四种审批模式,控制工具自动放行的积极程度:
| 模式 | 行为 |
|---|---|
DEFAULT |
所有破坏性操作均需确认 |
YOLO |
自动放行所有操作(仅在受信任环境中使用) |
AUTO_EDIT |
自动放行文件编辑,shell 命令仍需确认 |
PLAN |
只读模式,拦截所有写入和执行操作 |
这些模式通过规则中的 modes 字段与策略规则联动——规则可以指定仅在特定模式下生效。例如,YOLO 模式的规则可以设置 priority: PRIORITY_YOLO_ALLOW_ALL,覆盖所有其他规则。
确认流程整合了所有安全层:
stateDiagram-v2
[*] --> PolicyCheck: Tool call arrives
PolicyCheck --> AutoAllow: ALLOW
PolicyCheck --> AutoDeny: DENY
PolicyCheck --> CheckerPhase: ASK_USER
CheckerPhase --> AutoAllow: Checker says ALLOW
CheckerPhase --> AutoDeny: Checker says DENY
CheckerPhase --> HookPhase: Checker says ASK_USER
HookPhase --> AutoAllow: Hook says proceed
HookPhase --> AutoDeny: Hook says block
HookPhase --> UIConfirmation: Hook says ask user
UIConfirmation --> Executed: User approves
UIConfirmation --> Cancelled: User denies
UIConfirmation --> PolicyUpdate: User says "always allow"
PolicyUpdate --> Executed: Update rules, then execute
AutoAllow --> Executed
AutoDeny --> Cancelled
当用户对某个工具选择"始终允许"时,调度器中的 updatePolicy() 会以适当的精度创建一条新规则。对于 shell 命令,它会捕获 commandPrefix;对于 MCP 工具,则捕获 mcpName。用户还可以选择"始终允许并保存",将规则持久化到磁盘。
提示:
PolicyEngine上的disableAlwaysAllow标志可以阻止"始终允许"规则生效,适用于管理员希望保持集中管控的环境。更多安全配置项请参阅PolicyEngineConfig。
这套安全架构的优势在于其分层设计。策略规则提供粗粒度控制,沙箱约束执行环境,安全检查器引入上下文感知的判断能力。每一层都能独立拦截工具调用,而确认流程则确保在自动化决策不够充分时,用户始终掌握最终控制权。
下一篇文章将探讨构建在这套安全基础之上的可扩展性机制——hooks、skills、MCP 集成以及扩展打包系统。