CVE-2026-55615
Neo4jChatAgent passes LLM-generated Cypher queries straight to the Neo4j driver with no validation, no statement-type allowlist, and no opt-out gate. The query text is influenceable by prompt injection (direct user input or indirect content the agent reads back via RAG), so an attacker who can influence the prompt can read or destroy all graph data and, when APOC or dbms.security procedures are enabled on the server, achieve OS-command and filesystem access. This is the same defect class and threat model as the SQLChatAgent prompt-to-SQL-to-RCE issue fixed in version 0.63.0 (CVE-2026-25879); that fix did not extend to the neo4j module.
Technical detail
Untrusted-input to sink trace (reviewed on langroid HEAD b9df06f, v0.65.3):
- Tool schemas accept raw query text from the LLM.
langroid/agent/special/neo4j/tools.py:4-9(CypherRetrievalTool.cypherquery: str) and:15-21(CypherCreationTool.cypherquery: str). These tools are enabled unconditionally inneo4jchatagent.py:412-419(enable_message([GraphSchemaTool, CypherRetrievalTool, CypherCreationTool, DoneTool])). - Read path.
neo4jchatagent.py:300cypherretrievaltool(msg) ->:325query = msg.cypherquery ->:328self.readquery(query) ->:223session.run(query, parameters). The LLM-controlled string is the first positional argument to session.run; parameters is None. No validation occurs between :325 and :223. - Write path.
neo4jchatagent.py:338cyphercreationtool(msg) ->:348query = msg.cypherquery ->:351self.writequery(query) ->:276session.writetransaction(lambda tx: tx.run(query, parameters)). The only inspection of the string (writequery :251-264) is a query.upper() substring test for CREATE/MERGE/CONSTRAINT/INDEX whose sole effect is setting self.config.databasecreated = True; it blocks nothing. A query such asMATCH (n) DETACH DELETE n(the same statement the built-in removedatabase helper runs at :287-293) passes unrestricted. - Guarded-sibling contrast proving the incomplete fix. The SQLChatAgent, patched for the parent CVE, validates every query before execution.
langroid/agent/special/sql/sqlchatagent.py:256defines allowdangerousoperations: bool = False (opt-in gate);:618validatequery runs (a) a dangerous-pattern regex blocklist (DANGEROUSSQLPATTERNS, :628-641), (b) a sqlglot statement-type allowlist defaulting to SELECT-only (:643-686), and (c) an AST-level dangerous-function blocklist (:687-704). runquery calls rejection = self.validatequery(query) at:721before executing. The neo4j readquery/writequery paths have no equivalent: a grep for validatequery/allowdangerous in neo4jchat_agent.py returns nothing. The defense exists for SQL and is simply absent for Cypher, which is the definition of an incomplete fix for the same prompt-to-query-language injection class.
Proof of concept (static, no third-party systems, no live Neo4j required)
Step 1 (presence vs absence of the guard, one command):
grep -n "_validate_query\|allow_dangerous" langroid/agent/special/sql/sql_chat_agent.py # SQL: many hits (gate + validator + call site :721)
grep -n "_validate_query\|allow_dangerous" langroid/agent/special/neo4j/neo4j_chat_agent.py # neo4j: ZERO hitsStep 2 (sink trace is direct, no interposed check):
read_query: msg.cypher_query (line 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)The only string inspection (writequery 251-264) is a .upper() substring test that only sets databasecreated=True and rejects nothing. The asymmetry (validator + opt-out gate enforced on every SQL query at sqlchatagent.py:721; nothing on either Cypher path) is the deterministic artifact: the same project, for the same injection class, guards one query language and not the other. A dynamic confirmation against an operator-owned disposable Neo4j (create a CSPRNG-marked node via cyphercreationtool, read it back via cypherretrievaltool, then DETACH DELETE it) reproduces the read/write/destroy primitive without any third-party system.
Impact
An attacker who can influence the agent prompt (directly, or indirectly via content the agent reads back through RAG) controls the executed Cypher. Floor (no extra config, not contingent): unauthorized read of all graph data via cypherretrievaltool and full write/destroy (including MATCH (n) DETACH DELETE n) via cyphercreationtool, plus the built-in LOAD CSV remote-fetch (SSRF) primitive. Ceiling (config-conditional, when APOC / dbms.security procedures are granted to the DB role, a common deployment): apoc.load. (SSRF + remote/local file read), apoc.cypher.runFile / apoc.import. / apoc.export. (filesystem), and CALL dbms. admin procedures, i.e. the Cypher analogue of the parent's COPY ... FROM PROGRAM RCE primitive. This mirrors the privileged-role contingency the parent advisory (CVE-2026-25879) accepted.
Suggested fix
Mirror the SQLChatAgent fix in the neo4j module: (1) add allowdangerousoperations: bool = False to Neo4jChatAgentConfig with a read-only default for cypherretrievaltool; (2) add a validatecypher(query, write) method that, unless allowdangerousoperations is True, blocks CALL apoc., CALL dbms., CALL db.* admin procedures, LOAD CSV, and any procedure/function touching filesystem/network/OS, and (for the read path) rejects write clauses (CREATE/MERGE/SET/DELETE/DETACH DELETE/REMOVE/DROP); (3) enforce it before session.run (readquery :223) and tx.run (writequery :276), returning the rejection to the LLM the way runquery does at sqlchatagent.py:721; (4) document, as the SQL config does, that LLM-generated Cypher is prompt-injectable and that allowdangerous_operations should only be set with a least-privilege Neo4j role. I am happy to send this as a PR if useful.
Relationship to the parent advisory
GHSA-mxfr-6hcw-j9rq / CVE-2026-25879 (Langroid SQLChatAgent prompt-to-SQL injection leading to RCE; Critical; fixed 0.63.0, SQLChatAgent only). This report is the same injection class in the still-unpatched sibling Neo4jChatAgent.
Severity note (honest, both ways)
Filed as High to reflect the non-contingent floor (unrestricted attacker-steered graph read/write/destroy + LOAD CSV SSRF, present regardless of APOC). The ceiling is Critical and RCE-equivalent when APOC / admin procedures are enabled on the DB role, at parity with the parent CVE-2026-25879 which was rated Critical under an equivalent privileged-role contingency. Please rate per your deployment assumptions.
Resolution (maintainer)
Fixed in 0.65.5. Both Neo4jChatAgent (Cypher) and the sibling ArangoChatAgent (AQL, raised in the follow-up) now mirror the SQLChatAgent controls: a new allowdangerousoperations config gate (default False), with the retrieval tool restricted to read-only queries and both tools rejecting code-execution / file / network primitives (LOAD CSV, apoc.*, dbms.*, CALL db.* for Cypher; user-defined namespace::func calls for AQL) unless the operator opts in. Validation is enforced at the tool handlers, so internal schema/maintenance calls are unaffected. Upgrade to 0.65.5 and run the agents against a least-privilege database role.
Package Versions Affected
Automatically patch vulnerabilities without upgrading
CVSS Version



Related Resources
References
https://github.com/langroid/langroid/security/advisories/GHSA-2pq5-3q89-j7cc, https://github.com/langroid/langroid