首页
/ Gemini CLI Policy Engine 实战指南:TOML 规则、优先级分层与工具调用治理的源码级解析

Gemini CLI Policy Engine 实战指南:TOML 规则、优先级分层与工具调用治理的源码级解析

2026-09-06 13:07:29作者:丁柯新Fawn

Gemini CLI 内置的策略引擎(Policy Engine)为每一次工具调用提供了细粒度的准入控制:允许、拒绝或请求用户确认。本文以官方文档 docs/reference/policy-engine.md 为骨架,完整继承其中的快速上手步骤、TOML 规则模式、优先级计算公式与配置路径说明,并结合 packages/core/src/policy/policy-engine.tspackages/core/src/policy/config.ts 等源码实现,深入剖析规则匹配、Shell 命令二次校验与安全护栏的底层机制。读完后,你将能够编写生产级策略文件、理解 Admin/User 分层覆盖原理,并掌握 MCP 工具、子代理与 Shell 重定向的治理技巧。

策略引擎是什么

策略引擎在模型发起工具调用时介入:它把所有规则按优先级排序,逐一比对工具调用,最高优先级的命中规则决定最终决策。每条规则由三部分组成:

  • 条件(Conditions):工具名、参数、MCP 服务器名、子代理名、注解、交互模式等匹配标准;
  • 决策(Decision)allowdenyask_user
  • 优先级(Priority):0–999 的整数,数字越大越优先。

三种决策在源码中由枚举定义,见 types.ts

export enum PolicyDecision {
  ALLOW = 'allow',
  DENY = 'deny',
  ASK_USER = 'ask_user',
}

