Skip to main content
CVE-2026-114510VulnerabilityPublished

Flexible GraphRAG: Cypher/Graph Injection via broken string-escaping in FalkorDB adapter (_escape()) - full graph wipe

Affects flexible-graphragSource GCVE Database

0
Authoritative record:CVE-2026-114510· High 7.1

Not known to be exploited · CVSS 7.1 High : upgrade to 0.8.2 soon.

Data as of 27 Oct 2026, 16:27 UTC

CVSS 3.1 base score
7.1High
How severe the flaw is on the 0–10 CVSS scale.
Patch
Available – fixed in 0.8.2
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 22 Oct 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponised: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.

What to do

Upgrade flexible-graphrag to 0.8.2.

Affected software

ProductAffectedFixed in
PyPIflexible-graphragAffected
  • >=0.6.0 <0.8.2
Fixed in
  • 0.8.2

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

**Description** def _escape(v: str) -> str: return str(v).replace("'", "\\'").replace("\\", "\\\\") The escaping order is backwards, exactly like the classic HTML double-escape bug: step 1 inserts a literal backslash in front of every ', then step 2 re-matches that just-inserted backslash and doubles it. For an input ending in a single quote, the two steps combined produce \\' (two backslashes followed by the quote) in the output — which, under the standard backslash-escape convention used by Cypher/openCypher single-quoted string literals, is parsed as one escaped backslash followed by an unescaped, string-terminating quote. The quote is not neutralized; the string breaks open. This escaped value is interpolated directly into Cypher query strings built by string formatting (not parameters) at 5 call sites in the same method: self.lc_graph.query( f"MERGE (n:`{node.type}` {{id: '{_escape(node.id)}'}}) " f"{props}" f"RETURN distinct 'done' AS result" ) ... self.lc_graph.query( f"MATCH (a:`{rel.source.type}` {{id: '{_escape(rel.source.id)}'}}), " f"(b:`{rel.target.type}` {{id: '{_escape(rel.target.id)}'}}) " f"MERGE (a)-[r:`{rel_type}`]->(b) " f"{props}" f"RETURN distinct 'done' AS result" ) node.type, rel.source.type, rel.target.type and rel_type are also interpolated into backtick-quoted identifiers with no escaping at all. Where the attacker-controlled data comes from: node/rel objects are LLM-extracted entities and relationships derived from ingested document content (the whole point of this GraphRAG pipeline). Document content is therefore untrusted input relative to this database layer — a classic indirect-prompt-injection-to-query-injection chain: craft a document whose text causes the extraction LLM to produce an entity id containing the payload below, ingest it, and the broken escape function turns it into live Cypher. **Impact** Full Cypher clause injection into lc_graph.query() calls that run with no query parameterization and no read-only restriction visible in this adapter. Demonstrated impact: appending a second clause (MATCH (m) DETACH DELETE m) to the query wipes every node and relationship in the graph in a single call. The same primitive can be adapted to exfiltrate data (e.g. RETURN other nodes' properties back through a field the caller reads) or tamper with existing records, depending on what the calling code surfaces to the end user/LLM. Proof of Concept (executed, full query reconstruction) Only the two-line _escape() function and the f-string template were executed — no import of the flexible-graphrag package itself. raw attacker-controlled node.id: "pwn' }) MATCH (m) DETACH DELETE m RETURN count(m) AS pwn //" output of buggy _escape(): "pwn\\' }) MATCH (m) DETACH DELETE m RETURN count(m) AS pwn //" full query string actually sent to lc_graph.query(): MERGE (n:`Entity` {id: 'pwn\\' }) MATCH (m) DETACH DELETE m RETURN count(m) AS pwn //'}) RETURN distinct 'done' AS result Parse trace (standard backslash-escape convention for single-quoted Cypher strings): Opening ' starts the id string literal. pwn is consumed literally. \\ (two backslash chars) is consumed as one escaped-backslash sequence — the string now holds one literal backslash. The very next character is an unescaped ' — this closes the string literal early. id value ends up as "pwn\" (with a trailing backslash). The remaining text }) MATCH (m) DETACH DELETE m RETURN count(m) AS pwn //... is now parsed as live Cypher syntax, not string content. }) closes the property map and the node pattern — completing a valid MERGE clause. MATCH (m) DETACH DELETE m RETURN count(m) AS pwn is a second, fully valid Cypher clause chained immediately after the MERGE (Cypher allows sequential clauses with no separator). // starts a line comment, consuming the rest of the original template ('}) RETURN distinct 'done' AS result) so it never executes. Effective query the database actually receives: MERGE (n:`Entity` {id: 'pwn\'}) MATCH (m) DETACH DELETE m RETURN count(m) AS pwn - a syntactically complete, two-clause Cypher statement that creates one throwaway node and then deletes the entire graph. Suggested Fix Escape \ before ' (fixes the ordering bug, same class of fix as the litellm report): def _escape(v: str) -> str: return str(v).replace("\\", "\\\\").replace("'", "\\'") Stronger fix, recommended in addition: use FalkorDB's parameterized query support (lc_graph.query(query, params={...})) instead of string interpolation entirely, and reject/escape backtick characters in node.type/rel_type/rel.source.type/rel.target.type before using them as label/relationship-type identifiers (these have no escaping at all today). One caveat (same as original internal triage) This was verified as a pure string-construction bug (steps 1-8 above are executed, not hypothesized) and confirmed against FalkorDB's Cypher-compatible documentation's stated backslash-escape convention for string literals, but was not run ag

References

Community reviews

Loading reviews…