プラグインの配布
プラグインを作成したら、それをインストールする人々に無条件の信頼を求めることなく、自分のマシンから配布できるようにする必要があります。ZeroClaw の配布の仕組みには、2 つの独立したレイヤーがあります。Ed25519 マニフェスト署名(誰がこれを公開したか)とレジストリインストール(どのように配布されるか)です。このページでは、crates/zeroclaw-plugins/src/signature.rs、src/plugin_registry.rs、および host.rs 内のインストールパスと照合しながら、両方について説明します。
署名
署名済み
署名は正規マニフェストのバイト列を対象とします。ホストはTOMLを解析し、ルートにあるsignatureとpublisher_keyのエントリだけを削除し、ドキュメントの残りの部分を保持して、末尾の空行を削除します(signature.rs内のcanonical_manifest_bytes)。これにより署名を自己埋め込み可能にします。つまり、これらのルートフィールドを含めずにマニフェストに署名してから追加し、検証時にはチェック前にそれらを削除します。
結果として生じる影響:
- 署名の前に、コンポーネントの SHA-256 をルートの
wasm_sha256フィールドに設定してください。これにより、署名がそのダイジェストをマニフェストに結び付けます。ホストは、受け入れてコンパイルする正確なバイト列に対してこれを検証します。strict モードでは、これを含まない実行可能マニフェストは拒否されます。以下のレジストリのsha256は、転送中の配布アーカイブを独立して保護します。 config_schema.properties.signatureのような名前を持つネストされたフィールドや、signature_algorithmのように同様のプレフィックスが付いたルートフィールドは、引き続き署名対象になります。保持されたコンテンツの再フォーマットや並べ替えを行うと、署名が無効になります。マニフェストを確定した後、最後に署名します。- したがって、
config_schema.properties.api_token.x-secretのような設定公開マーカーは、署名対象となるポリシーです。ツールまたはチャネルのプロパティを、公開設定への露出とスコープ付きsecrets.getアクセスの間で変更するには、再ビルドして再署名する必要があります。 - 以前のプレフィックスベースの正規化処理によって署名されたパッケージは、そうしたエッジケースのいずれか、または削除されたフィールドに付加された TOML の装飾に依存していた場合にのみ、再署名が必要です。通常のマニフェストでは、署名対象の内容は変わりません。
キーとプロセス
Ed25519 署名は、ホストが検証に使うのと同じ ring プリミティブで行われます。署名は base64url(パディングなし)、公開鍵は hex エンコードです。このクレートは完全なツールチェーン(signature.rs)を公開しています。generate_signing_key は PKCS#8 鍵ペアとその hex 公開鍵を生成し、sign_manifest は正規化されたバイト列に対する base64url 署名を生成し、public_key_hex は保存された秘密鍵から公開鍵を復元します。sign_manifest_document は両方の手順を実行し、ルートの 2 フィールドがすでに設定された署名済みマニフェストを返します。zeroclaw plugin sign というコマンドは存在しません。パブリッシャーはリリースパイプラインからこれらの関数を呼び出します。このクレートには既製のヘルパー crates/zeroclaw-plugins/examples/sign_manifest.rs が含まれています。ZeroClaw のチェックアウトから、プラグインディレクトリへのパスを指定して実行してください。秘密鍵は両方のツリーの外に保管してください。リリースごとに新しい個別のパッケージディレクトリと新しいアーカイブパスを使用してください。ヘルパーが署名済みマニフェストを書き込む際にそれらを作成します:
cargo run -p zeroclaw-plugins --example sign_manifest -- keygen ~/.keys/my-plugin.pk8
cargo run -p zeroclaw-plugins --example sign_manifest -- \
sign path/to/my-plugin/manifest.toml path/to/my-plugin-package/manifest.toml \
--key ~/.keys/my-plugin.pk8 --payload path/to/my-plugin/my-plugin.wasm
cp path/to/my-plugin/my-plugin.wasm path/to/my-plugin-package/my-plugin.wasm
(cd path/to/my-plugin-package && zip -r ../my-plugin.zip .)
このレシピはwasm_path = "my-plugin.wasm"を前提としています。--payloadはコンポーネントのハッシュを計算しますが、コピーはしません。zipを作成する前に、そのバイト列と必要なその他のアセットを、マニフェストで宣言されたパッケージディレクトリからの相対パスにコピーしてください。アーカイブするのは署名済みのmanifest.tomlを含むそのディレクトリのみです。未署名の元のマニフェストは含めず、署名後に署名済みマニフェストを編集しないでください。
keygen は新しい秘密鍵を書き出します。必要に応じてディレクトリを作成し(Unix では所有者のみ読み取り可能)、既存のファイルは上書きを拒否します。その後、16 進数の公開鍵を表示します。sign は --payload が指定されている場合、コンポーネントの SHA-256 を wasm_sha256 に記録し、署名を行い、ホストの検証器で署名を検証し、署名済みマニフェストを書き出して、同じ公開鍵を表示します。wasm_path を宣言するマニフェストについては、wasm_sha256 が記録されていない場合に警告を出します。strict モードではそのようなパッケージは拒否されるためです。wasm_path を持たないマニフェスト(スキルバンドルなど)は --payload を受け付けません。その場合、ホストはダイジェストを拒否します。
On Windowsでは、鍵ファイルはそのディレクトリのACLを継承します。秘密鍵は、発行者のみが読み取れるディレクトリに保管してください。
署名済みマニフェストには、追加の root フィールドが2つ含まれます。signature(base64url 値)と publisher_key(16進数の公開鍵)です。[config_schema] を含む最初のテーブルヘッダーより前に、両方を配置してください。テーブルヘッダーの後に追加すると、TOML の規則ではそれらはそのテーブルのメンバーとなり、ホストは署名なしのマニフェストとして認識します。
手動で署名後にフィールドを埋め込む場合は、別のルートフィールドの直後に配置してください。ホストのTOMLエディタは、次のエントリまたはテーブルヘッダーの直前に空行またはコメントを付加することがあります。フィールドが、署名したマニフェストに既に存在していた空行またはコメントの直下に入ると、検証時にその行がフィールドとともに削除されます。残りのバイト列は署名時の内容と一致しなくなり、厳格モードではパッケージが拒否されます。フィールドの直上に追加した空行またはコメントは、フィールドとともに削除されるため無害です。フィールドの下に追加したものは次のエントリまたはテーブルヘッダーに付くため、署名されたバイト列が変わります。そのため、フィールドの下には何も追加しないでください。sign_manifest_document とヘルパーは、署名の前にフィールドを配置するため、この問題は発生しません。
name = "my-plugin"
version = "0.1.0"
wasm_path = "my-plugin.wasm"
wasm_sha256 = "<64文字の16進数コンポーネントSHA-256>"
signature = "<base64url署名>"
publisher_key = "<16進数の公開鍵>"
[config_schema]
type = "object"
properties = {}
additionalProperties = false
あなたを信頼するオペレーターは、その 16 進数キーを plugins.security.trusted_publisher_keys リストに追加します:
zeroclaw config set plugins.security.signature_mode strict
zeroclaw config set plugins.security.trusted_publisher_keys '["<your-key-hex>"]'
検証の動作
検証は検出時とインストール時の両方で実行されます(host.rs から enforce_signature_policy が呼び出されます)。検出時は失敗したプラグインをスキップしてログに記録し、インストール時はエラーを返します。オペレーター側から見たモードマトリックスは次のとおりです。
| モード | 符号なし | 署名済み、キーは信頼されていません | 署名済み、署名が無効 | 署名済み・信頼済み |
|---|---|---|---|---|
disabled | ロード | ロードされますが、チェックされません | ロードされますが、チェックされません | ロードされますが、チェックされません |
permissive | 警告付きで読み込み | 警告付きで読み込み | 警告付きで読み込み | 読み込み済み、検証済み |
strict | 却下されました | 却下されました | 却下されました | ロード |
パブリッシャーとしてのあなたにとって strict が何を意味するかに注意してください: strict モードのオペレーターは、あなたの正確なキーが信頼セットにあり かつ マニフェストのバイトが検証される場合にのみ、あなたのプラグインを読み込みます。署名後のマニフェストの編集は、あなたによるものであれ配布経路上の誰かによるものであれ、インストールを壊します。それがポイントです。
レジストリの公開
インストール先はローカルのプラグインディレクトリです。レジストリは、コマンド実行時に参照されるJSONインデックスにすぎません(zeroclaw plugin search / install)。両コマンドは、プラグインホストがコンパイルに含まれているバイナリにのみ存在します(ビルド機能を参照)。64ビットのデスクトップ向けおよびサーバー向けターゲットのリリースアーカイブにはこれが含まれており、すべてのリリースでは公開前に、それぞれからプラグインを1つ実行して確認します。32ビットARMアーカイブ、実験的なAndroidアーカイブ、およびデフォルトのソースビルドにはこれが含まれません(アーティファクトごとの表にすべてのアーティファクトが記載されています)。
どちらのコマンドでインデックスを取得しても、ローカルにキャッシュされます。zeroclaw plugin list は、インストール済みパッケージとキャッシュを、ネットワークリクエストを行わずに組み合わせます。任意のバージョン文字列が新しいかどうかを推測するのではなく、インストール済みのバージョンとレジストリのバージョンを別々に表示します。デフォルトのインデックスは zeroclaw-labs/zeroclaw-plugins リポジトリの registry.json です。プライベートレジストリは 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 だけで、この場合は指定されていれば検証されます。一方、zeroclaw quickstart の Plugins ステップは、ダイジェストのないエントリを拒否するため、すべてのエントリにダイジェストを公開してください。
パッケージ名とCargoフィーチャ
オペレーターは、パッケージをレジストリの name でインストール、確認、設定します。この name は、見た目が似ている場合でも Cargo feature ではありません。公式の Discord チャネルプラグインはパッケージ discord で、zeroclaw plugin install discord でインストールします。channel-discord は、組み込みの Discord チャネルをバイナリにコンパイルする Cargo feature です(チャネルの概要を参照)。channel-discord という名前のパッケージは存在しません。FND-001 と FND-004 はパッケージより前に作成されたもので、これを channel-discord および channel-discord.wasm と呼んでいます。これらは discord パッケージを指すものとして読んでください。
選定した名前は初回リリース前に決め、以降は変更しないでください。すべてのインスタンスキーはこの名前から導出されるため、新しい名前で再公開されたパッケージは、オペレーターが既に設定したインスタンスについて、設定、エグレス許可、保存済みの状態を一切引き継げなくなります。
アーカイブコントラクト
zeroclaw plugin install <name> は、エントリを解決し、zip をダウンロードし、ダイジェストがある場合はそれを検証し、安全に展開したうえで、展開済みディレクトリをローカルインストールと同じ承認・インストール経路に渡します。マニフェストは署名を検証され、内容も検証されます。既にインストールされている名前は拒否されます。コンポーネントは一度だけ読み込まれ、パッケージ内に限定され、シンボリックリンクは許可されず、上限は 64 MiB です。インストール時のロードチェックでは、承認されたバイト列をこのホストの WIT ワールドに対してインスタンス化してから、何もインストールされません。ロードできないコンポーネントは、インスタンス化の診断メッセージとともにインストールが失敗します。--no-verify を指定すると、チェックを省略してそのままインストールされ、デーモンは起動時にそのプラグインをスキップします。展開処理は構造的に防御的に作られており (src/plugin_registry.rs)、アーカイブはその処理を通過できる必要があります:
- zip にはルートレベルの
manifest.toml、またはそれを含むネストされたプラグインディレクトリがちょうど1つ含まれている必要があります。マニフェストが0個、または2個以上ある場合、アーカイブは拒否されます。 - パストラバーサル、絶対パス、または Windows ドライブ接頭辞を含むエントリ名は拒否されます。
- ダウンロードはストリーミング中に (50 MiB) に制限されているため、
Content-Lengthを送信しないサーバーが無制限のバッファリングを強制することはできません。展開も同じ上限に制限されているため、zip 爆弾が無制限に展開することはできません。
バージョン解決: インストーラーがバージョン指定のない名前を受け取った場合、インデックス内の最後に一致するエントリを選択します。name@version を指定した場合は、そのバージョンのみが選択されます。同じ名前が複数ある場合は、レジストリ内で意図的に順序付けしてください(古いものを先に)。zeroclaw plugin update も同じ方法で選択し、バージョン文字列は等価性のみで比較します。ルールが選択するバージョンを運用者が使用している場合は最新であり、何もダウンロードされません。変更された各パッケージは必ず新しいバージョンで公開してください。インストール済みのバージョンで再公開されたエントリは更新として届かないためです。
検索は信頼境界ではありません
zeroclaw plugin search はインデックスに対する認証不要の検索であり、何かをインストール・有効化・実行することは一切ありません。セキュリティが機能するのはインストール時です。ダイジェスト検証、安全な展開、マニフェスト検証、そしてオペレーターの署名ポリシーが、ローカルパスからのインストールと同一に適用されます。それを踏まえて公開してください。インストール前のすべては信頼できない転送とみなしてください。
パブリッシャーのチェックリスト
[!IMPORTANT] コンパイル済みの
.wasmおよび.cwasmファイルはバイナリ成果物であり、多くの場合それぞれ数メガバイトになります。Git LFS を使わずに Git のソースツリーへコミットしないでください。リビルドのたびに素のブロブとしてコミットされると、リポジトリの履歴が永久的に肥大化し、git diffやレビューツールがそれらの処理でつまずきます。これらは他のビルド出力と同様に扱ってください。target/と*.wasm/*.cwasmを.gitignoreに追加し、代わりにリリース成果物やプラグインレジストリのアーカイブを通じて配布してください。どうしても成果物をツリー内に置く必要がある場合は、最初のコミットの前に LFS でパターンを追跡してください(git lfs track "*.wasm")。
- マニフェストを確定する: 名前、バージョン、機能、およびコードが使用する最小限の権限セット。
- コンポーネントをビルドする。スキルバンドルの場合は、すべての
SKILL.mdでフロントマターを検証する(discovery はnameとdescriptionを強制する)。 - 署名: Ed25519 キーの生成または読み込みを行い、正規化されたマニフェストのバイト列に署名し、
signatureとpublisher_keyを埋め込みます。 - 組み立てたパッケージディレクトリを zip にします(署名済みマニフェスト 1 つ、それが参照するコンポーネントと必要なアセット、パスの小細工なし、50 MiB 未満)。
- ZIP の SHA-256 を計算し、ダイジェスト付きでレジストリエントリを公開します。
- 公開鍵の16進値を、オペレーターがレジストリとは独立して検証できる場所(自分のリポジトリやサイトなど)に公開してください。
strictモードのオペレーターが信頼するのは、レジストリではなく鍵そのものです。 - リリースのたびに:
versionをバンプし、再署名し(バージョン行はカノニカルバイト内にある)、再ダイジェストし、新しいエントリを古いエントリの後に追加する。