Seguridad

Seguridad del servidor MCP para Craft CMS: el límite es un usuario de Craft.

Un cliente de IA que puede cambiar un sitio en funcionamiento necesita límites claros. Esta página enumera cada límite en el orden en que lo encuentra una llamada. También dice qué no cubre cada límite.

Por dónde pasa cada llamada

En este orden. Una llamada que falla en cualquier paso nunca llega a la herramienta.

  1. Límite de peticiones — llamadas por minuto, contadas por token. Un freno contra un modelo que entra en bucle, no una defensa contra alguien decidido que tenga un token válido.
  2. Alcance del token — read, content o full. El alcance solo restringe lo que el usuario asociado ya puede hacer.
  3. Edición — el plugin se distribuye en una única edición, así que hoy este paso siempre se supera. Existe para que, si más adelante se dividen las ediciones, no se pueda entregar una herramienta sin que nadie lo note.
  4. Modo de solo lectura — mientras está activado, no se ejecuta nada que cambie algo, digan lo que digan los ajustes siguientes.
  5. Activación de herramientas — toda herramienta que cambia algo está desactivada hasta que un administrador la activa, una a una.
  6. Comprobación de administrador — las herramientas de administrador exigen además que el usuario asociado sea administrador.
  7. Esquema de argumentos — los argumentos se validan contra el esquema de la herramienta antes de que esta los vea.
  8. La propia comprobación de permisos de Craft, por elemento — después, la herramienta pregunta a Craft, para este elemento y este sitio, si el usuario asociado puede verlo, guardarlo o borrarlo.

Dos tipos de rechazo, a propósito. Una herramienta que no existe y una herramienta que el alcance del token no cubre reciben la misma respuesta — Unknown tool — porque la diferencia le diría a un atacante qué más hay. Una herramienta desactivada, bloqueada por el modo de solo lectura o reservada a administradores tampoco aparece en la lista, pero una llamada directa recibe el motivo en palabras claras, para que quien administra el sitio no busque el fallo por su lado.

Por qué el usuario es el límite real

Cada token pertenece exactamente a un usuario de Craft. Se hacen dos preguntas, y la segunda es la que importa: primero la general, si este tipo de acción está abierta a este usuario; después la precisa: ¿puede este usuario hacer esto, con este elemento, en este sitio? Un editor que solo puede trabajar en un sitio y ver una sección llega exactamente a eso a través del cliente de IA, y a nada más.

Esto tiene una consecuencia que conviene decir claramente: un token que pertenece a un administrador puede hacer lo que puede hacer un administrador. Asigna a cada token el usuario con menos permisos que todavía pueda hacer el trabajo, y usa el alcance para restringirlo aún más.

Una sección o un grupo de categorías desconocido o prohibido devuelve una lista vacía, no un error: un error confirmaría que existe. En cambio, un handle de sitio que no existe se indica como error, porque un handle incorrecto es un fallo en la llamada, no un límite.

Tokens

  • El texto plano se muestra exactamente una vez, al crear el token. Solo se guarda un hash SHA-256, así que no se puede volver a mostrar.
  • Los tokens viven en su propia tabla, nunca en la configuración del proyecto, que se sube a Git.
  • La página de tokens del panel de control solo está abierta a administradores. Craft registra un permiso «Access AlphaBridge MCP» para cada plugin con una entrada en la barra lateral; concederlo no abre esta página a quien no sea administrador.
  • Un token puede llevar una fecha de caducidad y se puede revocar en cualquier momento; la siguiente llamada con él falla.
  • La cabecera Authorization se oculta en las entradas de registro con el contexto de la petición del propio Craft, así que un token no acaba en storage/logs cuando se registra una petición.

El registro de auditoría, y lo que omite a propósito

Se registra cada llamada a herramientas: cuándo, qué herramienta, qué usuario de Craft y el resultado (permitida, rechazada o fallida). Las llamadas rechazadas también se registran; son las interesantes.

Se registran los nombres de los argumentos, no sus valores. Puedes ver que se creó una entrada con una sección y un título; no puedes leer el título en el registro. Es deliberado: un registro que guardara valores sería una segunda copia de tu contenido, y menos protegida.

