Distribution des plugins
Vous avez créé un plugin ; maintenant, il doit quitter votre machine sans demander aux personnes qui l’installent de vous faire aveuglément confiance. La distribution de ZeroClaw repose sur deux couches indépendantes : les signatures de manifeste Ed25519 (qui a publié ceci) et l’installation via le registre (comment il y arrive). Cette page couvre les deux, vérifiées par rapport à crates/zeroclaw-plugins/src/signature.rs, src/plugin_registry.rs, et le chemin d’installation dans host.rs.
Signature
Qu’est-ce qui est signé
La signature couvre les octets canoniques du manifeste. L’hôte analyse le TOML, supprime uniquement les entrées racine exactes signature et publisher_key, préserve le reste du document et supprime les lignes vides finales (canonical_manifest_bytes dans signature.rs). Cela rend la signature auto-intégrable : signez le manifeste sans ces champs racine, puis ajoutez-les ; la vérification les supprime avant le contrôle.
Deux conséquences à connaître :
- Le composant
.wasmlui-même n’est pas couvert par la signature. Ce que la signature atteste, c’est le manifeste : le nom, la version, les capacités et les permissions dont un éditeur se porte garant. Associez-le à un condensésha256de registre (ci-dessous) lorsque l’intégrité de l’artefact est importante en transit. - Les champs imbriqués portant des noms tels que
config_schema.properties.signature, ainsi que les champs racine préfixés de manière similaire tels quesignature_algorithm, restent signés. Le reformatage ou la réorganisation du contenu conservé invalide la signature. Signez en dernier, une fois le manifeste finalisé. - Les marqueurs d’exposition de la configuration tels que
config_schema.properties.api_token.x-secretrelèvent donc de la politique couverte par la signature. Modifier une propriété d’outil ou de canal entre une exposition dans la configuration publique et un accèssecrets.getà portée restreinte nécessite une reconstruction et une nouvelle signature. - Les paquets signés par l’ancien canonicaliseur basé sur des préfixes ne nécessitent une nouvelle signature que s’ils reposaient sur l’un de ces cas limites ou sur une décoration TOML attachée à un champ supprimé. Les manifestes ordinaires conservent le même contenu signé.
Clés et processus
La signature utilise Ed25519 via les mêmes primitives ring que l’hôte utilise pour vérifier. La signature est en base64url (sans padding) ; la clé publique est encodée en hexadécimal. Le crate expose la chaîne d’outils complète (signature.rs) : generate_signing_key produit une paire de clés PKCS#8 et sa clé publique hexadécimale, sign_manifest produit la signature base64url sur les octets canoniques, et public_key_hex récupère la clé publique à partir d’une clé privée stockée. Il n’existe pas de wrapper CLI pour la signature aujourd’hui ; les éditeurs pilotent ces fonctions à partir d’un court helper Rust dans leur pipeline de publication.
Le manifeste signé contient alors deux champs racine supplémentaires : signature (la valeur base64url) et publisher_key (votre clé publique hexadécimale). Placez-les tous les deux avant le premier en-tête de table, y compris [config_schema] ; les ajouter après un en-tête de table en fait des membres de cette table selon les règles TOML, et l’hôte verra un manifeste non signé.
name = "my-plugin"
version = "0.1.0"
signature = "<base64url-signature>"
publisher_key = "<hex-public-key>"
[config_schema]
type = "object"
properties = {}
additionalProperties = false
Les opérateurs qui souhaitent vous faire confiance ajoutent cette clé hexadécimale à leur liste plugins.security.trusted_publisher_keys :
zeroclaw config set plugins.security.signature_mode strict
zeroclaw config set plugins.security.trusted_publisher_keys ['["<your-key-hex>"]']
Comportement de la vérification
La vérification s’exécute à la fois lors de la découverte et de l’installation (enforce_signature_policy appelée depuis host.rs) ; la découverte ignore un plugin défaillant et journalise, l’installation renvoie l’erreur. La matrice de modes, du côté de l’opérateur :
| Mode | Non signé | Signé, clé non de confiance | Signé, signature invalide | Signé et de confiance |
|---|---|---|---|---|
disabled | loads | chargements, non vérifiés | chargements, non vérifiés | chargements, non vérifiés |
permissive | se charge avec un avertissement | se charge avec un avertissement | se charge avec un avertissement | chargements, vérifiés |
strict | rejeté | rejeté | rejeté | loads |
Notez ce que strict signifie pour vous en tant qu’éditeur : un opérateur en mode strict charge votre plugin uniquement si votre clé exacte figure dans son ensemble de confiance et que les octets du manifeste se vérifient. Toute modification du manifeste après signature, par vous ou par quiconque dans le chemin de distribution, brique l’installation. C’est le but.
Publication dans le registre
Le chemin d’installation est le répertoire local des plugins ; un registry est uniquement un index JSON consulté au moment de l’exécution de la commande (zeroclaw plugin search / install). Ces deux commandes n’existent que dans les binaires compilés avec le plugin host intégré (voir build features) ; les binaires de la version précompilée n’en disposent pas. L’index par défaut est le registry.json du dépôt zeroclaw-labs/zeroclaw-plugins ; les registries privés sont accessibles via une URL (--registry <url> par commande, ou la variable d’environnement ZEROCLAW_PLUGIN_REGISTRY_URL, résolus dans cet ordre selon registry_url dans src/plugin_registry.rs).
Une entrée de registre (PluginRegistryEntry dans crates/zeroclaw-plugins/src/registry.rs) contient : name, version, description et author optionnels, capabilities, l’url de l’archive, ainsi qu’un hachage sha256 optionnel du zip.
Le contrat d’archive
zeroclaw plugin install <name> résout l’entrée, télécharge le zip, vérifie le digest lorsqu’il est présent, extrait de manière sécurisée et confie le répertoire extrait au même chemin PluginHost::install qu’utilise une installation locale. L’extraction est défensive par construction (src/plugin_registry.rs), et votre archive doit y survivre :
- Le zip doit contenir soit un
manifest.tomlau niveau racine, soit exactement un répertoire de plugin imbriqué en contenant un. Zéro manifeste ou plus d’un est une archive rejetée. - Les noms d’entrée comportant une traversée de chemin, des chemins absolus ou des préfixes de lecteur Windows sont rejetés.
- Le téléchargement est limité lors du streaming (50 MiB) afin qu’un serveur qui ne fournit pas
Content-Lengthne puisse pas forcer une mise en mémoire tampon sans limite ; l’extraction est plafonnée à la même limite pour qu’une bombe zip ne puisse pas s’étendre sans limite.
Résolution de version : lorsque l’installateur reçoit un nom nu, il sélectionne la dernière entrée correspondante dans l’index ; un name@version épinglé sélectionne exactement cette version. Ordonnez intentionnellement les noms répétés dans votre registre, les plus anciens en premier.
La recherche n’est pas une frontière de confiance
zeroclaw plugin search est une découverte non authentifiée sur l’index ; elle n’installe, n’active ni n’exécute jamais rien. C’est à l’installation que la sécurité intervient : vérification du digest, extraction sécurisée, validation du manifeste et politique de signature de l’opérateur, identique à une installation depuis un chemin local. Publiez en conséquence : supposez que tout ce qui précède l’installation est un transport non fiable.
La liste de contrôle de l’éditeur
[!IMPORTANT] Les fichiers
.wasmet.cwasmcompilés sont des artefacts binaires, souvent de plusieurs mégaoctets chacun. Ne les versionnez pas dans un arbre source git sans Git LFS : chaque reconstruction validée en tant que blob simple gonfle l’historique du dépôt de manière permanente, et les outils degit diff/revue peinent avec eux. Traitez-les comme n’importe quelle autre sortie de compilation : ajouteztarget/et*.wasm/*.cwasmà.gitignore, et distribuez-les plutôt via un artefact de release ou une archive de registre de plugins. Si un artefact doit vraiment vivre dans l’arbre, suivez le motif avec LFS (git lfs track "*.wasm") avant le premier commit.
- Finalisez le manifest : nom, version, capacités et l’ensemble de permissions le plus restrictif que le code utilise.
- Construire le composant ; pour les bundles de compétences, valider le frontmatter sur chaque
SKILL.md(la découverte imposenameetdescription). - Signer : générer ou charger votre clé Ed25519, signer les octets du manifeste canonique, intégrer
signatureetpublisher_key. - Zippez le répertoire du plugin (un seul manifeste, pas de ruses de chemin, moins de 50 MiB).
- Calculer le SHA-256 du zip et publier l’entrée du registre avec le digest.
- Publiez l’hex de votre clé publique quelque part où les opérateurs peuvent le vérifier indépendamment du registre (votre dépôt, votre site). La clé, et non le registre, est ce en quoi les opérateurs du mode
strictont confiance. - À chaque publication : incrémenter
version, re-signer (la ligne de version est dans les octets canoniques), re-digérer, ajouter la nouvelle entrée après l’ancienne.