Web 插件市场
Web 应用带有一个插件市场,用于把 SheetForge 插件安装进浏览器,另外还有一条独立的路径,用于从一个 GitHub URL 旁加载一个未经审核的插件。同一个市场,用 UIToolkit 重新实现之后,也随 Unity 一起发布。
注册表——一份静态文件,两个读取方
市场列表来自一份单一的静态注册表文件(public/registry/plugins.json),Web 应用和 Unity 内的市场窗口都会读取它。因为只有一份列表,两个界面展示的目录永远一致。
SheetForge 本身不托管插件的二进制文件——每位作者自己的 GitHub release 才是托管方。注册表条目只是元数据加一个固定的哈希值,而不是插件的一份副本。
哈希锁定——在核心看到任何一个字节之前先校验完整性
每一条注册表条目都携带一个在插件被批准时捕获的 SHA-256 值。在获取插件时,每一个收到的字节都会先对照这个哈希值校验一遍,然后才会真正抵达核心。哪怕只有一个字节不匹配,也会被拒绝。
正是这一点,让"二进制文件存放在别人的 GitHub 上"这件事变得安全:release 可以从任何地方被重新下载,但只有精确匹配、已批准的那些字节才会被加载。
通过服务器代理实现一键安装
浏览器无法直接获取 GitHub release 资源——跨域(CORS)规则会阻止这一点。因此获取过程会经由一条服务器代理路由(api/market/artifact,通过 lib/market/install.ts 中的 ArtifactProxy 接通),由服务器端负责获取这个 release 并把它流式传回。因此安装只需要一次点击。
一条手动下载再上传的路径依然作为次要的兜底方案保留,供代理无法访问某个 release 时使用。它会经过完全相同的哈希校验——一个上传的文件所获得的信任度,并不会高于一个经代理获取的文件。
兼容性关卡——在完整性校验之后、在注册之前
通过哈希校验,只能证明这些字节就是被批准的那些字节。它并不能说明这个宿主是否能读懂这个插件的格式——那是第二个、完全独立的问题。
答案来自已验证的 DLL 本身:一个程序集级别的声明,说明它是针对插件格式的哪一代构建的,以及它要求的最低宿主版本。
- 列表只负责宣传,真正决定的是程序集本身。 一条注册表条目携带着同样的两个值(
pluginFormat、minHost),因此目录可以在你下载之前就展示它们。而这道关卡本身读取的是刚刚通过校验的那些字节中的声明。一条列表可能是过期的;一份已编译的声明不会。 - 一道关卡,三条路径。 市场安装、旁加载,以及一个普通的本地文件,问的都是同一个判定条件,因此不存在某条路径悄悄接受了另一条路径会拒绝的东西。
- 按程序集,要么全部,要么全不。 一个被拒绝的程序集什么都不会注册——绝不会只注册一半的契约——拒绝原因会说明这个程序集声明了什么、这个宿主实际读到了什么。
- 没有声明也没关系。 一个在该特性出现之前就构建好的插件,会被当作最早的一代、不带任何宿主要求来读取,因此它的加载方式和过去完全一样。
缓存与卸载
通过哈希校验的字节会被缓存在浏览器中(IndexedDB),因此一个在列表中被启用的插件,会在之后的访问中自动加载——你只需要启用它一次。
禁用或移除一个插件会立即生效:应用会让内核把该程序集从插件组合中移除并重建会话,因此它的单元格类型、枚举、模板、颜色预设、工作室操作和画布叠加层无需刷新就会消失。(.NET 本身不会卸载程序集——字节仍驻留在页面中,但不再起作用;如果再次启用同一个插件,内核会重新激活驻留的程序集,而不是第二次加载它。)
从一个 GitHub URL 旁加载一个未经审核的插件
与经过筛选的注册表分开,你可以把一个任意的 GitHub URL 交给应用,旁加载一个未经审核的插件(lib/market/sideload.ts,由 api/market/github 提供服务)。这适用于试用一个不在目录中的插件。
因为这条路径接受的是一个用户提供的 URL,而不是一条经过审查的注册表条目,所以它是唯一直接强制执行 SSRF 防护的地方:
- 这个 URL 必须解析到一个 GitHub 家族主机——
github.com、raw.githubusercontent.com、release-assets.githubusercontent.com等等——采用精确匹配、全部小写。 - 任何会泄露进内部网络的重定向,都会在这道边界上被阻止。
旁加载得到的产物,会被缓存在一个独立的归属标签下(sideload:)。因此一个未经审核、恰好与某条注册表条目字节相同的插件,会与那条经过筛选的条目保持分离——一次旁加载永远不会冒充成一个已批准的插件。
以 GitHub 作为导入来源
两条路径都会让插件字节经过同一套管线:
- 注册表安装会通过服务器代理,拉取托管在作者 GitHub release 上的产物,然后对照固定的 SHA-256 进行校验。
- 旁加载接受一个原始的 GitHub URL,被限制在 GitHub 家族主机范围内,然后用同样的方式校验这些字节。
在两种情况下,浏览器对 GitHub release 资源的 CORS 限制都在服务器端被处理掉了,而完整性则由客户端在核心加载任何内容之前所做的哈希校验来保证。
Unity 内的市场
Unity 的插件市场是用 UIToolkit 重新构建的,外观向 Web 端的市场看齐——一个卡片网格配一个详情面板,读取的是同一份静态注册表,具有相同的哈希不匹配拒绝机制,以及相同的完全信任安装提示。
登录,以及保存在浏览器中的插件列表与预设,依然仅限 Web 端;Unity 窗口只是目录与安装界面。
相关页面
- SheetForge Web——这个市场所在的浏览器应用
- 插件开发——构建一个市场条目所指向的插件