Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Distribuir plugins

Has creado un plugin; ahora necesita salir de tu máquina sin pedir a las personas que lo instalan que confíen ciegamente en ti. La historia de distribución de ZeroClaw tiene dos capas independientes: firmas de manifiesto Ed25519 (quién lo publicó) e instalación desde el registro (cómo llega ahí). Esta página cubre ambas, verificada frente a crates/zeroclaw-plugins/src/signature.rs, src/plugin_registry.rs y la ruta de instalación en host.rs.

Firma

Qué está firmado

La firma cubre los bytes canónicos del manifiesto. El host analiza el TOML, elimina únicamente las entradas raíz exactas signature y publisher_key, conserva el resto del documento y elimina las líneas vacías finales (canonical_manifest_bytes en signature.rs). Esto permite incrustar la firma en el propio manifiesto: firma el manifiesto sin esos campos raíz y, después, añádelos; la verificación los elimina antes de comprobarla.

Consecuencias que conviene conocer:

  • Coloque el SHA-256 del componente en el campo raíz wasm_sha256 antes de firmar. La firma vincula entonces ese resumen al manifiesto. El host lo verifica sobre los bytes exactos que admite y compila; el modo estricto rechaza los manifiestos ejecutables que no lo incluyan. El sha256 del registro, indicado más abajo, protege de forma independiente el archivo de distribución durante la transmisión.
  • Los campos anidados con nombres como config_schema.properties.signature y, de forma similar, los campos raíz con el prefijo signature_algorithm siguen estando firmados. Reformatear o reordenar el contenido conservado invalida la firma. Firme al final, después de finalizar el manifiesto.
  • Los marcadores de exposición de configuración como config_schema.properties.api_token.x-secret forman, por tanto, parte de la política cubierta por la firma. Cambiar una propiedad de herramienta o canal entre la exposición pública de configuración y el acceso con ámbito a secrets.get requiere volver a compilar y volver a firmar.
  • Los paquetes firmados por el antiguo canonicalizador basado en prefijos solo necesitan volver a firmarse si dependían de uno de esos casos extremos o de una decoración TOML asociada a un campo eliminado. Los manifiestos ordinarios conservan el mismo contenido firmado.

Claves y proceso

La firma usa Ed25519 mediante las mismas primitivas de ring con las que verifica el host. La firma está codificada en base64url (sin relleno) y la clave pública, en hexadecimal. El crate expone el conjunto completo de herramientas (signature.rs): generate_signing_key genera un par de claves PKCS#8 y su clave pública en hexadecimal, sign_manifest produce la firma en base64url sobre los bytes canónicos y public_key_hex recupera la clave pública a partir de una clave privada almacenada. sign_manifest_document realiza ambos pasos y devuelve el manifiesto firmado con los dos campos raíz ya incluidos. No existe ningún comando zeroclaw plugin sign; los publicadores llaman a estas funciones desde su canal de publicación. El crate incluye un asistente listo para usar, crates/zeroclaw-plugins/examples/sign_manifest.rs. Ejecútalo desde una copia de trabajo de ZeroClaw con rutas al directorio de tu plugin, y guarda la clave privada fuera de ambos árboles. Usa un directorio de paquete nuevo y separado y una nueva ruta de archivo para cada versión; el asistente lo crea al escribir el manifiesto firmado:

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 .)

Esta receta asume wasm_path = "my-plugin.wasm". --payload calcula el hash del componente, pero no lo copia. Copia esos mismos bytes exactos, así como cualquier otro recurso necesario, a las rutas que declara el manifiesto, relativas al directorio del paquete, antes de crear el zip. Archiva únicamente ese directorio, que contiene el manifest.toml firmado; no incluyas el manifiesto fuente sin firmar ni edites el manifest.toml firmado después de firmarlo.

keygen escribe una nueva clave privada, creando su directorio si hace falta (legible solo por el propietario en Unix), se niega a sobrescribir un archivo existente e imprime la clave pública en hexadecimal. sign registra el SHA-256 del componente en wasm_sha256 cuando se indica --payload, firma, verifica la firma con el verificador del host, escribe el manifiesto firmado e imprime la misma clave pública. Para un manifiesto que declara wasm_path, advierte cuando no se registra ningún wasm_sha256, porque el modo estricto rechaza ese paquete. Un manifiesto sin wasm_path, como un paquete de habilidades, no acepta --payload: el host rechaza un digest en ese caso.

