graphiti-core Cypher Injection via Unsanitized node_labels — CVE-2026-32247
by romain-deperne (ang3L) · 2026-07-05
- Severity
- High
- CVE
- CVE-2026-32247 (GHSA, getzep/graphiti)
- Category
- web
- Affected product
- graphiti-core (getzep/graphiti) — memory/graph layer used by AI agent frameworks, exposed via an MCP server
- Affected versions
- graphiti-core <= 0.28.1 (fixed in 0.28.2)
- Disclosed
- 2026-07-05
- Patch status
- patched
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-04 |
| Author / Researcher | romain-deperne (ang3L) |
| CVE / Advisory | CVE-2026-32247 (GHSA, getzep/graphiti) |
| Category | web |
| Severity | High |
| CVSS Score | 8.1 |
| Status | PoC |
| Tags | cypher-injection, neo4j, falkordb, graphiti, mcp, ai-agent, tenant-isolation-bypass, cwe-943 |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | graphiti-core (getzep/graphiti) — memory/graph layer used by AI agent frameworks, exposed via an MCP server |
| Versions Affected | graphiti-core <= 0.28.1 (fixed in 0.28.2) |
| Language / Platform | Python; Neo4j and FalkorDB Cypher backends (Kuzu backend unaffected) |
| Authentication Required | No (any MCP client connected to the server) |
| Network Access Required | Yes (via the MCP search_nodes tool interface) |
Summary
graphiti-core builds Cypher WHERE clauses for its search_nodes functionality by joining caller-supplied node label strings with | and concatenating the result directly into a raw query string, with no parameterization or input validation anywhere in the call chain. Because the label filter is wrapped in parentheses by the caller (n:Label1|Label2), an attacker-controlled label value can close that expression with a backtick and parenthesis, chain a WITH clause to pivot into an entirely new query, and use // to comment out the remainder of the original statement. The included PoC reproduces the exact vulnerable build_filter() logic from search_filters.py and demonstrates payloads for cross-tenant graph exfiltration and full graph deletion via DETACH DELETE.
Vulnerability Details
Root Cause
graphiti_core/search/search_filters.py computes node_label_filter = 'n:' + '|'.join(filters.node_labels) and interpolates it unsanitized into a Cypher WHERE clause (CWE-943); the entity_types parameter flowing in from the MCP search_nodes tool handler is passed straight through with no allowlisting.
Attack Vector
- Attacker (any client able to invoke the exposed MCP
search_nodestool) submits asearch_nodescall with a craftedentity_typesarray, e.g.["Entity) WITH n MATCH (x) RETURN x //"]`. - The string is joined and concatenated into the Cypher query as
WHERE (n:Entity) WITH n MATCH (x) RETURN x // AND …`. - The backtick-paren sequence closes the original
WHEREexpression,WITH npipelines into a completely newMATCHclause chosen by the attacker, and//comments out the rest of the legitimate query (including thegroup_idtenant-isolation filter, which is applied after the injectable clause). - The attacker’s injected
MATCH/RETURN(orDETACH DELETE) executes with the graph database’s full privileges, returning data across all tenants or destroying the graph.
Impact
Cross-tenant data exfiltration (reading any node/relationship regardless of group_id) and full graph destruction via DETACH DELETE, exploitable by any MCP client connected to a Graphiti-backed agent with no additional authentication.
Environment / Lab Setup
Target: graphiti-core <= 0.28.1 with Neo4j or FalkorDB backend, MCP server (graphiti_mcp_server.py) exposing search_nodes
Attacker: Python 3.10+ (stdlib json only) to run the standalone reproduction script; an MCP client to trigger it live
Proof of Concept
PoC Script
See
poc.pyin this folder.
| |
The script reproduces build_filter() verbatim from the vulnerable source, prints the benign vs. malicious filter strings and the resulting full Cypher statements for an exfiltration payload, a cross-tenant exfiltration payload, and a DETACH DELETE destructive payload, and prints example MCP search_nodes JSON tool-call bodies that trigger each.
Detection & Indicators of Compromise
WHERE (n:Entity`) WITH n MATCH (x) DETACH DELETE x // AND n.group_id = $group_id
Signs of compromise:
- MCP
search_nodescalls whoseentity_typesvalues contain backticks, parentheses,WITH,MATCH, or//sequences. - Cypher query logs (Neo4j/FalkorDB) showing statements with an unexpected second
MATCH/WITHpipeline orDETACH DELETEfollowing a label filter. - Sudden, unexplained drop in node/relationship counts across the graph (indicative of a destructive injection having run).
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade to graphiti-core 0.28.2 or later, which allowlists node labels to alphanumeric characters and parameterizes group_id in the fulltext search path |
| Interim mitigation | Validate/allowlist entity_types/node_labels input to alphanumeric characters at the MCP tool boundary before upgrading; monitor Cypher query logs for anomalous WITH/DETACH patterns. |
References
Notes
Mirrored from https://github.com/romain-deperne/CVE-2026-32247 on 2026-07-05.
| |