OpenCode Tool & Permission System

OpenCode 技术分享 第三期 | Tool 系统与权限控制 | 交互式演示

一次 Bash 命令的完整生命周期

当 Agent 决定执行 rm -rf /tmp/cache 时,系统需要经过工具查找、双重权限检查、执行、输出裁剪等多个环节。点击「下一步」逐步观察。

① 工具查找
② 目录权限
③ 命令权限
④ 用户确认
⑤ 执行
⑥ 输出处理
Step 0 / 12 点击「下一步」开始演示
🔧 当前操作
Agent 输出工具调用:

{
  "name": "bash",
  "input": {
    "command": "rm -rf /tmp/cache",
    "timeout": 120000
  }
}
🛡️ 权限状态
等待开始...
📋 执行日志 (0)

工具注册与 Stacking 机制

OpenCode 支持 4 种来源的工具统一注册。同名工具使用栈式覆盖(Stack),.at(-1) 为当前活跃版本。Scope 退出时自动 pop 恢复上一版本。

Step 0 / 8 点击「下一步」观察工具注册过程
📦 Tool Registry
Native (内置工具)
bash read edit write glob grep
MCP (Model Context Protocol)
mcp_github_pr mcp_jira_issue
Custom (用户自定义)
Plugin (插件)
📚 "bash" 工具栈
[0] bash (native) active .at(-1)
📋 注册日志 (0)

Last-Match-Wins 权限匹配算法

OpenCode 使用 findLast 从后向前遍历规则数组。规则来源:[...configuredRules, ...savedRules]。DENY 规则不可被 saved 覆盖,保证安全底线。

Step 0 / 9 点击「下一步」观察规则匹配过程
📋 规则列表 (从前到后)
Action: bash  |  Resource: rm -rf /tmp/cache
  • #0 configured action: *  |  resource: * ASK
  • #1 configured action: read  |  resource: * ALLOW
  • #2 configured action: bash  |  resource: rm * DENY
  • #3 saved action: bash  |  resource: * ALLOW
  • #4 saved action: bash  |  resource: rm -rf * ALLOW
匹配过程

算法:findLast(rule => match(rule, action, resource))


遍历方向:从 #4#0 逆序扫描


关键规则:configured 中的 DENY 不可被 saved 规则覆盖

📋 匹配日志 (0)

工具输出裁剪策略(Head/Tail Sampling)

输出超限时(MAX_LINES=2000, MAX_BYTES=50KB),采用 Head/Tail 采样:保留前 ceil(N/2) 行 + 后 floor(N/2) 行,中间插入省略标记。完整内容写入磁盘备查。

Step 0 / 6 点击「下一步」观察输出裁剪过程
📄 原始输出 (5000行 / 120KB)
Line 1: Starting compilation...
Line 2: Compiling src/main.ts
Line 3: Compiling src/utils.ts
Line 4: Compiling src/tool/bash.ts
Line 5: Compiling src/tool/edit.ts
... (Line 6 - 4995)
Line 4996: Bundling complete
Line 4997: Writing dist/index.js
Line 4998: Writing dist/index.d.ts
Line 4999: Build successful
Line 5000: Done in 12.3s
✂️ 裁剪后 (送入 LLM)
等待开始演示...
📊 裁剪统计
原始行数: 5000
保留行数: -
裁剪行数: -
原始大小: 120KB
裁剪后: -

三产品权限模型对比

OpenCode (规则式)、Claude Code (白名单式)、Codex (沙箱式) 代表了三种不同的安全哲学。

维度 OpenCode Claude Code Codex
核心思想 规则匹配 (Last-Match-Wins) 白名单枚举 (allowedTools) 沙箱隔离 (Docker/Firewall)
粒度 action + resource glob tool name 级别 环境级别
Per-Agent 差异 build/plan/explore 各有 Ruleset 不区分 Agent 不适用(统一沙箱)
DENY 不可覆盖 configured DENY 优先 不在白名单即 deny 网络/文件系统隔离
用户记忆 SQLite per-project "Always Allow" ~/.claude/settings allowedTools 无需(沙箱兜底)
安全边界 规则完备性依赖配置 白名单简单但粗粒度 容器隔离最强但灵活性低
哲学 默认 ASK,精细控制 默认 DENY,显式允许 默认 ALLOW,环境兜底
OpenCode 模式
// permission.ts - evaluate()
const rule = rules.findLast(
  r => match(r.action, action)
    && match(r.resource, resource)
);
return rule?.effect ?? "ask";
Claude Code 模式
// settings.json
{
  "allowedTools": [
    "Read",
    "Write",
    "Bash(npm test:*)"
  ]
}
// 不在列表中 → 需确认
Codex 模式
# Docker sandbox
FROM node:20-slim
# 网络隔离
RUN iptables -P OUTPUT DROP
# 只读挂载源码
VOLUME ["/workspace:ro"]
# 命令随意执行(沙箱兜底)