CVE-2026-48774 Overview
CVE-2026-48774 is an input validation flaw [CWE-20] in ProxySQL, a proxy for MySQL, its forks, and PostgreSQL. The vulnerability affects ProxySQL versions 3.0.0 through 3.0.8. The GenAI/MCP run_sql_readonly tool fails to enforce its documented read-only contract against MySQL targets. A caller can chain a benign SELECT with a side-effecting statement such as RENAME TABLE, SET, RESET, LOCK TABLES, or KILL. The validator approves the payload because it begins with SELECT, while the backend connection uses CLIENT_MULTI_STATEMENTS and executes every statement in the string.
Critical Impact
An MCP caller can perform backend writes or administrative SQL against MySQL targets through an endpoint advertised as read-only, bounded only by the configured MCP account's database privileges.
Affected Products
- ProxySQL 3.0.0 through 3.0.8
- ProxySQL GenAI/MCP run_sql_readonly tool
- ProxySQL /mcp/query endpoint with MySQL backend targets
Discovery Timeline
- 2026-06-19 - CVE-2026-48774 published to NVD
- 2026-06-23 - Last updated in NVD database
Technical Details for CVE-2026-48774
Vulnerability Analysis
ProxySQL's run_sql_readonly MCP tool is intended to permit only non-mutating SQL against MySQL backends. The tool inspects the entire input string with a substring blacklist and a first-keyword allowlist. Validation succeeds when the first keyword is SELECT and no blacklisted substring is found.
The backend connection used to execute the query is established with the CLIENT_MULTI_STATEMENTS MySQL client capability flag. This flag allows the server to execute multiple semicolon-separated statements in a single round trip. The validator never tokenizes statement boundaries, so any number of additional statements can ride along after the leading SELECT.
In a runtime test against the /mcp/query endpoint, a run_sql_readonly request of the form SELECT 1; RENAME TABLE ... returned success for the SELECT while direct backend verification confirmed the table had been renamed. The impact is bounded by the privileges of the MCP target account.
Root Cause
The validator treats the SQL payload as a single statement. It checks only the leading keyword and a substring blacklist that omits several side-effecting MySQL statements including RENAME TABLE, SET, RESET, LOCK TABLES, and KILL. Combined with CLIENT_MULTI_STATEMENTS on the backend connection, this allows trailing statements to execute despite the read-only contract.
Attack Vector
An attacker who can reach /mcp/query submits a run_sql_readonly request whose SQL body begins with a permitted read-only statement and appends a side-effecting statement after a semicolon. If mcp-query_endpoint_auth is unset or weak and the listener is network-exposed, no authentication is required. The backend executes both statements under the MCP target account.
// Patch excerpt: plugins/genai/include/MySQL_Tool_Handler.h
// fix(security): harden MySQL_Tool_Handler parallel class (GHSA-7wh6-2vcc-gcm4)
bool is_dangerous_query(const std::string& query);
/**
* @brief Detect multi-statement payloads
*
* GHSA-7wh6-2vcc-gcm4: returns true if @p query contains a ';'
* outside of string literals, identifier quoting (backticks), or
* comments, followed by any non-whitespace content (i.e. a second
* statement). A bare trailing ';' is permitted.
*/
bool contains_multi_statement(const std::string& query);
/**
* @brief Sanitize SQL to prevent injection
* @param query SQL to sanitize
*/
Source: ProxySQL commit e32b7fd. The fix adds a contains_multi_statement check that scans for semicolons outside string literals, backtick-quoted identifiers, and comments, rejecting any payload that contains a second statement.
Detection Methods for CVE-2026-48774
Indicators of Compromise
- Requests to /mcp/query invoking run_sql_readonly with payloads containing semicolons followed by non-whitespace SQL.
- Backend audit log entries showing RENAME TABLE, SET, RESET, LOCK TABLES, or KILL statements originating from the MCP target account.
- Schema or table-name changes on MySQL backends correlated in time with MCP traffic.
Detection Strategies
- Enable MySQL general or audit logging on backends used as MCP targets and alert on write or administrative statements issued by the MCP account.
- Inspect ProxySQL query logs for multi-statement payloads routed through run_sql_readonly.
- Compare ProxySQL /mcp/query response bodies against backend execution evidence to surface statements the MCP layer did not acknowledge.
Monitoring Recommendations
- Monitor network exposure of the MCP listener and alert on connections from sources outside an approved allowlist.
- Track ProxySQL version inventory and flag any instance running 3.0.0 through 3.0.8.
- Alert on changes to mcp-query_endpoint_auth configuration or MCP target account privileges.
How to Mitigate CVE-2026-48774
Immediate Actions Required
- Upgrade ProxySQL to version 3.0.9, which contains the fix.
- Disable MCP unless explicitly required by the deployment.
- Set a non-empty mcp-query_endpoint_auth token before exposing /mcp/query.
- Restrict the MCP listener to trusted network segments and authenticated callers only.
Patch Information
ProxySQL 3.0.9 introduces a contains_multi_statement validator that rejects payloads containing a semicolon followed by additional SQL. Details are in the GitHub Security Advisory GHSA-7wh6-2vcc-gcm4 and the ProxySQL commit e32b7fd.
Workarounds
- Configure MCP backend target credentials as database-level read-only users so trailing statements lack privilege to mutate data.
- Add temporary MCP query rules that block obvious multi-statement patterns containing ; followed by additional tokens.
- Place the MCP listener behind an authenticating reverse proxy and restrict source addresses via firewall rules.
# Configuration example: enforce auth and read-only backend account
# 1. Require an auth token on /mcp/query
mysql -u admin -padmin -h 127.0.0.1 -P6032 -e \
"SET mcp-query_endpoint_auth='REPLACE_WITH_STRONG_TOKEN'; LOAD MCP VARIABLES TO RUNTIME; SAVE MCP VARIABLES TO DISK;"
# 2. Create a strictly read-only backend account for MCP
mysql -u root -p -e "\
CREATE USER 'mcp_ro'@'%' IDENTIFIED BY 'REPLACE_WITH_STRONG_PASSWORD'; \
GRANT SELECT, SHOW VIEW ON appdb.* TO 'mcp_ro'@'%'; \
FLUSH PRIVILEGES;"
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

