Resumen

El servidor MCP (Model Context Protocol) de PraisonAI (praisonai mcp serve) registra por defecto cuatro herramientas de manejo de archivos: praisonai.rules.create, praisonai.rules.show, praisonai.rules.delete, y praisonai.workflow.show. Cada una acepta una cadena de ruta o nombre de archivo desde argumentos MCP tools/call y la une a ~/.praison/rules/ (o para workflow.show, acepta una ruta absoluta) sin verificación de contención. El despachador JSON-RPC pasa params["arguments"] directamente a cada manejador via **kwargs sin validar contra el esquema de entrada anunciado.

Detalles Técnicos

Dispatcher Acepta kwargs No Validados

El código en src/praisonai/praisonai/mcp_server/server.py:281-298 muestra que el despachador no aplica validación de esquema:

python
async def _handle_tools_call(self, params: Dict[str, Any]) -> Dict[str, Any]:
    tool_name = params.get("name")
    arguments = params.get("arguments", {})
    
    tool = self._tool_registry.get(tool_name)
    if tool is None:
        raise ValueError(f"Tool not found: {tool_name}")
    
    # Execute tool
    if asyncio.iscoroutinefunction(tool.handler):
        result = await tool.handler(**arguments)  # ← sin validación de esquema
    else:
        result = tool.handler(**arguments)

Manejadores Sin Contención

Los cuatro manejadores registrados carecen de verificaciones de contención:

python
@register_tool("praisonai.rules.create")
def rules_create(rule_name: str, content: str) -> str:
    rules_dir = os.path.expanduser("~/.praison/rules")
    os.makedirs(rules_dir, exist_ok=True)
    rule_path = os.path.join(rules_dir, rule_name)  # ← sin validación realpath/contención
    with open(rule_path, 'w') as f:
        f.write(content)
    return f"Rule created: {rule_name}"

Al establecer rule_name="../../<alguna-ruta>", un atacante puede salir del directorio de reglas y escribir cualquier archivo que el usuario pueda escribir.

Escalada a RCE via Archivos .pth de Python

CPython's Lib/site.py importa líneas que comienzan con import desde cada archivo .pth presente en site.getsitepackages() y site.getusersitepackages() en cada inicio de intérprete. El directorio site-packages del usuario siempre es escribible sin elevación. Un solo archivo .pth conteniendo import os; os.system("...") convierte la primitiva de escritura por path traversal en RCE en el próximo intérprete Python que inicie el usuario.

Transporte HTTP-stream Sin Autenticación por Defecto

La verificación de autenticación en mcp_server/transports/http_stream.py:191-198 está envuelta en if self.api_key: donde None omite todo el bloque. Configuración por defecto: praisonai mcp serve --transport http-stream vincula 127.0.0.1:8080/mcp sin autenticación.

Vectores de Ataque

Vector 1: LLM Conectado por MCP

Un LLM conectado por MCP (Claude Desktop, Cursor, Continue.dev, Claude Code) cuyo contexto está envenenado por contenido web controlado por atacante, documentos o emails. No se requiere clic del operador más allá del uso ordinario de "pide al LLM que resuma esta página".

Vector 2: HTTP-stream Local

praisonai mcp serve --transport http-stream sin --api-key (por defecto), alcanzable desde cualquier proceso local, pestaña de navegador con DNS-rebound, o vecino de contenedor compartiendo loopback.

Vector 3: Stdio MCP

Desde cualquier vector de inyección de prompt que alcance el LLM conectado.

Impacto

  • Ejecución arbitraria de código en la máquina del usuario, con privilegios del usuario, en cualquier proceso Python subsecuente que inicien
  • Lectura arbitraria de archivos de cualquier archivo que el usuario pueda leer, incluyendo ~/.ssh/, ~/.aws/credentials, ~/.config/praisonai/*.yaml
  • Escritura arbitraria de archivos en cualquier lugar donde el usuario pueda escribir para establecer persistencia
  • Eliminación arbitraria de archivos para cadenas destructivas estilo ransomware
  • Exfiltración de credenciales MCP: leer configuración del cliente MCP del usuario que lista todos los otros servidores MCP conectados con sus API keys/tokens OAuth/credenciales
  • Exfiltración de credenciales de proveedor LLM: leer claves API de OpenAI/Anthropic/Google desde archivos de entorno

La configuración por defecto de praisonai mcp serve registra las cuatro herramientas vulnerables incondicionalmente; no se requiere mala configuración del operador. La entrega por inyección de prompt indirecta via contenido web/documentos/emails convierte esto en RCE de red para cualquier usuario con un LLM conectado por MCP y el servidor MCP praisonai instalado.

Mitigación Sugerida

  1. Contención en cada manejador cli_tools: Reemplazar os.path.join simple con validación explícita de prefijo usando pathlib.Path.resolve() y verificación de que la ruta resuelta permanece dentro del directorio designado

  2. Aplicación de esquema en el despachador: Validar params["arguments"] contra tool.input_schema usando un validador JSON-Schema antes de tool.handler(**arguments)

  3. Reducir superficie de herramientas por defecto: Mover rules.* y workflow.show detrás de un opt-in explícito --enable-fs-tools

  4. Requerir autenticación en enlaces HTTP-stream no-loopback: praisonai mcp serve --transport http-stream debería rechazar iniciar con host != 127.0.0.1 si --api-key no está establecido