PT-2026-56085 · Pypi · Langroid
Published
2026-07-06
·
Updated
2026-07-06
·
CVE-2026-55615
CVSS v4.0
9.2
Critical
| Vector | AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
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.cypher query: str) and:15-21(CypherCreationTool.cypher query: str). These tools are enabled unconditionally inneo4j chat agent.py:412-419(enable message([GraphSchemaTool, CypherRetrievalTool, CypherCreationTool, DoneTool])). -
Read path.
neo4j chat agent.py:300cypher retrieval tool(msg) ->:325query = msg.cypher query ->:328self.read query(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.
neo4j chat agent.py:338cypher creation tool(msg) ->:348query = msg.cypher query ->:351self.write query(query) ->:276session.write transaction(lambda tx: tx.run(query, parameters)). The only inspection of the string (write query :251-264) is a query.upper() substring test for CREATE/MERGE/CONSTRAINT/INDEX whose sole effect is setting self.config.database created = True; it blocks nothing. A query such asMATCH (n) DETACH DELETE n(the same statement the built-in remove database 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/sql chat agent.py:256defines allow dangerous operations: bool = False (opt-in gate);:618validate query runs (a) a dangerous-pattern regex blocklist ( DANGEROUS SQL PATTERNS, :628-641), (b) a sqlglot statement-type allowlist defaulting to SELECT-only (:643-686), and (c) an AST-level dangerous-function blocklist (:687-704). run query calls rejection = self. validate query(query) at:721before executing. The neo4j read query/write query paths have no equivalent: a grep for validate query/allow dangerous in neo4j chat 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 (write query 251-264) is a .upper() substring test that only sets database created=True and rejects nothing. The asymmetry (validator + opt-out gate enforced on every SQL query at sql chat agent.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 cypher creation tool, read it back via cypher retrieval tool, 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 cypher retrieval tool and full write/destroy (including MATCH (n) DETACH DELETE n) via cypher creation tool, 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 allow dangerous operations: bool = False to Neo4jChatAgentConfig with a read-only default for cypher retrieval tool; (2) add a validate cypher(query, write) method that, unless allow dangerous operations 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 (read query :223) and tx.run (write query :276), returning the rejection to the LLM the way run query does at sql chat agent.py:721; (4) document, as the SQL config does, that LLM-generated Cypher is prompt-injectable and that allow dangerous 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 allow dangerous operations 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.Fix
Special Elements Injection
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Langroid