Skip to content

About

Host directory access control plugin for opencode: two-layer (global+project) hot-reloadable rules guarding read/write access with allow/deny/ask actions, ask mode with auto-reject timeout, headless downgrade, /permission TUI panel (en/zh), Windows support. Zero dependencies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

permission-guard — opencode 主机目录访问权限守卫插件

npm License: MIT

English version README.en.md · English

为 opencode 提供主机文件系统路径的读取权限管理:通过双层(全局 + 项目)可热更新的 JSON 规则文件,对指定路径执行 允许 / 拒绝 / 询问(原生弹窗 + 超时自动拒绝)。支持 Linux、macOS、Windows。

⚠️ 本插件对 opencode 的读写类文件操作进行拦截:read / list / grep / glob / write / edit / apply_patch 精确拦截,bash 词法级拦截。


目录


功能特性

  • 双层规则:全局层(~/.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 的三个生命周期点:

  1. config 钩子(启动时)——读取合并规则集;若询问模式激活(全局 default 为 ask,或存在 action 为 ask 的规则),则向原生配置注入 read/grep/glob/list = {"*": "ask"},使这些工具产生权限请求而非静默放行。(注入仅在启动时执行一次——启用/停用询问模式需重启 opencode。)
  2. tool.execute.before 钩子(每次工具调用)——对读取类工具:规范化目标路径(~ 展开、path.resolve、符号链接解析)后评估合并规则:
    • 命中 deny 规则 → 抛错,工具调用被阻止。
    • 命中 ask 规则 → 放行,由原生权限层弹窗,permission.ask 钩子保持请求挂起并启动自动拒绝定时器。
    • 未命中 → 走原生行为(项目内读取放行;询问模式下项目外路径可能弹窗)。
  3. 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 包——零依赖。

方式一:自动安装(npm,推荐)

  1. 安装插件包(二选一):
    • 通过 opencode 命令安装(自动写入配置):
      npx opencode plugin opencode-permission-guard-plugin
      该命令会安装 npm 包并自动更新 opencode.json / tui.json 的 plugin 数组。
    • 或手动在 ~/.config/opencode/opencode.json 的 plugin 数组加入包名:
      { "plugin": ["opencode-permission-guard-plugin"] }
      若同时需要 TUI 面板,在 ~/.config/opencode/tui.json 的 plugin 数组也加入同一包名 (opencode 会自动从包内 exports["./server"] / exports["./tui"] 加载对应入口)。
  2. 创建全局规则文件:~/.config/opencode/permission/rules.json(内容见下文"规则文件")。
  3. 重启 opencode。

方式二:手动安装(Git 克隆 / 源码)

  1. 克隆仓库:
    git clone https://github.com/helloydh007/opencode-permission-guard.git ~/permission
    插件按绝对路径注册,选定后不要移动。对应绝对路径为 /home/<用户名>/permission/。
  2. 注册 server 插件:在 ~/.config/opencode/opencode.json 的 plugin 数组加入:
    "file:///home/<你的用户名>/permission/index.ts"
  3. 注册 TUI 面板(可选但推荐):在 ~/.config/opencode/tui.json 的 plugin 数组加入:
    "file:///home/<你的用户名>/permission/tui.ts"
  4. 创建全局规则文件:同方式一第 2 步。
  5. 重启 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。

卸载

  1. 移除配置条目:
    • ~/.config/opencode/opencode.json → 从 plugin 数组删除 file:///.../permission/index.ts 行。
    • ~/.config/opencode/tui.json → 从 plugin 数组删除 file:///.../permission/tui.ts 行。
  2. 删除插件目录(如 rm -rf ~/permission)。
  3. (可选)删除规则文件:
    • ~/.config/opencode/permission/rules.json(全局层)
    • <项目>/permission/rules.json(各项目层)
  4. (可选)删除 skill(若安装过):
    • ~/.config/opencode/skills/opencode-permission/
  5. 重启 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 的规则时:

  1. 启动时插件注入原生 read/grep/glob/list 权限规则 → 这些工具产生原生权限请求。
  2. permission.ask 钩子裁决:显式 deny → 拒绝;显式 allow → 放行;显式 ask 或项目外未匹配路径 → 保持请求挂起(opencode 弹出原生弹窗)。
  3. 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 注入在启动时);超时值热更新。

CLI: manage.mjs

零依赖 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 面板: /permission

在 tui.json 注册 tui.ts 并重启后,TUI 中输入 /permission:

  • 列出两层规则:<pattern> [allow|deny|ask] (global|project[: note])。
  • 点击规则 → 直接选择 allow / deny / ask(当前动作高亮),或删除(二次确认)。
  • 添加规则向导:pattern → 动作 → 层 → 可选备注。
  • 设置项目层默认动作(allow/deny/ask)与 ask 超时。
  • 语言菜单:English / 中文 切换(默认英文,持久保存)。
  • 每次改动弹 toast 提示写入的文件路径。

Windows 平台支持

插件跨平台:

  • 路径处理全部使用 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

About

Host directory access control plugin for opencode: two-layer (global+project) hot-reloadable rules guarding read/write access with allow/deny/ask actions, ask mode with auto-reject timeout, headless downgrade, /permission TUI panel (en/zh), Windows support. Zero dependencies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages