Descripción general
El componente Neo4jChatAgent de Langroid pasa consultas Cypher generadas por un modelo de IA directamente al driver de Neo4j sin ningún tipo de validación, sin lista de tipos de sentencias permitidas y sin mecanismo de desactivación configurable. El texto de la consulta puede ser influenciado mediante inyección de prompt, ya sea por entrada directa del usuario o de forma indirecta a través de contenido que el agente recupera mediante RAG. Un atacante que logre influir en el prompt puede leer o destruir todos los datos del grafo y, cuando los procedimientos APOC o dbms.security están habilitados en el servidor, obtener acceso a comandos del sistema operativo y al sistema de archivos.
Esta vulnerabilidad pertenece a la misma clase de defecto y modelo de amenaza que el problema de inyección prompt-to-SQL-to-RCE del SQLChatAgent, corregido en la versión 0.63.0 bajo CVE-2026-25879. Dicha corrección no se extendió al módulo neo4j.
Detalle técnico
Traza de entrada no confiable hasta el sumidero (revisada en langroid HEAD b9df06f, v0.65.3):
1. Esquemas de herramientas que aceptan texto de consulta crudo del modelo de IA.
En langroid/agent/special/neo4j/tools.py:4-9 (CypherRetrievalTool.cypher_query: str) y :15-21 (CypherCreationTool.cypher_query: str). Estas herramientas se habilitan de forma incondicional en neo4j_chat_agent.py:412-419 mediante enable_message([GraphSchemaTool, CypherRetrievalTool, CypherCreationTool, DoneTool]).
2. Ruta de lectura.
neo4j_chat_agent.py:300 cypher_retrieval_tool(msg) -> :325 query = msg.cypher_query -> :328 self.read_query(query) -> :223 session.run(query, parameters). La cadena controlada por el modelo de IA es el primer argumento posicional de session.run; parameters es None. No ocurre ninguna validación entre las líneas :325 y :223.
3. Ruta de escritura.
neo4j_chat_agent.py:338 cypher_creation_tool(msg) -> :348 query = msg.cypher_query -> :351 self.write_query(query) -> :276 session.write_transaction(lambda tx: tx.run(query, parameters)). La única inspección de la cadena (write_query :251-264) es una prueba de subcadena con query.upper() que busca CREATE/MERGE/CONSTRAINT/INDEX y cuyo único efecto es establecer self.config.database_created = True; no bloquea nada. Una consulta como MATCH (n) DETACH DELETE n (la misma sentencia que ejecuta el helper interno remove_database en :287-293) pasa sin restricción.
4. Contraste con el componente corregido que demuestra la corrección incompleta.
El SQLChatAgent, parcheado para el CVE padre, valida cada consulta antes de ejecutarla. langroid/agent/special/sql/sql_chat_agent.py:256 define allow_dangerous_operations: bool = False (puerta de activación explícita); :618 ejecuta _validate_query, que aplica: (a) una lista de bloqueo por expresión regular de patrones peligrosos (_DANGEROUS_SQL_PATTERNS, :628-641), (b) una lista de tipos de sentencias permitidas mediante sqlglot, con valor predeterminado de solo SELECT (:643-686), y (c) una lista de bloqueo a nivel de AST de funciones peligrosas (:687-704). run_query invoca rejection = self._validate_query(query) en :721 antes de ejecutar. Las rutas read_query/write_query de Neo4j no tienen equivalente: una búsqueda de _validate_query/allow_dangerous en neo4j_chat_agent.py no devuelve ningún resultado. La defensa existe para SQL y está simplemente ausente para Cypher, lo que constituye la definición de una corrección incompleta para la misma clase de inyección prompt-to-query-language.
Prueba de concepto (estática, sin sistemas de terceros, sin Neo4j activo)
Paso 1 (presencia frente a ausencia del mecanismo de control, un solo comando):
grep -n "_validate_query\|allow_dangerous" langroid/agent/special/sql/sql_chat_agent.py
# SQL: múltiples coincidencias (puerta + validador + punto de llamada :721)
grep -n "_validate_query\|allow_dangerous" langroid/agent/special/neo4j/neo4j_chat_agent.py
# neo4j: CERO coincidencias
Paso 2 (la traza hasta el sumidero es directa, sin comprobación intermedia):
read_query: msg.cypher_query (línea 325) -> read_query(query) (328) -> session.run(query, parameters) (223)
write_query: msg.cypher_query (348) -> write_query(query) (351) -> tx.run(query, parameters) (276)
La única inspección de la cadena (write_query 251-264) es una prueba de subcadena con .upper() que únicamente establece database_created=True y no rechaza nada. La asimetría (validador más puerta de desactivación aplicados en cada consulta SQL en sql_chat_agent.py:721; nada en ninguna de las rutas Cypher) es el artefacto determinista: el mismo proyecto, para la misma clase de inyección, protege un lenguaje de consulta y no el otro. Una confirmación dinámica contra una instancia Neo4j desechable propiedad del operador (crear un nodo marcado con CSPRNG mediante cypher_creation_tool, leerlo con cypher_retrieval_tool y luego ejecutar DETACH DELETE) reproduce el primitivo de lectura/escritura/destrucción sin ningún sistema de terceros.
Impacto
Un atacante que pueda influir en el prompt del agente (directamente, o de forma indirecta a través de contenido que el agente recupera mediante RAG) controla el Cypher ejecutado.
Nivel base (sin configuración adicional, no contingente): lectura no autorizada de todos los datos del grafo mediante cypher_retrieval_tool y escritura/destrucción completa (incluyendo MATCH (n) DETACH DELETE n) mediante cypher_creation_tool, más el primitivo de recuperación remota LOAD CSV (SSRF) incorporado.
Nivel máximo (condicional a la configuración, cuando los procedimientos APOC o dbms.security están concedidos al rol de base de datos, una configuración de despliegue habitual): apoc.load.* (SSRF más lectura de archivos remotos y locales), apoc.cypher.runFile / apoc.import.* / apoc.export.* (sistema de archivos) y procedimientos de administración CALL dbms.*, es decir, el análogo en Cypher del primitivo RCE COPY ... FROM PROGRAM del CVE padre. Esto refleja la contingencia de rol privilegiado que el aviso padre (CVE-2026-25879) ya contemplaba.
Corrección sugerida
Replicar la corrección del SQLChatAgent en el módulo neo4j:
- Añadir
allow_dangerous_operations: bool = FalseaNeo4jChatAgentConfigcon un valor predeterminado de solo lectura paracypher_retrieval_tool. - Añadir un método
_validate_cypher(query, write)que, salvo queallow_dangerous_operationsseaTrue, bloqueeCALL apoc.*,CALL dbms.*,CALL db.*para procedimientos de administración,LOAD CSVy cualquier procedimiento o función que acceda al sistema de archivos, red o sistema operativo; y que para la ruta de lectura rechace cláusulas de escritura (CREATE/MERGE/SET/DELETE/DETACH DELETE/REMOVE/DROP). - Aplicarlo antes de
session.run(read_query :223) ytx.run(write_query :276), devolviendo el rechazo al modelo de IA de la misma forma querun_querylo hace ensql_chat_agent.py:721. - Documentar, como hace la configuración de SQL, que el Cypher generado por IA es susceptible de inyección de prompt y que
allow_dangerous_operationssolo debe activarse con un rol Neo4j de mínimo privilegio.
Relación con el aviso padre
GHSA-mxfr-6hcw-j9rq / CVE-2026-25879 (inyección prompt-to-SQL en Langroid SQLChatAgent que conduce a RCE; Critical; corregido en 0.63.0, solo SQLChatAgent). Este informe corresponde a la misma clase de inyección en el componente hermano Neo4jChatAgent, que permanecía sin parchear.
Relación con ArangoChatAgent
En el seguimiento posterior al informe original, se identificó que ArangoChatAgent (AQL) presenta la misma clase de defecto. Ambos componentes fueron corregidos conjuntamente en la versión 0.65.5.
Resolución
Corregido en la versión 0.65.5. Tanto Neo4jChatAgent (Cypher) como el componente hermano ArangoChatAgent (AQL) replican ahora los controles del SQLChatAgent: una nueva puerta de configuración allow_dangerous_operations (valor predeterminado False), con la herramienta de recuperación restringida a consultas de solo lectura y ambas herramientas rechazando primitivos de ejecución de código, acceso a archivos y red (LOAD CSV, apoc.*, dbms.*, CALL db.* para Cypher; llamadas a funciones de espacio de nombres definidas por el usuario para AQL) salvo que el operador lo active explícitamente. La validación se aplica en los manejadores de herramientas, por lo que las llamadas internas de esquema y mantenimiento no se ven afectadas. Se recomienda actualizar a la versión 0.65.5 y ejecutar los agentes contra un rol de base de datos de mínimo privilegio.
