分发插件
你构建了一个插件;现在它需要离开你的机器,而不必要求安装它的人盲目信任你。ZeroClaw 的分发方案有两个独立层面:Ed25519 清单签名(是谁发布的)和注册表安装(它如何到达那里)。本页涵盖这两者,并结合 crates/zeroclaw-plugins/src/signature.rs、src/plugin_registry.rs 以及 host.rs 中的安装路径进行核对。
签名
什么是已签名的
签名涵盖规范清单字节。主机会解析 TOML,仅移除根级别中完全匹配的 signature 和 publisher_key 条目,保留文档的其余部分,并去除末尾空行(signature.rs 中的 canonical_manifest_bytes)。这使签名可以自嵌入:在不包含这些根字段的情况下对清单签名,然后添加这些字段;验证时会先移除它们再进行检查。
值得了解的两个后果:
.wasm组件本身不受签名保护。签名所证明的是清单:发布者所担保的名称、版本、能力和权限。当制品在传输过程中需要保证完整性时,请将其与注册表sha256摘要(如下)配对使用。- 名称类似于
config_schema.properties.signature的嵌套字段,以及同样带此前缀的根字段(如signature_algorithm),仍会被纳入签名。对保留内容重新格式化或重新排序会使签名失效。请在清单最终确定后再进行签名。 - 因此,诸如
config_schema.properties.api_token.x-secret之类的配置暴露标记属于受签名保护的策略。在公开配置暴露和受作用域限制的secrets.get访问之间切换工具或通道属性,需要重新构建并重新签名。 - 由之前的基于前缀的规范化器签名的软件包,仅当它们依赖于其中某个边界情况,或依赖于附加在已移除字段上的 TOML 装饰时,才需要重新签名。普通清单的签名内容保持不变。
键和进程
签名使用 Ed25519,并通过主机用于验证的同一 ring 基元实现。签名采用 base64url(无填充);公钥使用十六进制编码。该 crate 暴露了完整工具链(signature.rs):generate_signing_key 生成一个 PKCS#8 密钥对及其十六进制公钥,sign_manifest 对规范化字节生成 base64url 签名,而 public_key_hex 则从已存储的私钥中恢复公钥。当前没有用于签名的 CLI 封装;发布者会在其发布流水线中通过一个简短的 Rust 辅助程序来驱动这些函数。
签名后的清单随后会携带两个额外的 根级字段:signature(base64url 值)和 publisher_key(你的十六进制公钥)。将二者都放在第一个表头(包括 [config_schema])之前;根据 TOML 规则,在表头之后追加它们会使其成为该表的成员,主机将看到一个未签名的清单。
name = "my-plugin"
version = "0.1.0"
signature = "<base64url-signature>"
publisher_key = "<hex-public-key>"
[config_schema]
type = "object"
properties = {}
additionalProperties = false
希望信任您的运维人员会将该十六进制密钥添加到其 plugins.security.trusted_publisher_keys 列表中:
zeroclaw config set plugins.security.signature_mode strict
zeroclaw config set plugins.security.trusted_publisher_keys '["<your-key-hex>"]'
验证的行为如何
验证在发现和安装时都会运行(enforce_signature_policy 从 host.rs 调用);发现阶段会跳过失败的插件并记录日志,安装阶段则返回错误。从操作者角度看,模式矩阵如下:
| 模式 | 无符号 | 已签名,但密钥不受信任 | 已签名,签名无效 | 已签名并受信任 |
|---|---|---|---|---|
disabled | 加载 | 已加载,未检查 | 已加载,未检查 | 已加载,未检查 |
permissive | 加载时带有警告 | 加载时带有警告 | 加载时带有警告 | 已加载,已验证 |
strict | 已拒绝 | 已拒绝 | 已拒绝 | 加载 |
注意 strict 对你作为发布者的含义:严格模式下的运营方只有在你的精确 key 位于其受信任集合中且清单字节校验通过时才会加载你的插件。任何签名后的清单修改,无论是你自己还是分发路径中的任何人所做,都会导致安装失效。这就是其目的。
注册发布
安装路径是本地插件目录;registry 只是一个在命令执行时查询的 JSON 索引(zeroclaw plugin search / install)。这两个命令仅存在于编译时包含插件宿主的二进制文件中(参见 build features);预构建的发布二进制文件不包含此功能。默认索引为 zeroclaw-labs/zeroclaw-plugins 仓库的 registry.json;私有 registry 只需提供一个 URL 即可使用(每条命令可通过 --registry <url> 指定,或使用 ZEROCLAW_PLUGIN_REGISTRY_URL 环境变量,按照 src/plugin_registry.rs 中 registry_url 所定义的优先级顺序解析)。
一个注册表条目(crates/zeroclaw-plugins/src/registry.rs 中的 PluginRegistryEntry)包含:name、version、可选的 description 和 author、capabilities、归档 url,以及 zip 的可选 sha256 摘要。
归档契约
zeroclaw plugin install <name> 会解析入口,下载 zip,在存在摘要时验证摘要,安全解压,并将解压后的目录交给本地安装所使用的同一 PluginHost::install 路径。解压过程在设计上具有防御性(src/plugin_registry.rs),你的归档必须能通过它:
- zip 必须包含一个根级别的
manifest.toml,或者恰好一个包含该文件的嵌套插件目录。没有 manifest 或者多于一个都会导致归档被拒绝。 - 带有路径穿越、绝对路径或 Windows 驱动器前缀的条目名称会被拒绝。
- 下载在流式传输时受限(50 MiB),因此一个不提供
Content-Length的服务器无法强制进行无限缓冲;解压也受同一上限限制,因此 zip bomb 无法无限展开。
版本解析:当安装程序收到一个裸名称时,它会在索引中选择最后一个匹配的条目;固定的 name@version 会精确选择该版本。请有意地在你的注册表中按顺序排列重复的名称,最旧的放在前面。
搜索不是信任边界
zeroclaw plugin search 是对索引的未认证发现;它绝不会安装、启用或执行任何内容。安全性发生在安装时:摘要校验、安全解压、清单验证,以及操作员的签名策略,与本地路径安装完全相同。因此应相应地发布:在安装之前,默认认为一切都是不受信任的传输。
发布者清单
[!IMPORTANT] 编译后的
.wasm和.cwasm文件是二进制构件,通常每个都有数 MB。不要在没有 Git LFS 的情况下将它们提交到 git 源树中:每次重建都作为普通 blob 提交,会永久膨胀仓库历史,并且git diff/审查工具会因此卡住。把它们当作其他构建输出一样处理:将target/和*.wasm/*.cwasm添加到.gitignore,并改为通过发布构件或插件注册表归档分发。如果某个构件确实必须留在树中,请在首次提交之前使用 LFS 跟踪该模式(git lfs track "*.wasm")。
- 完成清单:名称、版本、功能,以及代码使用的最小权限集。
- 构建组件;对于 skill bundles,验证每个
SKILL.md的 frontmatter(发现会强制要求name和description)。 - 签名:生成或加载你的 Ed25519 密钥,对规范化的 manifest 字节进行签名,嵌入
signature和publisher_key。 - 压缩插件目录(一个清单,无路径技巧,低于 50 MiB)。
- 计算 zip 的 SHA-256 并发布带有该摘要的 registry 条目。
- 将你的公钥十六进制值发布在某个地方,以便操作者可以独立于 registry 进行验证(你的 repository、你的网站)。在
strict模式下,操作者信任的是密钥,而不是 registry。 - 每次发布时:提升
version,重新签名(版本行位于规范字节中),重新生成摘要,将新条目追加到旧条目之后。