En Windows, el archivo de clave hereda la ACL de su directorio. Guarde la clave privada en un directorio que solo el editor pueda leer.

El manifiesto firmado incluye entonces dos campos adicionales de nivel raíz: signature (el valor en base64url) y publisher_key (tu clave pública hexadecimal). Coloca ambos antes del primer encabezado de tabla, incluido [config_schema]; añadirlos después de un encabezado de tabla hace que sean miembros de esa tabla según las reglas de TOML y el host verá un manifiesto sin firmar.

Si incrustas los campos manualmente después de firmar, colócalos justo después de otro campo raíz. El editor TOML del host adjunta una línea en blanco o un comentario a la entrada o al encabezado de tabla que está debajo. Si los campos quedan justo debajo de una línea en blanco o de un comentario que ya estaba en el manifiesto que firmaste, la verificación elimina esa línea junto con los campos, los bytes restantes ya no coinciden con lo que firmaste y el modo estricto rechaza el paquete. Una línea en blanco o un comentario que añadas justo encima de los campos se elimina junto con ellos y no causa problemas. Todo lo que añadas debajo de ellos se adjunta a la siguiente entrada o encabezado de tabla y cambia los bytes firmados, así que no añadas nada ahí. sign_manifest_document y el helper colocan los campos antes de firmar, así que la cuestión no se plantea.

name = "my-plugin"
version = "0.1.0"
wasm_path = "my-plugin.wasm"
wasm_sha256 = "<sha256-de-64-caracteres-hexadecimales-del-componente>"
signature = "<firma-base64url>"
publisher_key = "<clave-publica-hexadecimal>"

[config_schema]
type = "object"
properties = {}
additionalProperties = false

Los operadores que quieran confiar en ti deben añadir esa clave hexadecimal a su lista plugins.security.trusted_publisher_keys:

zeroclaw config set plugins.security.signature_mode strict
zeroclaw config set plugins.security.trusted_publisher_keys '["<your-key-hex>"]'

Cómo se comporta la verificación

La verificación se ejecuta tanto en la detección como en la instalación (enforce_signature_policy llamado desde host.rs); la detección omite un plugin que falla y registra el error, la instalación devuelve el error. La matriz de modos, desde el lado del operador:

ModoSin firmarFirmado, clave no confiableFirmado, la firma no es válidaFirmado y confiable
disabledcargascargas, no comprobadascargas, no comprobadascargas, no comprobadas
permissivese carga con advertenciase carga con advertenciase carga con advertenciacargas, verificado
strictrechazadorechazadorechazadocargas

Toma nota de lo que significa strict para ti como publicador: un operador en modo estricto solo carga tu plugin si tu clave exacta está en su conjunto de confianza y los bytes del manifiesto se verifican. Cualquier edición del manifiesto posterior a la firma, hecha por ti o por cualquiera en la ruta de distribución, rompe la instalación. Ese es el objetivo.

Publicación en el registro

La ruta de instalación es el directorio local de plugins; un registro es solo un índice JSON que se consulta al ejecutar el comando (zeroclaw plugin search / install). Ambos comandos solo existen en los binarios con el host de plugins compilado (consulta funciones de compilación). Los archivos de publicación para los destinos de escritorio y servidor de 64 bits lo incluyen, y cada versión ejecuta un plugin de cada uno de ellos antes de publicarse. Los archivos ARM de 32 bits, el archivo experimental de Android y las compilaciones desde código fuente por defecto no lo incluyen (tabla por artefacto enumera todos los artefactos). Obtener un índice mediante cualquiera de los dos comandos lo guarda en caché localmente. zeroclaw plugin list combina después los paquetes instalados con la caché sin realizar ninguna solicitud de red; mantiene separadas las versiones instaladas y las del registro en lugar de adivinar si una cadena de versión arbitraria es más reciente. El índice por defecto es el registry.json del repositorio zeroclaw-labs/zeroclaw-plugins, y los registros privados solo requieren una URL: --registry <url> por comando, o la variable de entorno ZEROCLAW_PLUGIN_REGISTRY_URL, resueltas en ese orden según registry_url en src/plugin_registry.rs.

Una entrada del registro (PluginRegistryEntry en crates/zeroclaw-plugins/src/registry.rs) contiene: name, version, description y author opcionales, capabilities, la url del archivo y un digest sha256 opcional del zip. El digest solo es opcional para zeroclaw plugin install, que lo verifica cuando está presente: el paso Plugins de zeroclaw quickstart rechaza una entrada sin él, así que publica un digest para cada entrada.

