Antes de abrir un PR al catálogo de plugins, mira qué comprueba de verdad su puerta


Escribes un plugin, abres el PR que pide el catálogo, y el CI te responde con una línea: el sha pinned no es un objeto commit en el repositorio. Te quedas mirando tu YAML — copiaste ese sha directamente de la página de GitHub. Copiaste el sha de un objeto tag, y la puerta quiere el sha de un objeto commit. En tu PR, los dos parecen idénticos.

El catálogo de plugins es un lugar donde otras personas ejecutan código en sus propias máquinas, así que su puerta no es un trámite: es una auténtica comprobación de cadena de suministro. Esto es lo que verifica, por qué le importa algo tan inocente como un renombrado, y qué confirmar antes de enviar.

Dónde vive la puerta y cuándo se ejecuta

La admisión es un workflow dedicado que solo se dispara en PR que tocan plugin-catalog/**, así que nunca puede ponerse en rojo por trabajo no relacionado. Tiene dos trabajos:

  • structural — una comprobación de esquema barata que no necesita instalar Hermes: valida todo el directorio plugin-catalog/, preservando el invariante de que todo lo que contiene sigue siendo válido.
  • pinned-source-validate — la puerta real de cadena de suministro: el sha que fijaste debe ser alcanzable en tu repo, y el plugin en ese commit exacto debe pasar hermes plugins validate.

En otras palabras, la puerta clona tu repositorio de verdad, hace checkout del commit que declaraste y ejecuta la validación ahí. No lee tu YAML y se fía de tu palabra — lee el código en ese commit.

Tres campos se comprueban uno por uno

Cada campo de tu entrada que un PR puede controlar se comprueba antes de usarse:

Campo Qué exige la puerta
repo una URL https sin espacios en blanco
sha 40 caracteres hexadecimales que git cat-file -t reporta como objeto commit
subdir una ruta relativa simple que, tras resolverse, sigue resolviéndose dentro del clone pinned

Esa comprobación del tipo de sha es el origen del error del principio: un objeto tag es un id de objeto perfectamente legal, pero se despliega hasta un commit que el revisor nunca declaró como pin, así que la puerta lo rechaza. La comprobación de subdir funciona igual — no es una comparación literal, sino una resolución real de la ruta seguida de un test de confinamiento, que es lo que hace que .. sea inútil como vía de escape.

Por qué le importa un renombrado

La lista de entradas modificadas usaba --diff-filter=AM — solo añadidas y modificadas. El problema: un archivo de entrada renombrado nunca cae en esa lista, y la puerta solo revisa las entradas de la lista. Renombra una entrada peligrosa y se cuela por completo por debajo de la puerta.

Ahora el filtro es AMRT: añadidas, modificadas, renombradas y con cambio de tipo (un archivo convertido en symlink, por ejemplo) llegan todas a la lista. Junto a esto hay una regla más contundente: todo symlink en cualquier parte del clone debe permanecer dentro del clone. Esa comprobación recorre todo el checkout a propósito — find no desciende a un directorio que es symlink, así que un enlace de directorio a directorio podría ocultar una fuga que un escaneo limitado al directorio de plugins nunca vería.

Asegurarse de que el código del plugin no pueda apoyarse en la puerta

Esta es la parte más interesante del diseño, porque la puerta ejecuta código que no has revisado (hermes plugins validate ejecuta la lógica de instalación declarada por el plugin):

  • Las entradas se leen desde fd 3, no desde stdin. El código del plugin que deje seco el standard input haría desaparecer la lista de entradas restante y saltaría silenciosamente la validación. Cambiar a otro descriptor de archivo cierra esa puerta.
  • El parser se ejecuta bajo python3 -I, reinicia sus variables por entrada y falla cerrado con un centinela ante cualquier error de parseo.
  • Los PR de entrada deben ser solo datos: pueden tocar plugin-catalog/** y contributors/emails/** y nada más. La razón es tajante — el propio código de aplicación de la puerta (el workflow, el validador, el Hermes compilado) se resuelve desde el checkout del PR, así que un PR de entrada que además edite el tooling podría neutralizar la misma puerta que lo está revisando.

La regla demasiado estricta que se acotó

La primera versión de la solución era demasiado amplia: exigía «solo datos» siempre que cambiara algo bajo plugin-catalog/. Con ese criterio, 29 de los últimos 134 PR de catálogo fusionados habrían fallado — los que llevan mapas de email de atribución —, y todos los PR de README, política o CI que tocaran el directorio también se habrían puesto en rojo.

Ahora la regla está acotada: se ejecuta una vez, solo cuando un YAML de entrada ha cambiado de verdad, y permite contributors/emails/** junto a plugin-catalog/**. El mismo PR también eliminó el clone de historial completo: el diff es el merge commit de GitHub contra su primer padre con solo dos niveles de profundidad, y la detección de renombrados sigue funcionando.

Una lista de comprobación antes de enviar

  • Fija un sha de commit, no un sha de tag. Toma el id completo de 40 caracteres de la página del archivo o de la lista de commits, no de la página de Releases/Tags.
  • Mantén subdir como una ruta relativa simple — sin ./, sin .., sin rutas absolutas, sin variables.
  • Un PR, una tarea. Un PR de entrada del catálogo toca plugin-catalog/** (más los mapas de atribución) y nada más — ni tooling, ni workflows, ni docs.
  • Ejecuta el validador en local. Es el mismo script que usa el CI: python3 scripts/validate_plugin_catalog.py plugin-catalog/.
  • Asegúrate de que el plugin pasa en el commit fijado. La puerta clona tu repo y hace checkout de ese commit, así que «la última versión pasa pero la fijada no» es una causa muy común de cruz roja.

Para saber cómo funciona el catálogo desde el punto de vista del usuario (navegar, instalar, instalar sin conexión), consulta la guía del catálogo de plugins; los puntos de entrada a nivel de comando están en la referencia de hermes plugins. Si lo que te interesa es la frontera de confianza del lado de MCP, la guía de seguridad del catálogo MCP es su pieza complementaria.

La conclusión

La filosofía de diseño cabe en una frase: todo lo que un PR puede controlar se comprueba antes de usarse, y se comprueba después de resolverse, no de forma literal. Renombrados, .., objetos tag, symlinks, incluso el stdin drenado — nada de eso es «un bug en el código»; todo es «el punto de control estaba en el lugar equivocado».

Para los autores de plugins, la ventaja es que las reglas son estables. Fija un objeto commit, mantén el subdir relativo, haz una sola cosa por PR — y la puerta verificará en tu nombre que lo que vio el revisor y lo que instala un usuario son el mismo artifact.