English version README.en.md · English
为 opencode 提供主机文件系统路径的读取权限管理:通过双层(全局 + 项目)可热更新的 JSON 规则文件,对指定路径执行 允许 / 拒绝 / 询问(原生弹窗 + 超时自动拒绝)。支持 Linux、macOS、Windows。
⚠️ 本插件对 opencode 的读写类文件操作进行拦截:read/list/grep/glob/write/edit/apply_patch精确拦截,bash词法级拦截。
- 功能特性
- 工作原理
- 安装
- 卸载
- 规则文件与双层结构
- 规则语义
- 询问模式(弹窗 + 超时自动拒绝)
- CLI: manage.mjs
- TUI 面板: /permission
- Windows 平台支持
- 环境变量
- 项目结构
- 测试
- 故障排查
- 双层规则:全局层(
~/.config/opencode/permission/rules.json)+ 项目层(<项目>/permission/rules.json),last-match-wins 合并(项目层覆盖全局层)。 - 精确拦截读取类工具:
read/list/grep/glob命中 deny 规则时直接抛错阻止调用。 - bash 词法级拦截:解析命令中的引号字符串、
$HOME/~展开、重定向目标、赋值等,提取候选路径后匹配全部 deny 规则(含 glob),可拦cat "$HOME/.ssh/id_rsa"、echo x > /etc/shadow等绕过(仍有局限)。 - 询问模式:项目外未匹配路径可触发 opencode 原生权限弹窗;
ask_timeout_ms(默认 5000ms)内无响应则自动拒绝。 - 热更新:每次工具调用按 mtime 重读规则文件,改完立即生效,无需重启。
- Fail-open:规则文件缺失/损坏时打印一次错误并放行——绝不因规则文件问题阻塞工作流。
- 可视化面板:TUI 中输入
/permission管理规则,支持英文/中文界面切换。 - 零运行时依赖:纯 TypeScript / Node ESM,无需 npm 安装。
- 跨平台:Linux、macOS、Windows(见 Windows 平台支持)。
插件接入 opencode 的三个生命周期点:
config钩子(启动时)——读取合并规则集;若询问模式激活(全局 default 为ask,或存在 action 为ask的规则),则向原生配置注入read/grep/glob/list = {"*": "ask"},使这些工具产生权限请求而非静默放行。(注入仅在启动时执行一次——启用/停用询问模式需重启 opencode。)tool.execute.before钩子(每次工具调用)——对读取类工具:规范化目标路径(~展开、path.resolve、符号链接解析)后评估合并规则:- 命中 deny 规则 → 抛错,工具调用被阻止。
- 命中 ask 规则 → 放行,由原生权限层弹窗,
permission.ask钩子保持请求挂起并启动自动拒绝定时器。 - 未命中 → 走原生行为(项目内读取放行;询问模式下项目外路径可能弹窗)。
permission.ask钩子——对每个权限请求做防御性裁决:显式 deny 规则强制deny;显式 allow 规则强制allow;显式 ask 规则保持请求挂起并启动自动拒绝定时器;项目内路径自动放行(避免弹窗风暴);全局询问模式开启但项目层覆盖 default 时,按合并后的 default 裁决而非让弹窗悬挂。
自动拒绝定时器(auto_reject.ts):ask_timeout_ms 内用户未响应时,通过 SDK 权限接口程序化回复 reject,随后弹错误 toast。定时器有上限并 unref()。
- opencode ≥ 1.18(依赖
plugin配置数组与 TUI 插件加载) - CLI/测试需要 Node.js ≥ 22(插件本体运行在 opencode 自带的 Bun 运行时)
- 无需安装任何 npm 包——零依赖。
- 安装插件包(二选一):
- 通过 opencode 命令安装(自动写入配置):
该命令会安装 npm 包并自动更新
npx opencode plugin opencode-permission-guard-plugin
opencode.json/tui.json的plugin数组。 - 或手动在
~/.config/opencode/opencode.json的plugin数组加入包名:若同时需要 TUI 面板,在{ "plugin": ["opencode-permission-guard-plugin"] }~/.config/opencode/tui.json的plugin数组也加入同一包名 (opencode 会自动从包内exports["./server"]/exports["./tui"]加载对应入口)。
- 通过 opencode 命令安装(自动写入配置):
- 创建全局规则文件:
~/.config/opencode/permission/rules.json(内容见下文"规则文件")。 - 重启 opencode。
- 克隆仓库:
插件按绝对路径注册,选定后不要移动。对应绝对路径为
git clone https://github.com/helloydh007/opencode-permission-guard.git ~/permission/home/<用户名>/permission/。 - 注册 server 插件:在
~/.config/opencode/opencode.json的plugin数组加入:"file:///home/<你的用户名>/permission/index.ts" - 注册 TUI 面板(可选但推荐):在
~/.config/opencode/tui.json的plugin数组加入:"file:///home/<你的用户名>/permission/tui.ts" - 创建全局规则文件:同方式一第 2 步。
- 重启 opencode。
先确认插件目录/包已就位,然后:
cd ~/permission && node manage.mjs list --global # 手动安装时或使用已安装的 CLI(npm 方式安装时 manage.mjs 位于包内,可用 npx opencode-permission-guard-plugin 或按包内路径运行)。
或在 TUI 中输入 /permission。
💡 插件必须注册在运行中的 opencode 实际加载的配置文件里。若你同时使用多个配置文件(
opencode.json、opencode.jsonc、config.json),则注意opencode.jsonc最后加载、其plugin数组会替换前面的——把注册放在最后加载的文件即可。npm 安装方式下,opencode 会自动把包名写到对应的全局配置,无需手动选择opencode.json还是opencode.jsonc。
- 移除配置条目:
~/.config/opencode/opencode.json→ 从plugin数组删除file:///.../permission/index.ts行。~/.config/opencode/tui.json→ 从plugin数组删除file:///.../permission/tui.ts行。
- 删除插件目录(如
rm -rf ~/permission)。 - (可选)删除规则文件:
~/.config/opencode/permission/rules.json(全局层)<项目>/permission/rules.json(各项目层)
- (可选)删除 skill(若安装过):
~/.config/opencode/skills/opencode-permission/
- 重启 opencode。无残留加载项、无后台进程、无缓存。
| 层 | 默认路径 | 环境变量覆盖 |
|---|---|---|
| 全局层 | ~/.config/opencode/permission/rules.json |
OPENCODE_PERMISSION_RULES_GLOBAL |
| 项目层 | <项目目录>/permission/rules.json |
OPENCODE_PERMISSION_RULES |
合并规则:
- 规则数组拼接顺序为全局在前、项目在后;last-match-wins 语义下,项目层规则覆盖全局层同 pattern 规则。
- 合并后的
default取项目层;项目层缺失时回退全局层。 - 缺失的层静默跳过。
- 某层文件损坏(非法 JSON / shape 不合法)时该层跳过并
console.error一次(fail-open),另一层照常生效。
{
"version": 1,
"default": "allow",
"ask_timeout_ms": 5000,
"rules": [
{ "pattern": "/path/or/glob/**", "action": "allow" },
{ "pattern": "/path/or/glob/**", "action": "deny" },
{ "pattern": "/path/or/glob/**", "action": "ask" },
{ "pattern": "~/bare-dir", "action": "deny", "note": "可选备注" }
]
}default:"allow"|"deny"|"ask"——无规则命中时使用。rules[].action:"allow"|"deny"|"ask"(规则级 ask 自 v1.1 起支持)。- pattern 形式:
- 绝对路径或
~/...(经os.homedir()展开)。 - 裸目录(无 glob 字符)匹配该目录本身及其下全部内容。
- glob:
**(跨分隔符)、*(单段内)、?(单字符)。 - Windows 上
C:\Users\me\**与C:/Users/me/**均可。
- 绝对路径或
- 评估:后匹配的规则胜出;匹配前解析符号链接(
realpathSync)。 - 生效范围:
read/list/grep/glob/write/edit/apply_patch精确拦截(apply_patch 从 diff 头提取路径);bash词法级提取候选路径后匹配全部 deny 规则(含 glob)。
当合并 default 为 ask,或存在 action 为 ask 的规则时:
- 启动时插件注入原生
read/grep/glob/list权限规则 → 这些工具产生原生权限请求。 permission.ask钩子裁决:显式 deny → 拒绝;显式 allow → 放行;显式 ask 或项目外未匹配路径 → 保持请求挂起(opencode 弹出原生弹窗)。ask_timeout_ms(默认 5000ms)内无响应 → 插件经 SDK 回复 reject 并弹错误 toast。
注意:
- 项目内路径自动放行(避免弹窗风暴);显式 ask 规则仍优先。
- 无 TUI/非交互环境降级:当宿主无终端(headless
serve/run、OpenChamber 后台等,即process.stdin非 TTY)时,ask 裁决自动降级为 deny,防止宿主毫秒级自动放行造成安全缺口。可用规则字段"ask_headless_fallback": false关闭此行为。 bash永不触发询问弹窗(仅 deny 规则)。- 启用/停用询问模式需重启(config 注入在启动时);超时值热更新。
零依赖 Node ESM 脚本。在插件目录内运行(如 cd ~/permission)。默认目标 = 项目层(脚本旁的 rules.json);--global / -g 操作全局层。
node manage.mjs list [--global]
node manage.mjs deny "<pattern>" [--note "..."] [--global]
node manage.mjs allow "<pattern>" [--note "..."] [--global]
node manage.mjs ask "<pattern>" [--note "..."] [--global] # ask 规则(需重启生效)
node manage.mjs remove "<pattern>" [--global]
node manage.mjs default <allow|deny|ask> [--global]
node manage.mjs timeout <ms> [--global] # ask_timeout_ms规则改动热更新(无需重启);但新增/删除 ask 规则或切换询问模式需重启。
在 tui.json 注册 tui.ts 并重启后,TUI 中输入 /permission:
- 列出两层规则:
<pattern> [allow|deny|ask] (global|project[: note])。 - 点击规则 → 直接选择 allow / deny / ask(当前动作高亮),或删除(二次确认)。
- 添加规则向导:pattern → 动作 → 层 → 可选备注。
- 设置项目层默认动作(allow/deny/ask)与 ask 超时。
- 语言菜单:English / 中文 切换(默认英文,持久保存)。
- 每次改动弹 toast 提示写入的文件路径。
插件跨平台:
- 路径处理全部使用
path.join/os.homedir()/path.resolve/realpathSync——平台感知。 - glob 匹配将
/与\均视为段分隔符:C:\Users\me\**、C:\Users\me\*、C:/Users/me/**均可;*不跨分隔符。 - Windows 上 opencode 配置目录为
%USERPROFILE%\.config\opencode——默认全局规则路径无需改动即可正确解析。 manage.mjs使用同目录临时文件原子写入(避免跨盘符 EXDEV)。- CLI 用
node manage.mjs ...运行(Node ≥ 22)。
Windows 提示:
- 推荐带盘符的完整路径:
C:\Users\me\secret\**。 ~经os.homedir()展开为%USERPROFILE%。- 默认示例规则(
~/.ssh/**、~/.aws/**、/etc/shadow)是 POSIX 风格,请替换为 Windows 等价写法(如~\.ssh\**或%USERPROFILE%\.ssh\**)。
| 变量 | 用途 |
|---|---|
OPENCODE_PERMISSION_RULES |
覆盖项目层规则文件路径 |
OPENCODE_PERMISSION_RULES_GLOBAL |
覆盖全局层规则文件路径 |
OPENCODE_CONFIG_CONTENT |
(opencode 内部)注入配置——见故障排查 |
通过 npm 方式 安装时,文件位于 npm 包的 node_modules/opencode-permission-guard-plugin/ 目录(与下方目录树相同但不需要自己 clone)。
通过 手动克隆 方式安装时,文件位于你选定的目录(如 ~/permission/)。
permission/
├── engine.ts # 纯规则引擎(无 opencode 依赖,可独立测试)
├── auto_reject.ts # 询问模式自动拒绝定时器(SDK 回复 + toast)
├── index.ts # server 插件入口:钩子、config 注入、询问模式
├── tui.ts # TUI 插件入口:/permission 斜杠命令
├── panel.ts # 面板主菜单 + 单规则子菜单
├── flows.ts # 添加规则 / 默认动作 / ask 超时子流程
├── rules.ts # 面板侧规则模型 + 文件 IO(与 engine/CLI 语义一致)
├── i18n.ts # en/zh 界面字典(默认英文)
├── rules.json # 项目层规则(默认空;不随 npm 包发布,仅手动安装时有)
├── manage.mjs # 规则管理 CLI(Node ESM,零依赖)
├── test.ts # 引擎测试(不随 npm 包发布;node --experimental-strip-types test.ts)
├── package.json # 模块元数据(含 exports["./server"]、exports["./tui"])
└── README.md / README.en.md / LICENSE
node --experimental-strip-types test.ts运行引擎单元测试(合并语义、glob 匹配含 Windows 分隔符、ask 规则处理、超时校验)。无需网络或 opencode 实例。
"failed to load plugin ... Spread syntax requires ...iterable"
插件模块加载失败。常见原因:最后加载的配置文件(opencode.jsonc 优先于 opencode.json)的 plugin 数组未包含条目,或 default 导出形态错误。本插件使用 export default { id, server } / { id, tui }(对象形式)——opencode ≥ 1.18 所要求。
修改 rules.json 后规则未生效 规则文件热更新,但 ask 规则与询问模式切换需要重启 opencode(config 注入在启动时)。
bash 绕过 deny 规则
bash 守卫基于子串检测,仅检查字面(非 glob)deny 路径。cat "$HOME/.ssh/id_rsa" 这类变量展开可绕过——这是已记录的局限;优先依赖 read 家族工具的精确拦截。
Fail-open 行为 规则文件缺失/损坏时,插件打印一次错误并放行。绝不会因规则文件问题硬性阻塞工作。
配置文件顺序(opencode.json vs opencode.jsonc)
opencode 按 config.json → opencode.json → opencode.jsonc 合并,数组被后加载文件替换。若使用多个配置文件,请把插件条目放在最后一个(或删除重复文件)。部分桌面壳注入的 OPENCODE_CONFIG_CONTENT 也会替换 plugin 数组——用 opencode debug config 检查。
License: MIT · 范围: opencode 读取权限控制 · 平台: Linux / macOS / Windows