# Langroid: Neo4jChatAgent executes LLM-generated Cypher without validation (prompt-to-Cypher injection; config-conditional RCE), mirroring the SQLChatAgent bug fixed in CVE-2026-25879

Langroid's Neo4jChatAgent passes LLM-generated Cypher straight to session.run with no validation, letting prompt injection read/destroy graph data and reach RCE via APOC.

- Payout: Research
- Severity: critical
- Program: Langroid
- Found by: matte1782
- Disclosed: 2026-07-06
- CVE: CVE-2026-55615
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-2pq5-3q89-j7cc
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-2pq5-3q89-j7cc
- Tags: area:ai domain:ai domain:web3 impact:data-leak impact:rce kind:research platform:ghsa sev:critical tech:llm technique:incomplete-fix technique:source-review vuln:prompt-injection vuln:rce

**Target:** Neo4jChatAgent Cypher query execution (session.run / tx.run)
**Root cause:** CypherRetrievalTool and CypherCreationTool accept raw LLM-supplied cypher_query and pass it directly to session.run/tx.run with no statement-type allowlist, dangerous-procedure blocklist, or opt-in gate; the only inspection is a substring test that merely sets database_created=True.
**Steps:**
1. `grep -n "_validate_query\|allow_dangerous" langroid/agent/special/sql/sql_chat_agent.py` shows the SQL validator and gate; same grep on neo4j_chat_agent.py returns zero hits
2. Trace read path: `msg.cypher_query` (neo4j_chat_agent.py:325) -> `read_query(query)` (:328) -> `session.run(query, parameters)` (:223)
3. Trace write path: `msg.cypher_query` (:348) -> `write_query(query)` (:351) -> `tx.run(query, parameters)` (:276)
4. Confirm write_query's only check (:251-264) is a `query.upper()` substring test for CREATE/MERGE/CONSTRAINT/INDEX that sets database_created=True and blocks nothing
5. Dynamic: create a CSPRNG-marked node via cypher_creation_tool, read it back via cypher_retrieval_tool, then run `MATCH (n) DETACH DELETE n` to confirm read/write/destroy primitive
**How found:** Noticed the SQLChatAgent prompt-to-SQL fix (CVE-2026-25879) added a validator and allow_dangerous_operations gate, then grepped the sibling neo4j module for the same guard and found zero hits; traced both tool handlers to the driver sinks to confirm no interposed validation.
**Exploitation:** Attacker influences the agent prompt directly or indirectly via RAG content the agent reads back, so the LLM emits attacker-chosen Cypher executed by the tools. Floor: read all graph data via cypher_retrieval_tool, write/destroy via cypher_creation_tool (`MATCH (n) DETACH DELETE n`), plus LOAD CSV remote-fetch SSRF. Ceiling when APOC/dbms.security procedures are granted: apoc.load.* (SSRF, file read), apoc.cypher.runFile/apoc.import.*/apoc.export.* (filesystem), CALL dbms.* admin procedures — Cypher analogue of the parent's COPY ... FROM PROGRAM RCE.
**Escalation:** Chains prompt injection (direct or indirect RAG) into arbitrary Cypher; escalates from data read/destroy to OS-command and filesystem access when APOC or dbms.security procedures are enabled on the DB role.
**Insight:** When a project patches an injection class in one query-language agent, grep sibling modules for the validator symbol — the fix is often scoped to the reported module only, leaving parallel sinks (Cypher, AQL) unguarded.

## Proof of concept (copied exactly from the original report)

**Grep proving SQL guard present, neo4j absent**

```
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 hits
```

**Sink trace from LLM query to session.run**

```
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)
```

