AI 工具执行的审批与防篡改执行链
SRE-Buddy 执行安全体系:审批、身份绑定与执行前校验
Note
SRE-Buddy 允许模型在目标主机上执行诊断命令。执行链的输入(模型参数)不可信,且审批环节天然存在"审批内容"与"执行内容"分离的时序替换风险。本文描述该执行链的安全设计:以单一规范执行请求贯穿审批与执行,身份字段由服务端从受信会话派生,审批内容固化为确定性摘要并在执行前强一致复验,审查异常一律转人工审批。
一、信任边界与设计原则
执行链要求模型产出工具参数(目标主机、登录账号、命令、超时等),并经人工批准后在目标主机执行。该模型的信任边界定义如下:
- 工具参数不可信。参数来源于模型输出,可能受提示词注入诱导、产生幻觉或误判,不作为可信输入直接使用。
- 审批存在两层不一致风险。审批人审阅的展示内容与执行器使用的参数若为两份数据,二者之间可发生时序替换(TOCTOU)。
- 执行身份仅能来自身份系统。操作用户与登录账号若可由模型参数决定,提示词注入即可直接提升权限。
对应的设计原则:
- 单一规范对象:审批前构造统一的规范执行请求,审批完整保存该请求,通过后仅恢复原请求执行,不重新生成;
- 身份服务端派生:操作人邮箱仅来自受信 SSO 会话,目标登录账号受白名单约束;
- 内容字节级固化:审批时对请求计算确定性摘要,执行前重算并与审批摘要强一致比对;
- 审查失败默认收口:自动放行条件不满足或审查组件异常时,一律转人工审批。
| 威胁形态 | 未防护后果 | 对策 |
|---|---|---|
| 提示词注入伪造高权限账号 | 模型参数中的账号字段生效 | 账号由邮箱派生,参数中的相关字段被忽略 |
| 批准后替换执行载荷(TOCTOU) | 审批展示与执行载荷不一致 | 摘要固化 + 执行前复验 |
| 目标指向本机回环地址 | 横向探测服务端自身 | 目标主机回环校验拒绝 |
| 审查组件不可用 | 请求被静默放行或静默拦截 | 明确转人工审批 |
二、规范执行请求:ExecuteRequest
全链路仅围绕 backend/tool/executor.go 中的 ExecuteRequest 运行。其类型注释直接约束了使用方式:
Server 在审批前构造它,审批完整保存它,审批通过后只能恢复原请求,不得重新生成。
关键字段如下:
| 字段 | 含义 | 来源 |
|---|---|---|
Call | 规范化工具调用(含命令、目标主机参数) | 模型参数经规范化处理 |
ActorEmail | 操作人邮箱 | 服务端从受信会话注入 |
ToolSource | 工具来源:native / mcp | 服务端判定 |
Provider | 传输方式:ssh / qssh | 服务端指定 |
RemoteUser | 跳板机侧登录用户 | 由邮箱派生,参数值被忽略 |
TargetUser | 目标机登录账号 | 白名单 qboxserver / root(默认前者) |
TargetHost / TargetPort | 目标地址 | 参数读取并校验 |
TimeoutSeconds | 命令超时(1–3600s) | 参数透传,默认 30 |
请求构造阶段即施加以下约束(NormalizeExecuteRequest / ValidateBashRequest):
- 身份派生校验:
remote_user仅允许由actor_email派生(邮箱@前缀),两者不一致直接拒绝构造;模型参数中携带的remote_user在规范化阶段被移除; - 账号与传输白名单:
target_user ∈ {qboxserver, root},provider ∈ {ssh, qssh},native 来源仅允许 bash 工具; - 回环地址拦截:目标主机命中
localhost、*.localhost或解析为回环 IP 时,返回tool: loopback target is not allowed。
三、确定性摘要与规范化
审批必须作用于"将要执行的内容",而非"经过渲染的内容"。每个执行请求可计算确定性摘要(ExecuteRequest.Digest):
- 规范化工具调用;
- 将模型自述元数据(执行原因
reason、只读自评is_read_only)从参数中拆分,纳入摘要计算; - 对结构化输入执行 RFC 8785 (JCS) 规范化序列化(
backend/security/seal),消除字段顺序、空白与转义差异导致的哈希漂移; - 计算 SHA-256,输出十六进制摘要。
摘要覆盖:调用 ID、会话 ID、工具名、参数、原因、只读自评、操作人、工具来源、MCP Server/工具、传输方式、跳板机用户、目标主机、目标端口、目标用户、命令全文、超时秒数。任一字段变更即导致摘要变化;等价写法必须产生相同摘要,该项由确定性测试保障。
四、审批单模型与批次一致性
审批单 approval.Request 持久化于 approval_requests 表,主键为 (session_id, tool_call_id),核心列包括 tool_call、execution_request、request_digest、status、permission_mode、phase。状态机仅含 pending / approved / rejected 三种状态。
批次模型:同一模型响应中产生的多个工具调用共享 batch_id(取该响应首个调用 ID)。
- 若批次内任一调用需要人工审批,整个批次进入待审状态,全部决策完成后按原始顺序执行。该约束保证同轮调用间的顺序语义与副作用一致,并避免"单独放行低风险调用"造成的越权缝隙。
- 若批次内全部调用自动放行,则不等待审批,按序就地执行,避免只读诊断被流程阻塞。
批次顺序此前存在执行倒置缺陷,已修复(#630)。
创建校验:审批单创建时叠加多层校验——执行请求合法性、工具调用与审批单身份一致(会话与调用 ID)、请求类型自洽(native 不得携带 MCP 字段,反之亦然)、以及传入的 request_digest 与执行请求现算摘要一致。任一校验失败,审批单无法创建。
五、自动放行策略与失败收口
每个候选工具调用在进入审批前先经 Reviewer 执行一次独立审查(reviewToolCall),输出 {is_read_only, risk_level, expect_result}。审查结果以 tool_review 事件进入会话事件流,供前端展示只读标记与风险等级。
自动放行需同时满足:
- 会话权限模式非
require_approval; - 模型只读自评合法;
- Reviewer 判定只读;
- 风险等级为
low。
任一条件不满足即转入人工审批。下列异常路径同样一律转人工,不产生任何静默放行:Reviewer 不可用、审查超时(默认 30 秒)、审查输出非法、模型自评字段非法。服务初始化失败时,系统记录"所有工具调用均需审批"告警。
会话层权限模式支持 default 与 require_approval 两档:后者强制该会话全部工具调用人工审批,可在建会话时指定,亦可在会话运行中切换(PATCH /api/sessions/:id/permission-mode)。
六、审批决策的并发与一致性
决策入口约束:决策请求仅允许会话所有者发起;目标会话必须处于审批暂停态且该调用位于待审集合内,否则返回 409。
存储层原子性:决策过程以行级锁(SELECT ... FOR UPDATE)锁定审批单,并校验当前状态必须为 pending,否则返回 already decided。
审计与状态一致性:状态更新与审计写入无法构成单一原子操作。为此引入显式阶段状态机 creating → committed → deciding → committed:
- 锁定并校验状态为
pending; - 将
phase置为deciding并提交——此时status保持pending,外部等待方不会提前读到终态; - 写入审批审计事件;
- 审计成功后执行条件更新(
WHERE phase = 'deciding')将status落为终态; - 审计写入失败时,
phase回退为committed,status保持pending,允许审批人重新决策。
跨实例语义:审批服务基于共享 PostgreSQL,行锁与阶段检查在不同实例间天然互斥,后到决策返回 already decided。
等待机制:编排层(waitForBatch)以审批服务的 Watcher 事件唤醒为主路径,并保留轮询兜底(100ms 起步、指数退避、上限 1 秒),避免 watcher 通道异常导致永久等待。
七、执行前校验与超时控制
决策就绪后,executeDecidedBatch 对每张 approved 审批单执行执行前绑定校验(ValidateExecutionBinding):
- 重新校验执行请求合法性;
- 确认执行请求与审批单身份一致(会话与调用 ID);
- 重算摘要并与审批时固化的
request_digest比对。
校验失败即终止整轮,命令不下发。该步骤将批准后任何中间环节对载荷的修改——缓存错乱、跨实例同步异常或恶意替换——在执行前拦截。
被拒绝的调用以明确结果回传模型(tool call rejected by user,退出码 -1),拒绝事实进入会话与审计,不静默丢弃。
执行阶段配置硬超时保护:以请求超时加 5 秒作为 guard 时限,执行置于独立 goroutine,由主协程定时器裁决。底层执行器死锁、不响应取消(目标机进程 D 状态、通道半开)时,到点强制返回,会话不会无限挂起。
八、审批界面约束:决策倒计时
为抑制"批准动作过廉",前端对批准操作施加分级倒计时冷却(#645/#650):
| 风险等级 | 批准等待 |
|---|---|
low | 0 秒 |
medium | 3 秒 |
high / critical / 未知 | 5 秒 |
默认值由后端经 /api/me 下发,可通过 SRE_APPROVAL_COUNTDOWN_SECONDS、SRE_APPROVAL_MEDIUM_COUNTDOWN_SECONDS 覆盖,置 0 即整体关闭。倒计时期间批准按钮置灰并显示剩余秒数;拒绝操作不设冷却,任何时刻立即可执行。
命令超时参数(默认 30 秒)透传至审批单并在卡片展示(#607/#608),审批人可确认该命令的执行时限。
九、方案确认的结构化表达
执行方案的人机确认,早期依赖模型输出特定文本标记与前端状态机握手。该方案受流式输出截断、标记改写与错误时机复用影响,状态机脆弱(#631)。
当前实现改为结构化工具调用 propose_remediation_plan:
- 方案必须独占一轮,不允许与产生执行副作用的工具调用同批出现,违规时整轮报错并提示等待用户确认;
- 引擎仅暴露工具 Schema,不执行该工具,方案本身不产生副作用;
- 模型产出方案后,轮次以"等待用户确认执行"结束,前端渲染确认操作;用户确认后以新消息驱动执行批次,该批次仍走完整的规范化、审批与摘要校验链。
一般性原则:需要与人工握手的协议状态,应以带 Schema、可校验、不可与副作用调用混淆的结构化事件表达,而非依赖模型恰好输出特定文本。
十、防重放与幂等性
| 场景 | 机制 | 效果 |
|---|---|---|
| 审批创建 | (session_id, tool_call_id) 唯一键 + 创建时摘要强校验 | 重复提交与创建前载荷变更被拒 |
| 审批决策 | 行锁 + pending 状态检查 + 阶段机 | 一张单据仅可决策一次 |
| 批次执行 | 整批等待、按原始顺序执行 | 防止乱序与部分放行越权 |
| 执行前校验 | 摘要重算并与 request_digest 比对 | 阻断批准后的时序替换 |
| 超时执行 | guard 定时器硬超时 | 执行器挂起不阻塞会话 |
小结
上述机制将批准内容与执行内容的一致性作为核心不变量:审批时固化规范请求与摘要,执行前复验摘要,身份仅取自受信会话,审查异常一律转人工。各环节的审计记录与幂等约束为该不变量提供可追溯支撑。
评论
评论组件加载中…