docs/book/src/developing/how-plugins-work.md
This page explains the plugin system from an operator's point of view: how a plugin is discovered, what it is allowed to do, and how the host keeps an untrusted plugin contained. For the on-disk contract a plugin author implements (manifest fields, bridge exports, host functions), see Plugin protocol.
A plugin is a sandboxed WebAssembly module plus a manifest. The host loads it, reads the capabilities and permissions it declares, and exposes its tools to the agent only when the operator has turned the plugin system on. Nothing about a plugin is implicit: a plugin gets exactly the capabilities its manifest declares and the operator's policy allows, and nothing else. To build one yourself, start with the plugin guides.
Three properties hold at every layer:
[plugins] enabled = true. A default build with no plugin configuration runs
no plugin code.When the runtime builds its tool set, the plugin loader runs through these stages in order. A plugin that fails an earlier stage never reaches a later one.
[plugins] enabled is false, the loader does nothing. This is
the first and cheapest check.[plugins] plugins_dir, default ~/.zeroclaw/plugins/) for subdirectories
containing a manifest.toml.wasm_path that exists. A malformed manifest is
skipped with a warning, never loaded.[plugins.security] signature_mode and trusted_publisher_keys. A plugin
that fails the policy is dropped from the loaded set, not surfaced as a tool.The signature stage is the one most easily misconfigured, so it is worth understanding on its own.
Every plugin manifest may carry an Ed25519 signature and the hex-encoded public
key of the publisher who signed it. The operator decides how strictly that
signature is enforced through [plugins.security] signature_mode:
| Mode | What loads | Use when |
|---|---|---|
disabled | Every well-formed plugin, signed or not | Local development against plugins you built yourself |
permissive | Every well-formed plugin; unsigned, untrusted, and invalid signatures load with a warning | Migrating toward signing without breaking existing installs |
strict | Only plugins with a valid signature from a trusted publisher load | Any shared or production host |
In strict mode the manifest's publisher_key must appear in
[plugins.security] trusted_publisher_keys, and the signature must verify
against the canonical manifest bytes. A plugin that is unsigned, signed by an
untrusted key, or whose signature does not verify is dropped at discovery and
never becomes a tool. The default is disabled so a fresh local checkout works
without key management, but a host that loads plugins from anywhere you do not
control should run strict.
This policy is enforced uniformly: the same check that the host applies when you
list plugins is the check the agent runtime applies when it builds the tool set,
so a plugin you cannot see in strict mode is also a plugin the agent cannot
call.
A manifest declares two separate things, and the distinction matters.
tool, channel,
memory, observer, or skill. A tool plugin contributes tools the LLM
can call.The host grants permissions narrowly: a permission the manifest does not declare is a host function the plugin cannot reach. The HTTP-egress and per-plugin configuration boundaries (SSRF-guarded egress that cannot reach loopback, private, link-local, or cloud-metadata addresses or redirect into one; config injected per-alias so a plugin reads only its own resolved values and never the raw process environment or another plugin's secrets) are delivered by the companion plugin-hardening work and are documented alongside those changes. This page covers the signature-policy boundary.
All settings live under the plugins.* config paths and are set through any
config surface (zerocode, the gateway, or the CLI):
# Master switch. Nothing loads while this is false.
zeroclaw config set plugins.enabled true
# Where plugins are discovered (default: ~/.zeroclaw/plugins).
zeroclaw config set plugins.plugins_dir ~/.zeroclaw/plugins
# disabled | permissive | strict
zeroclaw config set plugins.security.signature_mode strict
# Hex-encoded Ed25519 public keys allowed to publish plugins under strict mode.
zeroclaw config set plugins.security.trusted_publisher_keys '["a1b2c3d4e5f6..."]'
A host meant to load third-party plugins should set enabled = true,
signature_mode = "strict", and list only the publisher keys you trust. A host
that runs only plugins you build yourself can leave signature_mode at its
disabled default during development and tighten it before the host is shared.
Even with every permission granted, the sandbox bounds a plugin:
These bounds hold regardless of what the plugin's own code attempts, which is what makes it safe to load a plugin you did not write, provided your signature policy says you trust whoever published it.