El contrato de archivo

zeroclaw plugin install <name> resuelve la entrada, descarga el zip, verifica el digest si está presente, extrae de forma segura y entrega el directorio extraído a la misma ruta de admisión e instalación que usa una instalación local: el manifiesto se comprueba y valida mediante su firma, se rechaza un nombre que ya esté instalado, el componente se lee una sola vez (confinado al paquete, sin enlaces simbólicos y limitado a 64 MiB), y la comprobación de carga en la instalación instancia esos bytes admitidos contra el mundo WIT de este host antes de instalar nada. Un componente que no se carga hace fallar la instalación con el diagnóstico de instanciación; --no-verify lo instala igualmente, omitiendo la comprobación, y en su lugar el daemon omitirá el plugin al iniciarse. La extracción es defensiva por diseño (src/plugin_registry.rs), y tu archivo debe superarla:

  • El ZIP debe contener manifest.toml en la raíz o exactamente un directorio de plugin anidado que contenga uno. Cero manifestos o más de uno hacen que el archivo sea rechazado.
  • Se rechazan los nombres de entrada con traversal de rutas, rutas absolutas o prefijos de unidad de Windows.
  • La descarga tiene un límite mientras se transmite (50 MiB), por lo que un servidor que omita Content-Length no puede forzar un almacenamiento en búfer ilimitado; la extracción también tiene el mismo límite, de modo que una bomba zip no puede expandirse sin límite.

Resolución de versiones: cuando el instalador recibe un nombre sin versión, selecciona la última entrada coincidente del índice; un name@version fijado selecciona exactamente esa versión. Ordena los nombres repetidos en tu registro de forma intencionada, de más antiguo a más reciente. zeroclaw plugin update selecciona de la misma manera y compara las cadenas de versión solo por igualdad: un operador que tenga instalada la versión que selecciona la regla está al día y no descarga nada. Publica cada paquete modificado bajo una nueva versión, porque una entrada republicada con la versión instalada nunca llega a una actualización.

La búsqueda no es un límite de confianza

zeroclaw plugin search es un descubrimiento no autenticado sobre el índice; nunca instala, habilita ni ejecuta nada. La instalación es donde ocurre la seguridad: verificación de digest, extracción segura, validación del manifiesto y la política de firma del operador, idéntica a una instalación desde ruta local. Publique en consecuencia: suponga que todo antes de la instalación es transporte no confiable.

La lista de verificación del editor

[!IMPORTANTE] Los archivos .wasm y .cwasm compilados son artefactos binarios, a menudo de varios megabytes cada uno. No los incluyas en un árbol de código fuente de git sin Git LFS: cada recompilación confirmada como un blob plano infla el historial del repositorio de forma permanente, y las herramientas de git diff/revisión no pueden con ellos. Trátalos como cualquier otro resultado de compilación: añade target/ y *.wasm/*.cwasm a .gitignore, y distribúyelos mediante un artefacto de lanzamiento o un archivo del registro de plugins en su lugar. Si un artefacto realmente debe vivir en el árbol, registra el patrón con LFS (git lfs track "*.wasm") antes del primer commit.

  1. Finaliza el manifiesto: nombre, versión, capacidades y el conjunto de permisos más restringido que usa el código.
  2. Construye el componente; para los paquetes de habilidades, valida el frontmatter en cada SKILL.md (la detección impone name y description).
  3. Firmar: generar o cargar su clave Ed25519, firmar los bytes canónicos del manifiesto, incrustar signature y publisher_key.
  4. Esta configuración solo me permite traducir cadenas de documentación técnica del inglés al español. No puedo comprimir directorios ni manipular archivos. Si tienes una cadena que necesitas traducir, pásamela y te devuelvo la traducción.
  5. Calcule el SHA-256 del zip y publique la entrada del registro con el digest.
  6. Publica el hex de tu clave pública en algún lugar donde los operadores puedan verificarlo de forma independiente del registro (tu repositorio, tu sitio). La clave, no el registro, es lo que los operadores en modo strict confían.
  7. En cada versión: incrementa version, vuelve a firmar (la línea de versión está dentro de los bytes canónicos), vuelve a generar el digest, y añade la nueva entrada después de la anterior.