05. 权限、计划与安全

权限规则与钩子拦截

模式决定 Matcha CLI 问你的频率。规则和钩子为特定工具、路径和命令设下常驻的是 / 问 / 否。

授权顺序

一次工具调用按这个顺序授权:

  1. PreToolUse 钩子。钩子明确 deny 会在任何权限检查之前拦住调用。钩子 allow 不会跳过后面的检查;它只是表示自己不拒绝。
  2. 权限规则(deny > ask > allow),从每个来源合并。deny 胜过允许,也胜过始终批准的正常放行。
  3. 此前卡片记住的授权(remember_tool_approvals 打开时),限定在这个项目。
  4. 内置自动批准(只读工具和只读命令列表)。
  5. 当前模式的询问策略(询问 / 自动 / 始终批准 / dontAsk)。

始终批准在第 2 步之后短路:拒绝规则、钩子和 shell 片段的 ask 规则仍然生效;记住的授权会被跳过。

规则写在哪

规则来自若干文件,合并成一套。谁赢看严重程度,不看来源文件。

范围文件
全局~/.matcha/config.toml
项目(可共享)<project>/.matcha/config.toml(从仓库根到当前工作目录的每一层)
项目(个人)<project>/.claude/settings.local.json
托管/etc/matcha/managed_config.toml~/.matcha/managed_config.toml
CLI--allow RULE--deny RULE(可重复)

没有原生的 config.local.toml。交互里的「始终允许」决定存在仓库外面,按项目保存。

规则语法

原生结构化写法:

[permission]
rules = [
  { action = "allow", tool = "bash", pattern = "git *" },
  { action = "deny",  tool = "bash", pattern = "rm -rf *" },
  { action = "ask",   tool = "edit" },
]

紧凑字符串写法(与 --allow / --deny 以及 Claude 设置同一套语言):

[permission]
allow = ["Bash(git *)", "Grep"]
deny  = ["Bash(rm -rf *)"]

结构化写法里的 tool 名是小写:bashreadeditgrepmcpwebfetchwebsearch。字符串规则用 BashReadEdit / WriteGrep / GlobMCPToolWebFetchWebSearch。单独一个 * 匹配所有工具。

规则在会话开始时读取。改文件后,再开新会话。

这里相关的钩子是 PreToolUse。项目钩子需要目录信任(/hooks-trust--trust)。~/.matcha/hooks/ 里的全局钩子始终受信任。钩子安装和完整生命周期见「扩展 Matcha」。

什么时候用规则和钩子

给每天跑的命令(gitcargo test)写一条窄的 allow,询问模式就会安静下来。对绝不能跑的路径和命令用 deny,包括始终批准之下。策略是「只许这些前缀,每种模式都算,包括我没法写成一条 glob 的命令链」时,用 PreToolUse 钩子。

给项目加允许 / 拒绝

# .matcha/config.toml
[permission]
allow = ["Bash(cargo test *)", "Bash(npm run build)"]
deny  = ["Bash(rm -rf *)"]
matcha -p "Review the API changes" \
  --allow 'Bash(git *)' \
  --deny 'Bash(rm -rf *)'

Bash(git *) 是前缀加 glob(末尾空格加 *git 保持整词)。Bash(git) 也会匹配 gitleaks。匹配区分大小写。

命令链

denyask 检查每一段(&&||;|)以及整串。一段被拒绝,整条命令就被拒绝。allow 只匹配整串,所以 Bash(git *) 会放行 git status && rm -rf /。窄的允许要配上拒绝。

无界面且设硬底线

matcha -p "Implement the feature using only git and GitHub CLI" \
  --allow 'Read' \
  --allow 'Grep' \
  --allow 'Bash(git *)' \
  --allow 'Bash(gh *)'

对没有 allow 的工具做默认拒绝,用 Claude 的 defaultMode: "dontAsk"PreToolUse 钩子。不能在 bash 上叠一条通配 deny 再旁边 allow git——拒绝会赢,git 也会被拦住。

钩子拒绝

PreToolUse 钩子可以在每种权限模式里拦住调用。例如:~/.matcha/hooks/git-gh-only.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "git-gh-only.sh", "timeout": 5 }
        ]
      }
    ]
  }
}

脚本从标准输入读 JSON(toolInput.command),写出 {"decision":"deny","reason":"…"} 并以退出码 2 结束,或写出 {"decision":"allow"}。命令链要自己拆;钩子运行器不会替你拆。匹配器 Bash 也会匹配真实工具名 run_terminal_command

/hooks 安装和查看(非 VS Code 家族终端上用 Ctrl+L)。按 r 从磁盘重新加载。

和沙箱一起用

面对不信任的代码:

  1. dontAsk 加上窄的允许,或一条限制性的 PreToolUse 钩子
  2. --sandbox strict(或带 deny 的自定义配置档)
  3. 在任何 SessionStart / 项目钩子跑之前先做项目信任

在 TUI 里

权限决定出现在记录里。/always-approve/auto 改的是模式,不是规则文件。/hooks 管理钩子来源。