Para leer el registro hace falta el alcance de administrador. El registro se recorta a un tamaño fijo, así que no puede crecer sin límite.

Un límite honesto: la entrada del registro se escribe en la medida de lo posible. Si esa escritura falla (por ejemplo, por un error de la base de datos), la llamada no se interrumpe; el fallo va al propio registro de Craft. Así que trata el registro de auditoría como un registro muy bueno, no como una garantía de que no pueda faltar nada.

Leer los registros de Craft es más limitado de lo que parece

La herramienta de registros devuelve las entradas de registro de Craft sin el contexto de la petición que Craft les añade. Ese contexto contiene $_POST, $_COOKIE y $_SESSION, incluida, tras un inicio de sesión fallido, una contraseña en texto plano. Las sentencias SQL y los stack traces se cortan en la primera marca.

Por eso la herramienta de registros es una herramienta de administrador y está desactivada por defecto.

Las herramientas de base de datos y sus límites

Dos herramientas llegan a la base de datos, y solo una ejecuta SQL que escribes tú. craft_db_schema enumera los nombres de las tablas y marca las protegidas como ilegibles. craft_db_query ejecuta una consulta SELECT sobre una tabla y rechaza, antes de que nada llegue a la base de datos:

  • joins, subconsultas, common table expressions y UNION
  • cualquier función excepto count, min, max, avg y sum
  • tablas protegidas: usuarios, sesiones, tokens, la configuración del proyecto y otras que contienen credenciales o secretos
  • expresiones que nombran la cuenta del servidor o una secuencia

Una barrera hecha de reglas solo conoce lo que se le ha dicho. Por eso ambas son herramientas de administrador, la herramienta de consulta está desactivada por defecto y ninguna de las dos escribe nunca.

Orígenes y DNS rebinding

Si una petición lleva una cabecera Origin, tiene que estar en la lista de permitidos o el endpoint responde 403. La lista de permitidos es lo que has configurado más la URL del sitio, pero solo donde se pueda determinar de forma estática. Una URL del sitio construida a partir del alias @web de Craft no cuenta: Craft deriva @web de la cabecera Host de la petición entrante, así que un atacante se compararía con un valor elegido por él mismo.

En una instalación así, la lista configurada es la única fuente y, si está vacía, se rechaza toda petición que lleve un Origin. Los clientes de servidor a servidor no envían Origin y no se ven afectados.

Lo que este plugin no hace

Cada una de estas cosas es una decisión, y cada una elimina un tipo de riesgo en lugar de gestionarlo.

  • Sin ejecución arbitraria de código. Ninguna herramienta ejecuta PHP, comandos de shell ni una plantilla que le pases.
  • Sin despliegue ni transferencia de archivos. El plugin no mueve archivos y nunca toca un servidor por SSH o FTP.
  • Sin gestión de plugins. No instala, activa, desactiva ni elimina plugins.
  • Sin escrituras en la configuración del proyecto ni en usuarios. Craft aplica las reglas para eso en sus controladores; este plugin habla con la API de elementos, que no ve esas reglas, así que no escribe ahí en absoluto.
  • Sin etiquetas. Craft no define ningún permiso para las etiquetas, así que cualquier límite habría que inventarlo en lugar de aplicarlo.
  • Sin valores de ajustes de campos. Los ajustes de los campos se devuelven solo como nombres, nunca como valores, porque un plugin puede meter cualquier cosa en ellos. No es una promesa general sobre secretos: la herramienta de base de datos llega a toda tabla física cuyo nombre no esté en su lista de protegidas, y una tabla de terceros con un nombre discreto no está en ella. Precisamente por eso esa herramienta es para administradores y está desactivada por defecto.

Cómo se mantiene esto cierto

Cada cambio ejecuta una batería de pruebas unitarias, análisis estático con PHPStan nivel 6 sin baseline y una prueba de principio a fin contra una instalación de Craft recién creada en PostgreSQL 16 y MySQL 8.4. Esa prueba crea un editor que no es administrador, que puede ver exactamente una sección y trabajar exactamente en un sitio, y demuestra que el filtro de permisos corta de verdad, no solo que las herramientas devuelven algo.

¿Has encontrado algo? Infórmanos en privado en mail@cultureclub.dev, no en una incidencia pública.