feat(plugin): 插件在线分发的安全模型——签名、审核、封禁 + 补三个加载期漏洞 - #202
Merged
Conversation
在线分发上线前先把本地 JAR 链路的三个问题收掉——它们与分发无关, 当前手放插件的场景就会踩。 1) 禁用状态下不加载 JAR。此前禁用只是把工具从 LLM 可见列表摘掉, JAR 早已 loadClass、静态块与无参构造器都跑过了。管理员点"禁用" 的预期是"别运行它",必须在加载前拦住。元数据仍登记,管理页照常 展示与启停;运行中启用一个启动时被禁的插件会自动补加载其 JAR。 2) backendJars 路径校验。manifest 由作者提供,"../../evil.jar" 能指到插件目录之外。新增 resolveBackendJar() 用 canonical path 要求目标落在插件目录内(同时挡符号链接绕行),非法则记 ERROR 跳过。 3) pluginSkillDirs 改 CopyOnWriteArrayList。同类集合都因 rescan clear+重填而用了并发安全实现,这一处是遗漏。 PLUGIN_SPEC 同步修正会让读者高估安全性的措辞:permissions 从"被授予" 改为"自行声明"并加显式警告(校验的两个集合都出自作者同一份 manifest, 且声明与行为完全不挂钩,对恶意插件零防御);"独立 ClassLoader"点明父 加载器是宿主、插件可见宿主全部类;补充禁用语义表(能拦未加载的,撤不回 已加载的,处置可疑插件要禁用→重启→移除)、rescan 限制、桌面版插件目录 落在用户可写的 ~/.aiworkdeck/plugins/。 验证:56 tests 全过,新增 3 个覆盖路径逃逸拒绝、目录内 JAR 放行、 禁用插件登记但不注册工具。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
桌面端对接官网的插件分发链路,契约见 docs/PLUGIN_DISTRIBUTION.md。 PluginMarketService 安装流程严格按 §7:先拉 bundle 用内置 Ed25519 公钥 验签,**验签失败在下载任何文件之前中止**;再逐文件下载并比对 SHA-256, 任一不符即中止;全部通过后先写临时目录再原子移动,避免半成品被扫描到。 清单里的相对路径同样做逃逸校验——注册表本身也可能被攻陷。 装完先 markDisabledBeforeLoad 再 rescan:新插件默认禁用,配合上一个提交 的「禁用即不加载 JAR」,用户在广场点启用之前插件代码一行都不会执行。 PluginRevocationService 启动时与每 24 小时同步平台封禁列表,命中的已安装 插件强制禁用。封禁不可撤销——setEnabled 拒绝启用被封禁的插件,只允许卸载; 不自动删文件,避免误封导致用户数据丢失。注册表不可达时静默跳过,不影响本地。 未配置 ai.plugins.registry-public-key 时拒绝一切在线安装(默认留空, 需随版本发布填入平台公钥)。 跨语言签名一致性做了真机对拍:用官网 Node crypto 签发,桌面端 JDK 验签 通过——canonical JSON 差一个字节就会失败,这条必须实测而非推断。 对拍用例以 -Dcrosscheck.file 开关控制,CI 无该文件时自动跳过。 验证:后端全量 301 tests 通过,新增 11 个覆盖验签正误、files 键序无关、 未配公钥拒装、非法 id、路径逃逸、哈希不符中止且不落盘、验签失败不下载、 封禁强制禁用且不可重新启用、semver 比较。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
把插件分区从「即将上线」占位换成真实列表:展示版本、作者、体积、下载数、 工具数与权限自述,已安装的标记出来并在有新版本时给「更新」。 安装前弹确认框,列出插件名、作者、声明能力,并明确写「插件与本机应用同等 权限,能读写你的文件并访问网络。安装后默认停用,需你手动启用」——这句是 整个信任模型的最后一环,用户点确认之前后端不会加载任何插件代码。 区块底部常驻信任提示,说明在线插件都经过人工审核与平台签名、安装时验签。 注册表不可达时只在本区块内提示,不影响 Skill 区块与本地已安装列表 (旧版后端没有 market 接口时同样走这条降级路径,已实测)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PLUGIN_SPEC §3 原本引用「见 §8」,但 §8 是版本演进而非分发章节,改为直接 指向 PLUGIN_DISTRIBUTION.md;§8 补记 v2.2(加载期收口 + 在线分发落地)。 AI_ARCHITECTURE 的「插件进程级运行时沙箱」TODO 改为已完成的分发链路安全 + 未来的进程外形态(MCP server),并显式写明进程内沙箱不可行的原因 (JEP 486 在 JDK 24 永久禁用 SecurityManager,OpenJDK 官方推荐进程外隔离)。 留着这条 TODO 会让人反复去追一个做不到的目标。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 27, 2026
zeweihan
added a commit
that referenced
this pull request
Jul 28, 2026
自 v0.8.0 累积 19 个 PR(#196–#216)。升 minor 而非 patch 的理由: 在线插件分发链路本版首次真正可用(#202 安全模型 + #203 生产签名公钥), 插件广场提级到左栏并引入 Skill 三档生效方式(#198),属新增用户可见能力。 发版前回归(本 worktree 冷启动): - backend mvn -B test:318 通过 / 0 失败 / 2 跳过(JDK 21) - desktop npm test:14/14 - frontend check:emits:通过 - app-e2e:30/30(基线 29,#216 新增剪贴板面板一步) - lowa-e2e:38/38(持平 v0.8.0 基线) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
zeweihan
added a commit
that referenced
this pull request
Aug 27, 2026
- #200 充值弹窗与账户卡重设计:RechargeDialog 视觉重做(副标题/档位卡/ 自定义金额前缀框/带金额主按钮/轮询呼吸点);账户卡改为「身份一行 + 动作按齐」(头像/显示名/用户名 + 刷新权益/退出登录) - #201 剪贴板「立即解锁」扣费后未解锁:UnlockHint 购买成功(含 already_owned) 广播 awd:entitlements-changed,ClipboardPanel 与暂存区用量订阅重拉—— hiddenCount/limited 是后端算的,不重拉横幅停在旧值 - #202 「优化者」卡对普通用户隐藏:桌面单机人人 isAdmin 挡不住,改按 optimizer.enabled(这台机器配没配 optimizer.*)门控 - #203 工作台空态美化:品牌图标白色圆角砖 + 淡绿光晕 + 标题/引导副文案 - #204 左 rail 图标拖动排序:railSort.js(awd_rail_order 全局键), applyRailOrder 套在停靠过滤之后,与跨 dock 停靠拖拽共存 - #205 退出/断开入口统一:「断开连接」废除,账户卡与顶栏头像下拉(恢复为 设置/退出登录两项)都走 utils/signOut.js 唯一编排;「解除授权」补说明文案 - #206 设置里「插件广场」导航项删除:入口收敛到左 rail 插件中心, /pages/plugin-market 独立页保留给直链 契约同步:check-navigation-contract 头像下拉断言更新为「恰好两项」; app-e2e J6.3 同步;五份领域文档(sidebar-shell/licensing-billing/ utility-tools/feedback-optimizer/plugin-marketplace)随改动更新。 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
上一轮讨论确定了方向:学 VS Code 的路子,把安全前移到分发链路。原因是运行时根本拦不住——插件 JAR 与宿主同 JVM 同权限,而 Java 的
SecurityManager已于 JEP 486 在 JDK 24 永久禁用,OpenJDK 官方给出的替代方案就是进程外隔离。VS Code 与 JetBrains 都明确不做运行时沙箱,靠的是签名 + 审核 + 封禁。配套官网 PR:aiworkdeck_website
feat/plugin-distribution(162a5d1 + e42949e)设计文档:
docs/PLUGIN_DISTRIBUTION.md一、先补三个加载期漏洞(28e175ec)
这三个与在线分发无关,本地手放插件的场景现在就会踩:
loadClass、静态初始化块与无参构造器都跑过了。管理员点「禁用」的预期是「别运行它」,必须在加载前拦住。现在元数据仍登记(管理页照常展示与启停),但不碰 JAR;运行中启用一个启动时被禁的插件会自动补加载。backendJars路径逃逸。manifest 由作者提供,"../../evil.jar"能指到插件目录外。新增resolveBackendJar()用 canonical path 校验(同时挡符号链接)。pluginSkillDirs改CopyOnWriteArrayList——同类集合都因 rescan clear+重填用了并发安全实现,这处是遗漏。PLUGIN_SPEC.md同步修正了会让读者高估安全性的措辞:permissions从「被授予」改为「自行声明」并加显式警告,「独立 ClassLoader」点明父加载器是宿主,补充禁用语义表、rescan 限制、桌面版插件目录落在用户可写的~/.aiworkdeck/plugins/。二、在线安装与验签(b3d5e984)
PluginMarketService按PLUGIN_DISTRIBUTION.md§7:markDisabledBeforeLoad再 rescan——新插件默认禁用,配合第一部分的「禁用即不加载」,用户点启用之前插件代码一行都不会执行。PluginRevocationService启动时与每 24 小时同步平台封禁列表,命中的已安装插件强制禁用。封禁不可撤销(setEnabled拒绝启用被封禁插件,只允许卸载),但不自动删文件——避免误封导致用户数据丢失。未配置
ai.plugins.registry-public-key时拒绝一切在线安装(默认留空,需随版本发布填入平台公钥)。三、广场插件分区(3779c6d2)
从占位换成真实列表,安装前弹确认框列出插件名、作者、声明能力,并写明「插件与本机应用同等权限,能读写你的文件并访问网络。安装后默认停用」。注册表不可达时只在本区块内降级提示。
关于扫描能力(在官网侧)
官网的静态扫描做 class 常量池字符串匹配,覆盖进程执行、TLS 篡改、反射突破、宿主内部访问、网络、文件、反序列化、原生库,另检出硬编码 IP 与明文 HTTP。
最有价值的是交叉验证:把实际引用与 manifest 的
permissions自述比对,声明与行为不符(如未声明network却引用Socket)会被标出。这让权限声明第一次具备约束力——它不再只是作者的自说自话,而是审核时的对照基准。验证
-Dcrosscheck.file开关控制,CI 无该文件时自动跳过。../与编码绕过。check:emits与build:h5通过;浏览器验证插件分区渲染、已安装/可更新标记、权限标签、注册表不可达的降级路径。部署前须做
AWD_PLUGIN_SIGNING_KEY,公钥填入application.yml的ai.plugins.registry-public-key(未配置时在线安装一律拒绝,属安全默认)。/api/registry/plugins系列接口。🤖 Generated with Claude Code