Apr 17, 2026·5 min read·5 visits
A bypass in mcp-neo4j-cypher's read-only mode allows data modification and SSRF via Cypher stored procedures (CALL) due to an incomplete regex blocklist.
CVE-2026-35402 is an improper access control vulnerability in the mcp-neo4j-cypher server. The application implements a read-only mode using a regex-based keyword blocklist, which fails to restrict execution of Cypher stored procedures via the CALL keyword. This allows authenticated users or LLM agents to bypass restrictions, potentially leading to unauthorized data modification and Server-Side Request Forgery.
The mcp-neo4j-cypher server functions as a Model Context Protocol (MCP) bridge, enabling Large Language Models to interact directly with Neo4j graph databases. To mitigate the risk of untrusted AI-generated queries modifying the database, the server includes a read_only configuration mode. This mode intends to restrict query execution to data retrieval operations.
The vulnerability, identified as CWE-284 (Improper Access Control), resides in the application's method for enforcing this read-only state. The server employs a fail-open, regular expression-based keyword blocklist. If an incoming query does not contain specific data manipulation language (DML) keywords, the server forwards the query to the backend database.
This implementation fails to account for Cypher's extensibility via stored procedures. By utilizing the CALL keyword, an attacker can invoke backend procedures that manipulate data or interact with the underlying operating system. The application-layer filter ignores the CALL keyword, allowing destructive queries to pass through to the database layer unaffected.
The root cause of the vulnerability exists in the _is_write_query function within server.py. The application relies entirely on semantic parsing of the query string using a regular expression. The system enforces access control by attempting to identify write-oriented commands, rather than explicitly allowlisting safe read-oriented commands.
The initial regex pattern targeted a static list of explicit DML commands, specifically MERGE, CREATE, SET, DELETE, REMOVE, and ADD. This denylist approach is inherently fragile when applied to complex query languages. Neo4j allows the execution of arbitrary Java code via stored procedures, which bypass query-level semantic checks.
Because the CALL keyword was absent from the regular expression denylist, the application assumes any query beginning with CALL is a read operation. The server subsequently forwards the execution context to the Neo4j database. The procedure then executes with the permissions of the authenticated database user, completely circumventing the intended application-layer restrictions.
The vulnerable implementation utilizes the Python re.search function to evaluate incoming queries. The code snippet below demonstrates the implementation prior to version 0.5.0. The function returns a boolean value indicating whether the query constitutes a write operation based on strict keyword matching.
def _is_write_query(query: str) -> bool:
"""Check if the query is a write query."""
return (
re.search(r"\b(MERGE|CREATE|SET|DELETE|REMOVE|ADD)\b", query, re.IGNORECASE)
is not None
)In version 0.5.0, the developers attempted to patch a related bypass involving the INSERT keyword, which was introduced in modern Cypher syntax. The patch updated the regular expression but failed to address the broader structural flaw regarding stored procedures.
# From Commit 7bf941bb6d9d8cf95d91fd1209b2bf24fdc609d2 (v0.5.0)
- re.search(r"\b(MERGE|CREATE|SET|DELETE|REMOVE|ADD)\b", query, re.IGNORECASE)
+ re.search(r"\b(MERGE|CREATE|INSERT|SET|DELETE|REMOVE|ADD)\b", query, re.IGNORECASE)Version 0.6.0 resolves the primary bypass by explicitly adding the CALL keyword to the blocklist. Furthermore, the development team transitioned toward protocol-level enforcement. The application now implements RoutingControl.READ within the Neo4j driver for specific administrative procedures, reducing reliance on the regular expression filter.
Exploitation requires an attacker to submit a crafted Cypher query to the mcp-neo4j-cypher endpoint. The target server must operate with the NEO4J_READ_ONLY=true environment variable enabled. The downstream Neo4j database must grant execution permissions to the configured user account.
To perform unauthorized data modification, an attacker leverages standard Neo4j procedures. The query successfully bypasses the application-layer filter because the initial keyword does not trigger the regex check. The database processes the execution block and commits the transaction.
CALL apoc.create.node(['Vulnerable'], {name: 'Exploited'})If the target environment includes the Awesome Procedures on Cypher (APOC) library, the vulnerability facilitates Server-Side Request Forgery (SSRF). Attackers use network-enabled procedures to retrieve data from adjacent services or internal network interfaces. The following payload demonstrates internal metadata exfiltration.
CALL apoc.load.json("http://169.254.169.254/latest/meta-data/")
YIELD value RETURN valueThe primary impact of CVE-2026-35402 is unauthorized data modification within the Neo4j database. Despite explicit configuration requiring read-only behavior, attackers can execute write operations. The scale of the modification is constrained only by the privileges assigned to the database user account utilized by the MCP server.
The secondary impact is internal network exposure via SSRF. The Neo4j database server originates the outbound network requests during procedure execution. This mechanism allows external entities to interact with services located within the database server's internal network segment, bypassing perimeter firewalls.
The assigned CVSS 4.0 score of 2.3 categorizes this vulnerability as Low severity. This rating accounts for the specific prerequisites required for successful exploitation. The attack relies entirely on the target database granting sufficient permissions to the service account and the presence of libraries like APOC to maximize the available attack surface.
The immediate remediation requires upgrading the mcp-neo4j-cypher package to version 0.6.0. This release updates the _is_write_query validation logic to identify CALL statements as restricted operations. Administrators must verify the installed version across all application instances connecting to production databases.
Defense-in-depth requires implementing strict Role-Based Access Control (RBAC) at the database layer. Application-layer validation filters are inherently susceptible to bypasses. Administrators must provision a dedicated Neo4j user account for the MCP server and assign it explicitly limited privileges, such as a custom role restricted to TRAVERSE and READ operations.
Environments utilizing the APOC library must implement procedure allowlisting within the database configuration. Modifying the neo4j.conf file to define the dbms.security.procedures.allowlist variable restricts access to high-risk procedures. This database-level control mitigates the SSRF risk regardless of the application's validation logic.
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
mcp-neo4j-cypher Neo4j | < 0.6.0 | 0.6.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-284 |
| Attack Vector | Network |
| CVSS 4.0 Score | 2.3 |
| Impact | Unauthorized Data Modification / SSRF |
| Exploit Status | Proof of Concept |
| CISA KEV | No |
Improper Access Control