来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)
本内容改编自小山学堂《学 AI 产品,从入门到精通》,为二次演绎配音版
本集属于技术侧(S2)模块 T2「解剖 Grok Build」,取材自 2 节课:
核心命题有两个:
读完本集,你应该能够:
| 能力 | 具体表现 |
|---|---|
| 复述授权链 | 按 PARSE → PLAN → HOOKS → POLICY → DECIDE → ENFORCE 六段说出每段的判据 |
| 区分两层 | 讲清「权限层决定能否尝试」与「沙箱层决定能做到什么」 |
| 判断规则胜负 | 知道策略优先级为 deny > ask > allow,且与来源顺序无关 |
| 处理混合命令 | 知道 Bash 会被分段,每段独立过检,不可借首段放行 |
| 识别阻断事件 | 15 个事件里只有 PreToolUse 的 is_blocking() 为真 |
| 解释 fail-open | 能列出六类 hook 结果分别对应阻断还是放行 |
| 写出可测配置 | 按「事件 → matcher 组 → 处理器列表」写 JSON,并设计四条故障测试 |
| 定位强制保证 | 知道哪些规则必须移到权限层或沙箱,不能留在 hook |
01 PARSE 工具输入 → AccessKind(Read / Edit / Bash / Grep / MCPTool / WebFetch / WebSearch)
02 PLAN plan_mode_edit_gate 可在发出权限请求前拒绝修改
03 HOOKS PreToolUse 显式 deny 立即阻断;timeout / 崩溃 / 格式错误 fail-open
04 POLICY 合并 requirements、managed settings、managed config、Grok config、Claude fallback
05 DECIDE managed deny 最先短路 → yolo pin / session grants / Auto / sandbox Bash / 只读安全项 / MCP 与域名 → prompt
06 ENFORCE Permission Allow 只放行本次请求;沙箱 active 时仍受 capability set 约束
决策输入比 ToolKind 更具体。
ToolInput 会携带路径、命令、域名或 MCP 名称等细节。同样一个工具,操作仓库源码与操作家目录密钥是完全不同的两件事。
计划模式下修改动作被拦在权限请求之前,用户根本不会看到弹窗。不问用户反而是更强的约束——它绕过了确认环节,而不是依赖它。
用户的确认是稀缺资源。一个每次都弹窗的系统,用户很快会养成无脑点同意的习惯——届时弹窗不再是防线,反而成了风险放大器。评估一个系统的授权设计,别只看「有没有弹窗」,要看「有多少请求在到达弹窗之前就被自动裁决掉了」。
permission/resolution.rs 合并五路来源:
规则评估与来源顺序无关,优先级为 deny > ask > allow。
不同来源的配置由不同的人写:托管设置来自组织,项目配置来自仓库,请求自带的要求来自模型。若按来源顺序决定胜负,那么谁能控制顺序,谁就能控制安全——这显然是错的。「与来源顺序无关」是一条抗操纵的设计。
这条与上一集「全局优先保护、项目只能新增 profile 名称」是同一个原则:系统尽量排除「谁先写 / 谁后写」的影响,只关心「有没有人说过不行」。落地自查:你的配置系统里,是否存在「后加载者覆盖先加载者」的路径? 只要存在,它就是攻击面。
managed policy deny ← 最先短路,命中即止
↓
yolo pin → session grants → Auto fast path / classifier
→ sandbox Bash auto → 只读安全项 → MCP 与域名授权
↓
prompt(仍未决定才问用户)
// Managed policy runs before YOLO and sandbox fast paths.
if let Some(Decision::Reject(reason)) = policy_decision { /* PolicyDeny */ }
if matches!(&access, AccessKind::Bash(_))
&& xai_grok_sandbox::should_auto_allow_bash()
&& !policy_forced_prompt
&& !auto_forced_prompt { /* allow */ }
「沙箱内所有写操作自动批准」并非通用规则。
源码中的 sandbox fast path 专门检查 Bash,且受 policy_forced_prompt 与 auto_forced_prompt 约束;Edit 另有自己的会话授权与 edit policy。
同理,Auto 分类器的判定也可以被强制提示开关覆盖——模型的主观判断永远排在被配置的规则后面。
| 权限层 | 沙箱层 | |
|---|---|---|
| 回答什么 | 这次工具请求是否允许进入执行 | 获准执行的进程实际能访问哪些文件与网络 |
| 读取什么 | AccessKind、目标细节、组织策略、会话授权、Auto verdict、用户选择 | capability set、子进程网络策略 |
| 失效后果 | 规则可以要求 ask,也可以直接 deny | 若未 active,权限弹窗 ≠ 内核隔离 |
一句话:授权决定「能否尝试」,沙箱限制「执行时能做到什么」。两层叠加才是一次工具执行的真实安全边界。
bash_command_splitting)tree-sitter-bash 将可安全分解的脚本拆成每个 plain command,识别 &&、||、分号与管道。
ls && rm 借首段放行;rm、chmod、chown、kill、git push;而是「每一段各自能不能独立通过检查」。模型很擅长生成看起来无害的命令行,若只对整条命令做一次判断,拼接技巧就成了现成的绕过手段。给自己的命令过滤加一条自查:是否存在「只校验首段 / 只校验前缀」的捷径? 有就是漏洞。
主流程 8 个:SessionStart · SessionEnd · Stop · StopFailure · PreToolUse · PostToolUse · PostToolUseFailure · PermissionDenied
扩展 7 个:UserPromptSubmit · Notification · SubagentStart · SubagentStop · 兼容别名 SubagentEnd · PreCompact · PostCompact
「事件被触发」不等于「能控制主流程」。
事件枚举负责定义触发点,is_blocking() 单独声明阻断能力。15 个事件里只有 PreToolUse 的 is_blocking() 为真。
Hook 的输入是事件信封(经 stdin 传给外部命令),输出是 stdout 上的结构化决策。它是一段外部程序,不是内联策略——外部程序会崩溃、会超时、会被杀掉。
阻断意味着主流程要等一个外部进程的决定,这个等待有代价,系统只在一个地方付这个代价。
| Hook 结果 | dispatcher 解释 | 工具调用 |
|---|---|---|
JSON decision = deny | 显式拒绝 | 阻断 |
| 无有效 JSON,退出码 2 | fallback 拒绝 | 阻断 |
有效 JSON allow,退出码 2 | JSON 优先 | 放行 + 冲突警告 |
| 退出码非 0 且非 2 | HookRunResult::Failed | 放行 + 警告 |
| 超时或进程崩溃 | HookRunResult::Failed | 放行 + 警告 |
| stdout 无效或 decision 未知 | 回退退出码或 Failed | 输出本身不阻断;fallback 退出码 2 仍拒绝 |
HookRunnerResult::Failed(err) => {
tracing::warn!(error = %err, "hook failed; ignoring (fail-open)");
}
源码注释明确要求:Hook 故障不能破坏工具可用性。
系统明确区分了两件事:「我决定了不行」(阻断)与 「我没能做出决定」(放行)。理解了这个区分,就能判断一个检查机制能承担什么职责:
把强制规则放进 Hook,等于把它降级成了建议。
事件(PreToolUse)
→ matcher 组(正则 + Bash 兼容别名)
→ 处理器列表(type: command / command / timeout)
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [ { "type": "command",
"command": "bin/safe-shell-guard.sh",
"timeout": 5 } ] }
]
}
DENY_EXIT_CODE = 2 表达拒绝;Bash 命中内部工具名 run_terminal_command;~/.grok/hooks/;项目 Hook:.grok/hooks/,受 folder trust 控制;deny → 工具确实被阻断;第 2 条最容易被跳过,也最该测——你以为在保护你的那个钩子,可能早就在静默放行了。
额外建议:Hook 脚本要尽量短、尽量快地返回,它跑在主流程关键路径上;设明确超时,并确认超时后的行为符合预期。
deny > ask > allow,且与来源顺序无关?PreToolUse)xai-grok-workspace/permission、xai-grok-hooks);实现可能随版本演进,落地前以当前版本源码为准。真正可靠的系统不会把安全寄托在某一个组件上,而是让每一层都清楚自己负责什么,以及自己失效时会发生什么。知道自己失效时的行为,这本身就是可靠性的一部分。
最后留一个问题:你的系统里,有没有哪个安全组件,你从未验证过它失效时会发生什么?
*来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)*
*本内容改编自小山学堂《学 AI 产品,从入门到精通》,为二次演绎配音版*