它们的行为语义为:

  • allow:工具调用无需任何交互直接执行;
  • deny:调用被拦截且不执行。对于全局规则(不带 argsPattern,被拒绝的工具会从模型可见的工具列表中完全剔除——模型根本看不到该工具,既更安全也节省上下文窗口。这一行为由 policy-engine.ts 中的 getExcludedTools() 实现:它会以“剥离 argsPattern”的方式静态评估每条规则,只有无条件的 deny 才会导致工具被排除;
  • ask_user:向用户发起确认提示。在非交互(headless)模式下,ask_user 一律按 deny 处理。这有双重保障:默认决策在 config.ts 中按交互模式区分(defaultDecision: interactive ? ASK_USER : DENY),且内置策略 non-interactive.toml 还显式拒绝 ask_user 工具本身(priority = 999interactive = false)。

建议:排除工具的推荐方式是 deny 决策的策略规则。settings.json 中遗留的 tools.exclude 配置已不推荐,正在被策略引擎取代。

快速上手

创建第一条策略只需三步:

1. 创建策略目录(若不存在):

macOS / Linux:

mkdir -p ~/.gemini/policies

Windows (PowerShell):

New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.gemini\policies"

2. 创建策略文件(例如 ~/.gemini/policies/my-rules.toml)。目录下任意以 .toml 结尾的文件都会被加载并合并:

[[rule]]
toolName = "run_shell_command"
commandPrefix = "rm -rf"
decision = "deny"
priority = 100

3. 触发验证:让 Gemini CLI 执行 rm -rf / 之类的命令,该调用会被策略自动拦截。

从源码看,~/.gemini/policies 这一路径由 storage.tsgetUserPoliciesDir() 给出;toml-loader.tsreadPolicyFiles() 负责扫描目录中的全部 .toml 文件,目录不存在时静默返回空列表。

TOML 规则模式(完整字段)

以下是策略规则支持的全部字段,注释结合文档与源码校验逻辑补充:

[[rule]]
# 工具名(唯一名或名称数组)。
toolName = "run_shell_command"

# (可选)子代理名。提供后,规则仅对该子代理发起的调用生效。
subagent = "codebase_investigator"

# (可选)MCP 服务器名。与 toolName 组合后内部会拼成
# "mcp_mcpName_toolName" 形式的完全限定名(FQN)。
mcpName = "my-custom-server"

# (可选)工具注解元数据。规则中给出的所有键值对
# 都必须存在于工具的注解中才匹配。
toolAnnotations = { readOnlyHint = true }

# (可选)匹配工具参数的正则。参数会先序列化为稳定 JSON 字符串。
argsPattern = '"command":"(git|npm)'

# (可选)字符串或字符串数组,Shell 命令必须以其开头。
# 是 toolName = "run_shell_command" + argsPattern 的语法糖。
commandPrefix = "git"

# (可选)匹配整条 Shell 命令的正则,同样是 run_shell_command 的语法糖。
# 注意:它实际测试的是参数的 JSON 表示(如 {"command":"<你的命令>"}),
# 由于前置了 "command":",等价于从命令开头匹配;
# 因此 `^`/`$` 这类锚点作用于整个 JSON 串,通常应避免使用 ^。
# 同一条规则中不能同时使用 commandPrefix 和 commandRegex。
commandRegex = "git (commit|push)"

# 决策,必须是 "allow"、"deny" 或 "ask_user"。
decision = "ask_user"

# 规则优先级,取值 0 到 999(超出会被 schema 拒绝,防止"层级溢出")。
priority = 10

# (可选)本规则拒绝调用时展示的自定义消息,
# 会同时返回给模型与用户,用于解释拒绝原因。
denyMessage = "Deletion is permanent"

# (可选)规则生效的审批模式数组。
# 省略或为空表示所有模式都生效。
modes = ["default", "autoEdit", "yolo"]

# (可选)布尔值,限制规则仅在交互式(true)或非交互式(false)环境生效。
# 省略则两种环境均生效。
interactive = true

# (可选)为 true 时允许该规则匹配的 Shell 命令使用
# 重定向操作符(>, >>, <, <<, <<<)。默认情况下即使规则匹配,
# 策略引擎检测到重定向也会降级为请求确认。
# 该权限是细粒度的:只对定义它的这条规则生效;
# 链式命令(如 cmd1 > file && cmd2)中每条子命令
# 各自的规则都必须允许重定向。
allowRedirection = true

字段在加载时经过严格的 Zod 校验(见 toml-loader.ts),几个关键约束值得注意:

  1. priority 强制 0–999:源码注释明确写道 “Priorities >= 1000 would jump to the next tier”,即防止 priority = 1000tier + priority/1000 的计算结果推到上一层级,破坏 Admin > User > … > Default 的层级关系;
  2. commandPrefix / commandRegex 互斥:二者不能与 argsPattern 同用,也不能彼此同用,且只能配合 toolName = "run_shell_command"(字符串形式,不能是数组),否则加载时报错,见 validateShellCommandSyntax
  3. 正则安全:所有正则都会先编译并经过 isSafeRegExp 检查,嵌套量词等可能引发 ReDoS 的模式会被拒绝并给出提示(toml-loader.ts);
  4. 工具名拼写检查:无法识别的 toolName 若与某个内建工具名的 Levenshtein 距离不超过 3,会给出拼写建议警告;而 __ 双下划线的旧 MCP 命名语法已严格弃用(validateToolName)。

使用数组(lists)

toolNamecommandPrefix 都支持字符串数组,一条规则即可覆盖多个对象:

# 同一条规则同时作用于 write_file 与 replace
[[rule]]
toolName = ["write_file", "replace"]
decision = "ask_user"
priority = 10

在加载阶段,数组会被展开为多条独立规则(每个工具名一条,见 toml-loader.tsflatMap 转换)。

run_shell_command 专用语法

为简化 Shell 策略,可以用 commandPrefixcommandRegex 替代繁琐的 argsPattern。需要强调:这两个字段是策略规则的简写,并非 run_shell_command 工具本身的参数(工具自身参数参见 Shell toolTools reference)。

  • commandPrefixcommand 参数以给定字符串开头即命中;
  • commandRegexcommand 参数匹配给定正则即命中。

示例:执行任何 git 命令前请求用户确认:

[[rule]]
toolName = "run_shell_command"
commandPrefix = "git"
decision = "ask_user"
priority = 100

commandPrefix 在内部通过 buildArgsPatterns 转换为对 "command":"git... 的锚定正则,因此对命令开头做了可靠匹配。

MCP 工具专用语法

mcpName 是治理 MCP(Model Context Protocol)工具的推荐方式,比手写 FQN 或通配符更健壮:

警告:MCP 服务器名称中不要使用下划线(用 my-server 而不是 my_server)。策略解析器在 mcp_ 前缀后按第一个下划线拆分完全限定名 mcp_server_tool;若服务器名含下划线,解析会误判服务器身份,导致通配规则与安全策略静默失效

三种典型用法:

1. 针对某服务器的某个工具——toolName 必须写简单名(如 search),而非 FQN(如 mcp_server_search):

# 允许 my-jira-server 上的 search 工具
[[rule]]
mcpName = "my-jira-server"
toolName = "search"
decision = "allow"
priority = 200

2. 针对某服务器的全部工具(对所有决策类型生效):

# 拒绝 untrusted-server 提供的所有工具
[[rule]]
mcpName = "untrusted-server"
decision = "deny"
priority = 500
denyMessage = "This server is not trusted by the admin."

3. 针对所有 MCP 服务器——mcpName = "*" 可设置类别级默认值:

# 任何 MCP 服务器的任何工具调用都请求确认
[[rule]]
toolName = "*"
mcpName = "*"
decision = "ask_user"
priority = 10

从源码结构看,mcpName 在规则匹配时走元数据通道:policy-engine.tsrule.mcpName 会与工具调用注入的 _serverName 注解做严格相等比较(* 则要求是任意 MCP 工具),不依赖字符串解析,这正是它比通配 FQN 更稳的原因;同时加载器会把 mcpName + toolName 拼装成 FQN(formatMcpToolName)以兼容旧的字符串匹配路径。

子代理专用语法

把子代理名当作 toolName 即可用标准策略规则治理子代理。当主代理通过统一的 invoke_agent 工具调用子代理时,策略引擎会把参数中的 agent_name 作为虚拟工具别名注入匹配流程,见 policy-engine.ts

# 拒绝 codebase_investigator 子代理
[[rule]]
toolName = "codebase_investigator"
decision = "deny"
priority = 500
denyMessage = "Deep codebase analysis is restricted for this session."

两点补充:

  • 向后兼容:针对历史 1:1 子代理工具名编写的旧规则依然透明匹配;
  • 区分“谁调用”:如果你想按“是哪个子代理在调用某工具”来写规则,应使用 subagent 字段(见完整模式一节),ruleMatches() 会将其与当前调用上下文中的子代理名做比较(policy-engine.ts)。

条件详解

工具名与通配符

toolName 必须与正在调用的工具名匹配,完整内建工具清单见 Tools reference。支持的通配符:

模式 语义
* 匹配任何工具(内建或 MCP)
mcp_server_* 匹配特定 MCP 服务器的任意工具
mcp_*_toolName 匹配所有 MCP 服务器上的某个具体工具名
mcp_* 匹配任意 MCP 服务器上的任意工具

建议:FQN 通配符虽受支持,但针对 MCP 工具的策略推荐使用 mcpName 字段。

匹配实现见 matchesWildcardmcp_server_* 会先验证服务器名一致、再验证工具名以 mcp_server_ 前缀开头,避免把非 MCP 工具误纳入。

参数模式(argsPattern)

指定 argsPattern 时,工具参数会先转换为稳定 JSON 字符串(键排序序列化,由 stable-stringify.ts 实现),再与正则测试;不匹配则规则不适用。各工具可用的参数键见 Tools referenceParameters 部分。注意 check() 会先探测是否存在任何 argsPattern 规则,只有需要时才做序列化,避免无谓开销。

执行环境(interactive)

interactive 指定后,规则仅在 CLI 运行环境与布尔值一致时生效:true 仅交互模式,false 仅非交互(headless)模式;省略则两者都适用。这一字段在内置策略中广泛使用,例如 discovered.toml 对动态发现工具“交互式默认询问、非交互式直接拒绝”。

优先级系统与分层(Tiers)

警告Workspace 层(项目级策略)目前不可用——在工作区的 .gemini/policies 目录中定义策略不会生效(已知问题 #18186)。请使用 User 或 Admin 策略替代。

当多条规则同时命中一次工具调用时,最高优先级规则胜出。为形成清晰层级,策略被组织成多个 Tier,每个 Tier 有一个基数参与最终优先级计算:

Tier Base 说明
Default 1 随 Gemini CLI 分发的内置策略
Extension 2 扩展(extension)定义的策略
Workspace 3 (当前禁用)工作区配置目录中的策略
User 4 用户自定义策略
Admin 5 管理员策略(如企业环境)

这些基数在 config.ts 中定义为常量(DEFAULT_POLICY_TIER = 1ADMIN_POLICY_TIER = 5)。TOML 中你写的是 0–999 的优先级,引擎按公式变换:

final_priority = tier_base + (toml_priority / 1000)

变换逻辑见 transformPriority。该体系保证了:

  • Admin 策略永远覆盖 User、Workspace、Default;
  • User 覆盖 Workspace 与 Default;
  • Workspace 覆盖 Default;
  • 同一 Tier 内部仍可用 0–999 精细排序。

换算示例(按公式计算):

  • Default 策略 TOML 中 priority = 501.050
  • User 策略 TOML 中 priority = 1004.100
  • Admin 策略 TOML 中 priority = 205.020

引擎构造时将所有规则按优先级降序排序(policy-engine.ts),运行时线性扫描即得“最高优先级命中规则”。除了 TOML 文件,还有若干设置级动态规则占据 User 层的小数段(见 config.ts):

动态规则 优先级 来源
MCP 排除列表 4.9 settings.mcp.excluded(持久服务器封禁)
--exclude-tools 4.4 命令行标志(显式临时阻断)
confirmationRequired 4.35 设置(覆盖 allowed/core)
--allowed-tools 4.3 命令行标志(显式临时放行)
tools.core 4.25 核心工具白名单
MCP 受信服务器 4.2 mcpServers.<name>.trust = true
MCP 允许列表 4.1 settings.mcp.allowed

这意味着“Always Allow(总是允许)”这类交互确认产生的规则(小数段 0.95,见 types.tsALWAYS_ALLOW_PRIORITY_FRACTION = 950)与上述设置规则同层竞争,层级关系有严格保障。

审批模式(Approval Modes)

策略引擎可依据 CLI 运行模式套用不同规则集。规则可通过 modes 字段绑定到一个或多个模式,仅当 CLI 处于其中之一时激活;未指定 modes 的规则始终激活。四种模式定义于 types.ts

  • default:标准交互模式,大多数写工具需要确认;
  • autoEdit:面向自动化代码编辑,部分写工具可自动批准(例如内置 write.tomlreplaceautoEdit 下以 priority = 15 放行,并挂接 allowed-path 安全检查器限制路径范围);
  • plan:严格的只读研究/设计模式,可参考 Customizing Plan Mode Policies
  • yolo:所有工具自动批准(极度谨慎使用)。内置 yolo.toml 用一条 priority = 998allow * 规则兜底,同时保留 ask_user 工具在 YOLO 下仍可提问(priority = 999),并禁止 Plan 模式切换以维持状态一致性。

为保持 Plan Mode 作为安全研究环境的完整性,持久化工具批准是上下文感知的。当你选择“Allow for all future sessions”时,引擎会显式包含当前模式及所有更宽松模式(宽松度顺序为 plan < default < autoEdit < yolo,见 MODES_BY_PERMISSIVENESS):

  • plan 模式下的批准:代表有意全局信任,生成的规则显式包含全部四个模式;
  • 其他模式下的批准:只作用于当前模式及更宽松者。例如 default 下批准 → 生效于 defaultautoEdityoloautoEdit 下批准 → 生效于 autoEdityoloyolo 下批准 → 仅 yolo。信任由此正确流向更宽松的环境,而 plan 等受限模式保持安全。

持久化写入由 createPolicyUpdater 完成:交互式确认事件经消息总线到达后,规则先加入内存引擎,再按需追加到自动保存的 TOML 文件(用户级或工作区级路径),写入采用“临时文件 + 原子 rename”,遇到语法损坏的旧文件会备份为 .bak 后重建。

规则匹配与 Shell 二次校验

调用发生时的匹配流程是:引擎按优先级从高到低遍历所有激活规则,第一条同时满足全部条件的规则决定结果——工具名(含通配与 MCP FQN 双向匹配)、argsPattern(对稳定 JSON 测试)、mcpNamesubagenttoolAnnotations(全部键值对需命中)、modesinteractive 逐一校验,见 ruleMatches

没有任何规则命中时的回退逻辑(check()):

  1. YOLO 模式直接 ALLOW
  2. 否则使用默认决策(交互 = ASK_USER,非交互 = DENY);
  3. 若目标是 Shell 工具,还会叠加启发式调整applyShellHeuristics):
    • 不受信工作区执行 git 命令 → 强制 ASK_USERcontainsGitCommand 能识别 sudo gitVAR=x git、管道前后等各种前置形式);
    • 沙箱管理器判定为危险命令 → 强制 ASK_USER(YOLO 模式除外);
    • 判定为已知安全命令(如 lscat)且原决策为 ASK_USER → 升级为 ALLOW

