插件的工作原理
本页从运维者的角度解释插件系统:如何发现插件、插件被允许执行什么操作,以及宿主如何将不受信任的插件限制在可控范围内。关于插件作者实现的磁盘协议约定(清单字段、bridge 导出、宿主函数),请参见 Plugin protocol。
系统的形态
插件是一个沙盒化的 WebAssembly 模块加上一个清单。宿主会加载它,读取它声明的能力和权限,并且只有在操作员打开插件系统时,才将其工具暴露给代理。插件中的任何内容都不是隐式的:插件获得的能力仅限于其清单所声明且操作员策略允许的那些,除此之外没有别的。要自己构建一个,请从 plugin guides 开始。
每一层都满足三个属性:
- 默认禁用。 插件系统不会加载任何内容,除非
[plugins] enabled = true。没有插件配置的默认构建不会运行任何插件代码。 - 默认拒绝。 插件只有在其清单中声明了匹配的权限,才能访问宿主能力(HTTP 出站、配置、内存)。未声明的能力不可达,而不仅仅是未使用。
- 已由策略验证。 未签名或不受信任的插件是否允许加载完全由操作员决定,在配置中一次性设置,并在发现时统一强制执行。
插件加载的生命周期
当运行时构建其工具集时,插件加载器会按顺序经历以下阶段。未通过前一阶段的插件不会进入后一阶段。
- 门控。 如果
[plugins] enabled为 false,加载器不会执行任何操作。这是第一道也是最便宜的检查。 - 发现。 加载器会扫描解析后的插件目录(
[plugins] plugins_dir,默认为~/.zeroclaw/plugins/),查找包含manifest.toml的子目录。它会跳过以点号开头的目录,包括zeroclaw plugin install在组装软件包时使用的隐藏暂存目录,从而确保未写完的安装内容永远不会被加载。 - 验证结构。 每个清单必须至少声明一项能力,且非 skill 插件必须指定一个受限的相对路径
wasm_path。包含路径遍历和符号链接的路径会被拒绝。格式错误的清单会被跳过并发出警告,绝不会被加载。 - 强制执行签名策略。 每个插件都会根据已配置的
[plugins.security] signature_mode和trusted_publisher_keys进行检查。未通过该策略的插件会从已加载集合中移除,不会作为工具展示。 - 准入可执行字节。 主机会一次性打开受限组件,验证已声明的任何
wasm_sha256,并保留这些确切字节。在strict模式下,签名清单必须声明该摘要。适配器编译已准入的缓冲区,而不是重新打开其路径。在 Unix 上,主机一次性打开包目录,并通过该句柄逐个路径组件地访问组件,从不跟随符号链接,因此在准入期间移动或替换目录要么使准入失败,要么使读取仍限定在已受限的包内。组件必须是常规文件,FIFO 或设备会立即失败,而不是阻塞。包目录和清单在准入开始时仍通过路径名查找;在strict模式下,签名的wasm_sha256用于将清单与字节绑定。其他平台保留路径名检查,这些检查在运行时可拒绝仍然存在的替换,但无法使查找变为原子操作,也无法防止 FIFO 在打开时阻塞。 - 注册工具。 通过筛选的工具插件会被封装为智能体工具,并追加在内置工具之后。若某插件的包名或工具名与已注册的工具冲突,该插件将被拒绝注册并给出警告;参见 工具名称冲突。工具插件和技能插件采用_自动发现_机制,因此只有在设置
[plugins] auto_discover = true(默认为false,即默认关闭)时才会执行此枚举。若设置了enabled = true但auto_discover = false,则不会加载任何插件工具或技能;不过,你在[channels.plugin.<alias>]下声明的通道(无论是手动配置,还是通过zeroclaw plugin bind配置)仍会激活。技能加载器同样受auto_discover开关控制。
签名阶段是最容易配置错误的阶段,因此值得单独理解。
恢复旧版安装
运行 zeroclaw plugin migrate 可将旧版软件包目录迁移到 [plugins] plugins_dir。自动恢复会搜索已配置数据根目录下的 plugins/、未拆分的 <install>/workspace/plugins,以及实际 V2 生成方的 <install>/data/plugins 目标位置,即使 data 字段不同也是如此。新的 V2 文件系统拆分会将插件移动到宿主数据目录,因此新建和恢复的拆分仍可访问,而无需将默认代理实时可写的工作区用作恢复来源。宿主数据目录不一定是已配置的运行时插件目录,后续的迁移由 plugin migrate 负责执行。已配置的目标及其规范别名会被排除,重复的根目录只会报告一次。
对这些旧版根目录的受支持别名会被跟随处理。规范目标别名会被排除,同一根目录的别名会去重。与已知 agent 工作区存在重叠的根目录会被排除,无论其位于该工作区内部还是包含该工作区,包括别名在内。已知角色来自每个已配置 agent 的实际工作区路径、隐式默认值以及历史默认工作区。已存在的路径通过规范方式进行比较;尚不存在的已声明工作区则按规范化的词法路径进行保守比较,并在可用时解析已存在的祖先别名。选择过程不会创建缺失的工作区,也不会将已声明的路径视为旧版迁移的证据。
这是根目录选择:现有的迁移器仍会使用自身的包检查重新扫描符合条件的根目录。恢复提示和迁移器都要求普通的子包目录,与运行时发现保持一致。子目录符号链接保留在源位置,即使在包含普通包的根目录中,也不会被移动或实体化。位于普通包目录内的 manifest 别名仍然沿用现有的 manifest 加载和准入检查。
这并不能提供全局的禁止跟随(no-follow)保证、经过认证的快照,也不构成新的并发防覆盖(no-clobber)契约。其现有的同名目标检查会将发生冲突的软件包保留在源位置,供操作人员处理并重试。
目录迁移辅助工具不会修改插件配置、授权、发布者信任或启用状态。命令执行前的配置加载会保留其常规的初始化和迁移副作用。移动软件包后,它们将有资格进行常规发现:当插件已启用且 auto_discover = true 时,工具插件和技能插件可能变为可用,但仍需通过准入检查。迁移操作本身既不验证 ABI 兼容性,也不验证来源身份。
Packages 已拆分到 agent 工作区中
更早的版本可能会把软件包移动到 <install>/agents/default/workspace/plugins。该历史位置以及自定义的实时代理工作区,不再是 plugin migrate 的自动来源。清单文件、匹配的备份名称或调用方编写的标记,都不足以授权对整个目录进行整体接管。请先检查你打算接管的每个软件包,然后使用现有的安装命令逐个选择其单独的目录:
zeroclaw --config-dir '/operator/install' plugin install \
'/operator/install/agents/default/workspace/plugins/package-name'
请使用实际的安装路径和包路径,不要传入整个 plugins/ 目录。同样,可以通过其实际路径显式选择备份中的包。授权来源是操作者的安装操作,而不是路径表面上的历史记录。此功能需要带有插件 CLI 支持的构建版本。
这是兼容软件包的全新安装。宿主会应用当前的清单、签名、负载和配置模式规则,默认情况下,CLI 会根据其 WIT ABI 及限制对可执行软件包进行加载检查。在严格策略下,未签名或不受信任的软件包、不完整的旧版清单、不兼容的组件或不受支持的旧版配置行可能会被拒绝;重试前请遵循诊断信息和类型化配置迁移指南。不提供历史格式转换或签名策略绕过功能。已安装的同名软件包会被拒绝,而不会被静默替换。
显式安装会保留源目录。它会写入已通过审核的清单和可执行负载,并复制声明的技能内容。任意辅助文件不构成完整目录的恢复契约,技能复制也不是不可变的生成快照。对于需要宿主持有状态的工具绑定,安装可以创建缺失的类型化配置行,并将声明的 HTTP 出口目标写入新行。现有行和授权不会被自动扩展,旧的包名行也不会被静默转换。在启用运行时使用之前,请检查这些安装影响,并配置所需的值。因此,显式采用与自动迁移助手中保持配置/授权不变的行为是相互独立的。
签名策略
每个插件清单都可以携带一个 Ed25519 签名,以及签署它的发布者的十六进制编码公钥。运维人员通过 [plugins.security] signature_mode 决定对该签名的强制执行有多严格:
| 模式 | 什么加载 | 用于 |
|---|---|---|
disabled | 每个格式正确的插件,无论是否已签名 | 针对你自己构建的插件进行本地开发 |
permissive | 每个格式正确的插件;未签名、不受信任,以及无效签名的插件都会在加载时发出警告 | 向签名迁移,同时不破坏现有安装 |
strict | 只有来自受信任发布者且签名有效的插件会加载 | 任何共享或生产主机 |
在 strict 模式下,清单中的 publisher_key 必须出现在 [plugins.security] 的 trusted_publisher_keys 中,并且签名必须能够针对规范化的清单字节验证通过。可执行插件还必须声明一个已签名的 wasm_sha256,且与实际准入的字节完全一致。任何未通过这些检查的插件都会在发现阶段被丢弃,不会成为工具。默认值为 disabled,因此全新的本地检出无需密钥管理即可运行。但如果宿主程序会从你无法控制的来源加载插件,则应使用 strict 模式。
此策略统一强制执行:主机在你列出插件时应用的检查,与代理运行时在构建工具集时应用的检查是同一个,因此你在 strict 模式下看不到的插件,代理也无法调用。
工具名称冲突
注册时会拒绝名称冲突,而不是让一个工具覆盖另一个。工具插件按包名顺序注册,宿主会对每个插件检查两次:
- 包名。 如果插件的包名(清单中的
name)与已注册的工具相同,则会在其组件实例化之前被拒绝。宿主会记录一条WARN事件,其attributes中包含error_keyplugin_package_name_conflict。 - 工具名称。 客户端组件自行声明工具名称,因此宿主只有实例化该组件以读取其元数据时才能得知名称。若插件的工具名称与已注册的工具相同,则该插件不会被注册。宿主会在其
attributes中记录一条WARN事件,其error_key为plugin_tool_name_conflict。即使某个工具名称尚未被占用,只要其中包含__,也会被拒绝,因为 MCP 工具命名为<server>__<tool>,且注册时晚于插件工具。此类拒绝带有error_keyplugin_tool_name_reserved。
被检查的名称包括注册表构建时已注册的工具(包括同一轮处理中先前被接受的插件工具),以及当 [pipeline] enabled = true 时的 execute_pipeline。这并不是所有内置名称的固定列表:某个构建未注册的内置工具(例如其配置段被禁用)在该构建中不会被保留。除 MCP 命名空间外,在插件注册之后才加入注册表的工具不在此检查范围内。请确保插件包和工具的名称本身即是唯一的,不要依赖此检查。
功能与权限
清单声明两件独立的事情,而这种区别很重要。
- Capabilities 是插件属于哪种扩展类型:
tool、channel、memory、observer或skill。tool插件会提供 LLM 可以调用的工具。 - 权限是插件代码在运行时可访问的宿主服务:HTTP 外联、配置、内存。清单未声明的权限,就是插件无法访问的宿主功能。
主机以严格范围授予权限:清单未声明的权限对应的主机函数,插件无法调用。配置根据主机签发的实例身份解析,因此插件无法选择其他包或绑定,也绝不会读取原始进程环境。http_client 控制出站 wasi:http 接口;共享的 SSRF 防护出站策略仍属于配套的插件强化工作。本页介绍签名策略边界。
配置参考
所有设置都位于 plugins.* 配置路径下,并可通过任何配置界面(zerocode、网关或 CLI)进行设置:
# 主开关。在这为 false 时不会加载任何内容。
zeroclaw config set plugins.enabled true
# 在运行时加载自动发现的工具和技能插件(默认值:false)。
# Without this, `enabled = true` activates only explicitly-declared channels.
zeroclaw config set plugins.auto_discover true
# 插件的发现位置(默认:~/.zeroclaw/plugins)。
zeroclaw config set plugins.plugins_dir ~/.zeroclaw/plugins
# 已禁用 | 宽松 | 严格
zeroclaw config set plugins.security.signature_mode strict
# 允许在严格模式下发布插件的十六进制编码 Ed25519 公钥。
zeroclaw config set plugins.security.trusted_publisher_keys '["a1b2c3d4e5f6..."]'
旨在加载第三方插件的宿主应设置 enabled = true、signature_mode = "strict",并仅列出你信任的发布者密钥。若还要加载自动发现的工具和技能插件,还应设置 auto_discover = true;它默认为 false,因此仅设置 enabled = true 只会启用你在 [channels.plugin.<alias>] 下声明的通道,不会启用任何插件工具或技能。仅运行你自行构建的插件的宿主,在开发期间可以让 signature_mode 保持其 disabled 默认值,并在共享宿主之前收紧设置。
渠道绑定位于 plugins.* 之外,位于 [channels.plugin.<alias>] 下。你可以手动编写该表,也可以让 zeroclaw plugin bind <package> --channel-alias <alias> 为已安装的渠道包自动写入。该命令还会填充实例的 [[plugins.entries]] 行。在根据声明了目标地址的清单创建该行之前,它要求显式提供 --egress declared 或 --egress none 标志,并且需要持有能够访问这些目标地址的传输(http_client、websocket_client 或 socket_client)。命令执行后,会报告该实例在启动前仍需完成的事项。它从不设置 plugins.enabled,也从不编辑智能体的 channels。参见 绑定渠道实例。
运行发布版二进制文件中的插件
插件系统处于实验阶段:wit/v0 契约并不稳定,因此插件必须针对运行它的发行版所附带的 WIT 进行构建,版本不匹配会在安装时的加载检查中暴露,而不会在清单(manifest)中体现。在此限制之内,面向 64 位桌面和服务器目标的发行版归档包含插件宿主以及用于加载 .wasm 组件的编译器,并且每个发行版在发布前都会从这些归档中各执行一个插件。32 位 ARM 归档、实验性的 Android 归档,以及使用 Cargo 默认配置的源码构建均不包含插件宿主;协议页面列出了所有构件。要在发行版二进制文件上运行插件,需要运营人员做出四项决定,每一项默认均为关闭。
-
安装软件包。 本地软件包使用
zeroclaw plugin install <directory>,插件注册表中的软件包使用zeroclaw plugin install <name>。安装过程会校验清单、运行签名策略、在清单声明了负载摘要时校验该摘要,并依据本主机的 WIT 实例化组件(使用--no-verify时跳过此步骤)。任何一项检查失败的软件包都不会被安装。安装(除非使用--no-verify)、zeroclaw plugin info <name>和zeroclaw plugin list --verify都会编译并实例化组件,无论插件系统是否已启用。实例化不会调用组件的任何导出函数,但会运行组件核心模块中包含的起始函数,该函数在没有文件访问权限和网络访问权限的 Store 中执行,并受每次调用的燃料、内存和时间限制。 -
启用插件系统。 先执行
zeroclaw config set plugins.enabled true,再执行zeroclaw config set plugins.auto_discover true,以便枚举工具和技能插件。在两者都设置之前,已安装的工具或技能包不会产生任何作用。两者都设置后,每次构建工具注册表时,都会编译并实例化每个已准入的工具组件,并请求其名称、描述和参数 schema。这一过程发生在任何审批之前,运行的是 guest 代码,且受该插件包声明的权限和出站(egress)策略约束;随后该工具会被提供给模型。通道插件包需要
plugins.enabled、一条[channels.plugin.<alias>]声明,以及一个启用的 agent,且该 agent 的 channels 列表中包含plugin.<alias>;auto_discover在此不起作用。对于通过provides镜像原生通道的插件包,在[channels.<provides>.<alias>]下的激活会被记录,但尚未构建;若同一插件包绑定到[channels.plugin.<alias>]下,则会运行。 -
逐次批准每个工具调用。 插件工具调用与其他未预先批准的工具一样,需要操作员批准:CLI 会在终端中弹出提示,渠道则会发送审批卡片。若要让智能体无人值守地调用某个插件工具,请将该工具添加到该智能体风险配置文件的
auto_approve列表中,例如zeroclaw config set risk_profiles.default.auto_approve '["<tool-name>"]'。config set会替换整个列表:配置加载时,内置默认值会被合并回risk_profiles.default,但你自行添加的条目必须重新写入;使用其他名称的配置文件则只保留你传入的内容。level = "full"和*通配符会预先批准所有不在always_ask中的工具;“始终允许”(Always)选项会在当前 CLI 会话或渠道对话轮次的剩余时间内预先批准该工具,但always_ask中的工具仍会每次都提示。若没有审批方,调用会被拒绝且不会执行。 -
选择签名策略。
signature_mode默认保持为disabled,因此您自行构建的插件无需密钥管理即可安装。在strict模式下,只有由trusted_publisher_keys中的密钥签名的软件包才会加载;由于官方注册表目前发布的是未签名软件包,在其发布者完成签名之前,这些软件包在strict模式下都无法安装。
编译某个组件需要一块在写入之后会变为可执行的内存。禁止这种行为的主机,例如设置了 MemoryDenyWriteExecute=yes(Nix 模块的默认值)的 systemd 单元,会拒绝所有插件加载,因为编译器无法将代码映射为可执行内存。守护进程的其余部分不需要此类映射,因此可以继续运行。
要回滚,请使用 zeroclaw plugin remove <name> 删除插件包;或者设置 plugins.enabled = false,在不卸载的情况下停止加载所有插件。若要恢复到不包含插件宿主的版本,请使用 zeroclaw update --version <previous> --force 将二进制文件固定到该版本;如果不加 --force,更新程序会拒绝切换到更旧的版本。更新程序会将 Linux musl 主机解析为 glibc 归档包,因此 musl 安装(例如通过包管理器安装的)需要通过重新安装上一个归档包或软件包来回滚。
插件仍然无法做到什么
即使授予了所有权限,沙箱仍会约束插件:
- 它作为一个 WebAssembly 模块运行,无法对宿主进程或其根工作区之外的文件系统进行环境访问。网络外出流量受 HTTP 权限控制;由 SSRF 防护的外出边界本身由配套的插件加固工作提供。
- 可信工具或通道插件可在已授权的服务调用期间,通过其作用域限定的
secrets.get导入读取架构指定机密的明文。工具会在execute期间获得访问权限。通道会在configure和运行调用期间获得config.get和secrets.get;同一次调用中的读取使用同一个规范修订版本,因此同一绑定的公开配置/机密轮换会在下一次操作时可用。实例化和静态元数据发现不能使用任一导入。主机可防止公开配置注入和跨实例选择,但返回明文的导入无法阻止恶意来宾保留其读取的内容。符合要求的通道插件必须在每个使用点解析配置和凭据。 - 无法接受已注册工具的名称。注册时会拒绝冲突的插件,而不会注册其工具,具体范围见工具名称冲突。
沙箱和命名空间边界不受插件代码尝试执行的任何操作影响。相反,不保留规则属于受信任的通道插件契约的一部分,这也是发布者审核和签名策略仍然重要的原因。