GHSA-vqrg-773h-4wxc: High severity pip/flexible-graphrag vulnerability

Published Oct 27, 2026
·
Updated

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.lcgraph.query( f"MERGE (n:{node.type} {{id: '{escape(node.id)}'}}) " f"{props}" f"RETURN distinct 'done' AS result" ) ... self.lcgraph.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:{reltype}]->(b) " f"{props}" f"RETURN distinct 'done' AS result" ) node.type, rel.source.type, rel.target.type and reltype 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 lcgraph.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 lcgraph.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 (lcgraph.query(query, params={...})) instead of string interpolation entirely, and reject/escape backtick characters in node.type/reltype/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 against a live FalkorDB instance to observe the parser's exact behavior byte-for-byte. If FalkorDB's dialect deviates from this convention, the specific PoC payload above may need minor adjustment — the underlying escape-ordering defect itself is confirmed either way.

Maintainer note: confirmed exploitable against a live FalkorDB. FalkorDB requires a WITH between MERGE and MATCH, so the payload needs "WITH 1 AS x" added; with it, the payload deleted every node in the graph. Fixed in 0.8.2: values are passed as query parameters and labels/relationship types/property keys are stripped of backticks.

Affected configuration: GRAPHBACKEND=langchain with GRAPHDB=falkordb only. The LlamaIndex FalkorDB store is not affected.

Workaround before upgrading: use GRAPHBACKEND=llamaindex with FalkorDB, or another graph database.

Affected Software

1 affected componentFixes available
pip/flexible-graphrag>=0.6.0<0.8.2
0.8.2

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/flexible-graphrag to a version that resolves this vulnerability.

    Fixed in 0.8.2
  2. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Fixed in 0.8.2
  3. Configuration

    Before upgrading, set GRAPH_BACKEND=llamaindex when using FalkorDB, or use another graph database.

    Graph backend GRAPH_BACKEND = llamaindex
  4. Compensating control

    Use FalkorDB's parameterized query support via lc_graph.query(query, params={...}) instead of interpolating values directly into Cypher query strings.

  5. Compensating control

    Reject or escape backtick characters in node.type, rel_type, rel.source.type, and rel.target.type before using them as label or relationship-type identifiers.

Event History

Oct 27, 2026
Advisory Published
via GitHub·03:32 PM
Data Sourced
via GitHub·03:32 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are exposed to exploitation?

Deployments are exposed when untrusted input can reach node IDs or relationship source or target IDs that are used to construct Cypher queries. The affected method interpolates escaped values directly into query strings at multiple call sites.

2

What must an attacker control to exploit this issue?

An attacker needs the ability to supply a value that is inserted into one of the affected Cypher string literals, such as a node ID or relationship endpoint ID. A value ending in a single quote can cause the intended string literal to terminate.

3

How can I determine whether my code is affected?

Inspect the graph-query construction code for an _escape function that replaces single quotes before backslashes, followed by f-string or other direct interpolation into Cypher queries. The described affected method has five such query-construction call sites.

4

What can be done while a patch is not available?

Prevent untrusted values from reaching node IDs and relationship source or target IDs used in these query-building paths. The vulnerable behavior depends on directly formatting those values into Cypher string literals rather than using query parameters.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203