run_shell_command 的匹配只是第一层:checkShellCommand 会进一步把命令拆分为子命令&&||、管道、bash -c 包装等),对每段子命令递归调用 check(),任一子命令被 DENY 则整体拒绝,出现 ASK_USER 则整体降级为 ASK_USER。此外,重定向检测>>>< 等)会把 ALLOW 降级为 ASK_USER——除非该规则显式设置了 allowRedirection;链式命令中每个子命令各自的规则都必须放行重定向。YOLO / autoEdit 模式下不做该降级。

配置与策略文件位置

策略以 .toml 文件定义,CLI 从 Default、User、Admin(以及 Extension、Workspace)目录加载。位置总览:

Tier 类型 位置
User 自定义 ~/.gemini/policies/*.toml
Workspace 自定义 (已禁用) $WORKSPACE_ROOT/.gemini/policies/*.toml
Admin 系统 见下表(按操作系统)

系统级策略(Admin)

管理员可在系统标准位置或补充路径下发 Admin 层策略,覆盖所有用户与默认设置。标准路径由 getSystemPoliciesDir 按平台决定:

操作系统 策略目录
Linux /etc/gemini-cli/policies
macOS /Library/Application Support/GeminiCli/policies
Windows C:\ProgramData\gemini-cli\policies

补充 Admin 策略可通过两种方式指定(对应 CLI 参数定义 与设置项 adminPolicyPaths):

  • --admin-policy 命令行标志;
  • 系统设置文件中的 adminPolicyPaths

补充路径与标准位置的策略同属 Admin 层(Base 5)

安全护栏:若标准系统位置中已存在任意 .toml 策略文件,补充路径会被忽略createPolicyEngineConfig 中检测并告警 “Ignoring --admin-policy…”)。这防止在中央系统策略已建立后,通过标志实现旁路覆盖。

另外,--policy 标志 / policyPaths 设置可加载替代用户策略目录的额外策略文件,属于 User 层(见 getPolicyDirectories 的目录组装顺序:Admin → User → Workspace → Default)。

安全要求(防止提权)

为防止提权,CLI 对标准系统策略目录执行严格安全检查,检查失败时该目录中的策略被整体忽略filterSecurePolicyDirectories 调用 isDirectorySecure):

  • Linux / macOS:目录必须归 root(UID 0)所有,且组与其他用户不可写(例如 chmod 755);
  • Windows:目录必须位于 C:\ProgramData 下,标准用户(UsersEveryone)不得拥有 WriteModifyFull Control 权限。若看到安全警告,请在文件夹属性中移除非管理员分组的写权限,必要时在高级安全设置中“禁用继承”。

注意:通过 --admin-policy / adminPolicyPaths 提供的补充 Admin 策略不受上述严格属主检查约束——因为它们由用户/管理员在当前执行上下文中显式提供。

与 settings.json 的配合

除 TOML 外,设置项会在构建引擎配置时(createPolicyEngineConfig)自动编译为动态规则,常用项包括:

  • mcp.excluded:封禁服务器,生成高优先级(4.9)deny 规则;"*" 封禁全部 MCP;
  • tools.exclude / tools.core:工具排除 / 核心白名单。tools.core 除生成 allow 规则外,还会补一条 toolName = "*"deny 兜底规则(优先级略低于白名单),实现“白名单之外全拒”;
  • tools.allowed / tools.confirmationRequired:临时放行 / 强制确认,支持 shell(cmd) 这类带参数收窄的旧格式(收窄的 Shell 工具会附加低 0.01 优先级的 deny 兜底,防止未收窄的调用被放行);
  • mcpServers.<name>.trust = true:受信 MCP 服务器,全部工具自动允许(仅限非 plan 模式);
  • mcp.autoAllowInHeadless:headless 模式下自动放行已配置的 MCP 服务器工具,无需逐条写入 mcp.allowed

扩展贡献的策略则有额外的安全过滤:loadExtensionPolicies 会丢弃扩展提交的 ALLOW 规则与任何涉及 YOLO 模式的规则——扩展只能收紧、不能放松策略。

内置默认策略

Gemini CLI 自带一组默认策略,位于 packages/core/src/policy/policies/,提供开箱即用的安全基线:

文件 作用
read-only.toml 只读工具(read_fileglobgrep_searchlist_directory 等)allow,priority 50
write.toml 写工具(write_filereplacerun_shell_command)默认 ask_user,priority 10;autoEdit 模式下 replace 以 priority 15 放行并挂接 allowed-path 检查器
agents.toml invoke_agent 默认 allow(priority 50),子代理自管破坏性操作确认
non-interactive.toml headless 下拒绝 ask_user(priority 999)
yolo.toml YOLO 模式 allow *(priority 998)+ 保留 ask_user 提问能力
plan.toml Plan 模式的状态转移治理:plan 中禁止再次进入、exit_plan_mode 需确认等
discovered.toml 动态发现工具交互式 ask_user、非交互式 deny
conseca.toml 为所有工具挂接 conseca 进程内安全检查器

概括地说:只读工具默认允许;代理委托默认 ask_user(便于远程代理请求确认,本地子代理动作静默执行并逐条校验);写工具默认 ask_user;YOLO 模式由高优先级规则放行一切;autoEdit 模式允许特定写操作免确认。

内置策略文件头部统一维护了优先级注释,与 config.ts 中的说明一致:TOML 优先级 10(写工具询问)、15(autoEdit 覆盖)、40(Plan 模式兜底拒绝)、50(只读工具)、70(模式切换覆盖)、998/999(YOLO)等关键档位,可作为编写自定义策略时的参照基线。

实践建议

  1. deny 排除工具,而非已弃用的 tools.exclude;全局 deny 会让工具从模型视野中消失,节省上下文;
  2. MCP 策略一律用 mcpName,服务器名避免下划线,防止 FQN 解析错位;
  3. Shell 策略优先 commandPrefix/commandRegex,可读性远好于手写 argsPattern;注意 commandRegex 实际匹配的是 JSON 串,避免 ^ 锚点;
  4. 警惕重定向allow 规则默认不含重定向放行,确有需要时逐规则设置 allowRedirection
  5. 企业部署用 Admin 层:标准系统目录 + 严格属主检查 + 补充路径安全护栏,形成完整的策略下发链路;同时注意 Workspace 层当前不可用,项目级策略请放到 User 层;
  6. 加载失败不会静默:TOML 语法错误、schema 违规、不安全正则都会以 [TIER] Policy file warning/error in <file> 的形式反馈到 UI(formatPolicyError),可用 policy-engine.test.tstoml-loader.test.ts 等测试文件理解各类边界的预期行为。
登录后查看全文
热门项目推荐
相关项目推荐