CVE-2026-49257
mcp-pinot's insecure default allows full read/write access to Pinot.
- CVSS 10.0
- CWE-306
- Authentication, Authorization, and Session Management
- Remote
mcp-pinot is a Python-based Model Context Protocol (MCP) server for interacting with Apache Pinot. In versions 3.0.1 and below, mcp-pinot defaults to running an HTTP MCP server bound to 0.0.0.0:8080 with no authentication enabled. All MCP tools, including SQL query execution, schema creation, and table-config mutation, are reachable by any network-adjacent caller. The server proxies these calls using server-side Pinot credentials, producing a confused-deputy condition that yields full read/write access to the configured Pinot cluster. This issue has been fixed in version 3.1.0
- CWE
- CWE-306
- CVSS base score
- 10.0
- Published
- 2026-06-18
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Server-Side Request Forgery (SSRF)
- Accessibility scope
- Remote
- Impact
- Privilege Escalation
- Affected component
- mcp-pinot
- Fixed by upgrading
- Yes
Solution
Upgrade to mcp-pinot version 3.1.0.
Vulnerable code sample
import os
from flask import Flask, request, jsonify
# In a real application, these credentials would be loaded from a secure
# configuration or environment variables and used to connect to Pinot.
# This simulates the server having privileged access.
PINOT_CONTROLLER = os.getenv("PINOT_CONTROLLER", "http://localhost:9000")
PINOT_AUTH_TOKEN = os.getenv("PINOT_AUTH_TOKEN", "server-secret-admin-token")
app = Flask(__name__)
def execute_proxied_pinot_query(sql_query):
"""
Simulates the server using its own credentials to execute a query
on the backend Pinot cluster. This is the "confused deputy".
"""
print(f"Proxying query to Pinot controller: {PINOT_CONTROLLER}")
print(f"Using server's internal auth token to execute: {sql_query}")
# In a real implementation, this would use a library like 'requests'
# to send the query to the Pinot controller with the auth token.
# e.g., requests.post(..., headers={'Authorization': f'Bearer {PINOT_AUTH_TOKEN}'})
return {"result": f"successfully executed '{sql_query}'", "node": "pinot-broker-1"}
@app.route('/mcp/sql', methods=['POST'])
def handle_sql_query():
"""
An open endpoint that accepts a SQL query and executes it.
No authentication or authorization is performed on the incoming request.
"""
data = request.get_json()
if not data or 'query' not in data:
return jsonify({"error": "Missing 'query' in request body"}), 400
query = data['query']
# The server executes the user-provided query with its own privileges.
result = execute_proxied_pinot_query(query)
return jsonify(result)
if __name__ == '__main__':
# The server defaults to binding on 0.0.0.0, making it accessible
# to any network-adjacent caller.
# The default port is 8080.
# No authentication is configured for the Flask app.
app.run(host='0.0.0.0', port=8080)Patched code sample
import click
# This code represents the fix for the vulnerability described.
# The original vulnerable code defaulted the 'host' parameter to '0.0.0.0',
# which binds the server to all network interfaces, making it accessible
# to any network-adjacent caller.
#
# The fix, shown below, is to change the default value for 'host' to
# '127.0.0.1'. This ensures that by default, the server only binds to the
# localhost interface and is not exposed to the network, mitigating the
# "confused-deputy" vulnerability. The user must now explicitly set
# --host 0.0.0.0 to achieve the previous, less secure behavior.
@click.command()
@click.option(
"--host",
# This default value is the fix. It was previously "0.0.0.0".
default="127.0.0.1",
show_default=True,
help="The host on which to run the server.",
)
@click.option(
"--port",
default=8080,
show_default=True,
help="The port on which to run the server.",
)
def serve(host, port):
"""
A simplified representation of the mcp-pinot server startup command.
The key change is the default value for the '--host' option.
"""
print(f"Starting server on http://{host}:{port}...")
if host == "127.0.0.1":
print("FIX APPLIED: Server is correctly bound to localhost by default.")
elif host == "0.0.0.0":
print("WARNING: Server is bound to all interfaces and is network-accessible.")
# In the actual application, this function would proceed to start
# a web server (e.g., using wsgiref.simple_server) with the provided
# host and port.
#
# from wsgiref.simple_server import make_server, demo_app
# with make_server(host, port, demo_app) as httpd:
# httpd.serve_forever()
if __name__ == '__main__':
# Running the script without arguments uses the new, secure default.
# > python your_script_name.py
# Output:
# Starting server on http://127.0.0.1:8080...
# FIX APPLIED: Server is correctly bound to localhost by default.
serve()Payload
curl -X POST http://VULNERABLE_HOST:8080/query/sql \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT tableName FROM \"pinot_server_tables\""}'
Cite this entry
@misc{vaitp:cve202649257,
title = {{mcp-pinot's insecure default allows full read/write access to Pinot.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-49257},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-49257/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
