Permission (PermissionNext)
PermissionNext 是 OpenCode 的权限控制系统,管理工具和操作的访问控制。
概述
PermissionNext 使用基于规则的权限系统,支持通配符匹配和精细的访问控制。每个操作(如 write、bash、read)都需要权限检查,可以设置为 allow(允许)、deny(拒绝)或 ask(询问用户)。
定义位置
packages/opencode/src/permission/next.ts:12-95
主要对象
Action
权限动作类型。
type Action = "allow" | "deny" | "ask"
| 值 | 说明 |
|---|---|
allow | 允许操作,无需确认 |
deny | 拒绝操作 |
ask | 询问用户确认后再执行 |
Rule
单条权限规则。
| 属性名 | 类型 | 必填 | 说明 |
|---|---|---|---|
permission | string | 是 | 权限名称 |
pattern | string | 是 | 匹配模式 |
action | Action | 是 | 允许的动作 |
Ruleset
规则集合,按优先级排序。
type Ruleset = Rule[]
Request
权限请求对象。
| 属性名 | 类型 | 必填 | 说明 | | ---------------- | ------------------- | ---- | --------------------------- | ------ | --- | -------- | | id | string | 是 | 请求唯一标识符 | | sessionID | string | 是 | 会话 ID | | permission | string | 是 | 请求的权限名称 | | patterns | string[] | 是 | 匹配模式列表 | | metadata | Record<string, any> | 是 | 元数据 | | always | string[] | 是 | 总是要询问的模式列表 tool | object | 否 | 工具信息 | | tool.messageID | string | - | 工具所在的消息 ID | | tool.callID | string | - | 工具调用 ID |
Reply
用户对权限请求的回复。
type Reply = "once" | "always" | "reject"
| 值 | 说明 |
|---|---|
once | 本次允许,下次继续询问 |
always | 总是允许(添加到批准列表) |
reject | 拒绝操作 |
TypeScript 类型定义
export type Action = "allow" | "deny" | "ask"
export type Rule = {
permission: string
pattern: string
action: Action
}
export type Ruleset = Rule[]
export type Request = {
id: string
sessionID: string
permission: string
patterns: string[]
metadata: Record<string, any>
always: string[]
tool?: {
messageID: string
callID: string
}
}
export type Reply = "once" | "always" | "reject"
权限事件
| 事件名 | 类型 | 说明 |
|---|---|---|
permission.asked | request: Request | 询问权限 |
permission.replied | {sessionID, requestID, reply} | 收到回复 |
典型使用场景
1. 从配置创建规则集
const permission = {
"*": "allow",
write: "ask",
bash: {
"rm -rf": "deny",
"npm install": "ask",
},
}
const ruleset = PermissionNext.fromConfig(permission)
转换为:
;[
{ permission: "*", action: "allow", pattern: "*" },
{ permission: "write", action: "ask", pattern: "*" },
{ permission: "bash", action: "deny", pattern: "rm -rf" },
{ permission: "bash", action: "ask", pattern: "npm install" },
]
2. 检查权限
// 创建权限请求
const request: PermissionNext.Request = {
id: Identifier.ascending("permission"),
sessionID: session.id,
permission: "write",
patterns: ["/path/to/file.js"],
metadata: {},
always: [],
}
// 发送权限检查
await PermissionNext.ask({
...request,
ruleset: agent.permission,
})
// 如果需要用户确认,会发布 permission.asked 事件
// UI 应该显示对话框并等待用户回复
3. 回复权限请求
// 用户选择 "允许一次"
await PermissionNext.reply({
requestID: request.id,
reply: "once",
})
// 用户选择 "总是允许"(添加到批准列表)
await PermissionNext.reply({
requestID: request.id,
reply: "always",
})
// 用户选择 "拒绝"
await PermissionNext.reply({
requestID: request.id,
reply: "reject",
})
4. 合并多个规则集
const defaults = PermissionNext.fromConfig({
"*": "allow",
write: "ask",
})
const agentRules = PermissionNext.fromConfig({
bash: "deny",
})
const userRules = PermissionNext.fromConfig({
read: "ask",
})
// 合并(优先级:defaults < agent < user)
const merged = PermissionNext.merge(defaults, agentRules, userRules)
5. 添加永久批准
// 当用户选择 "always" 时,添加到批准列表
await PermissionNext.addApproval({
projectID: project.id,
patterns: ["/path/to/file.js"], // 总是允许的模式
})
6. 检查是否批准
const approvals = await PermissionNext.listApproval(project.id)
for (const approval of approvals) {
console.log(`Approved patterns: ${approval.patterns.join(", ")}`)
}
权限匹配规则
通配符匹配
支持通配符模式:
"*" // 匹配所有路径
"*.js" // 匹配所有 .js 文件
"/path/*" // 匹配 /path/ 下所有内容
"/path/*.js" // 匹配 /path/ 下所有 .js 文件
模式匹配优先级
- 精确匹配优先于通配符
- 更具体的模式优先于更通用的模式
示例:
const rules = [
{ permission: "write", pattern: "*.js", action: "allow" },
{ permission: "write", pattern: "*", action: "deny" },
]
// "/path/to/file.js" 匹配规则 1 (允许)
// "/path/to/file.txt" 匹配规则 2 (拒绝)
权限系统架构
内置权限规则
默认权限
const defaults = {
"*": "allow", // 默认允许所有操作
doom_loop: "ask", // 防止无限循环
external_directory: {
"*": "ask", // 外部目录需要确认
[Truncate.DIR]: "allow", // 允许截断目录
[Truncate.GLOB]: "allow", //允许使用 glob
},
question: "deny", // 禁用 question 工具
plan_enter: "deny", // 禁用 plan_enter
plan_exit: "deny", // 禁用 plan_exit
read: {
"*": "allow", // 默认允许读取
"*.env": "ask", // 环境变量文件需要确认
"*.env.*": "ask", // 环境变量文件需要确认
"*.env.example": "allow", // 环境变量示例文件允许
},
}
权限存储
待定请求
;["permission_pending", projectID, sessionID, requestID] = Request
批准列表
["permission_approval", projectID] = {
patterns: string[]
}[]
错误处理
DeniedError
当权限被拒绝时抛出:
class DeniedError extends Error {
constructor(rules: Rule[]) {
super("Permission denied")
this.rules = rules
}
}
权限配置示例
完整示例
{
"permission": {
// 全局设置
"*": "allow",
// 文件写入需要确认
"write": "ask",
// 危险命令拒绝
"bash": {
"rm -rf": "deny",
"dd": "deny",
":(){ :|:& };": "deny", // 防止 fork 炸弹
},
// 特定文件路径
"edit": {
"/etc/*": "deny",
"/root/*": "deny",
"node_modules/**": "allow",
},
// 网络操作
"webfetch": "ask",
"websearch": "ask",
// 测试工具
"test": {
"--coverage": "allow",
"*": "ask",
},
},
}
对象关系
变更历史
| 版本 | 变更内容 | 日期 |
|---|---|---|
| v1 | 初始 PermissionNext 架构 | - |
| v1.1 | 添加 approval 持久化 | - |
| v1.2 | 改进通配符匹配 | - |