From e351a0df74e2f11c629dbe305a6b14fe42233274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 28 Jan 2026 23:54:49 +0100 Subject: [PATCH] feat: Add leading comments (--) detection for 'run_sql_readonly' Read only queries are no longer flagged as non-readonly when starting with double dash comments. --- include/MySQL_Tool_Handler.h | 16 +++++++++++ include/Query_Tool_Handler.h | 16 +++++++++++ lib/MySQL_Tool_Handler.cpp | 52 ++++++++++++++++++++++++++++++++++-- lib/Query_Tool_Handler.cpp | 32 ++++++++++++++++++++++ 4 files changed, 114 insertions(+), 2 deletions(-) diff --git a/include/MySQL_Tool_Handler.h b/include/MySQL_Tool_Handler.h index 459c0077d..66897e997 100644 --- a/include/MySQL_Tool_Handler.h +++ b/include/MySQL_Tool_Handler.h @@ -285,6 +285,22 @@ public: int timeout_sec = 2 ); + /** + * @brief Strip simple SQL comments from the start of a query + * + * Removes leading '-- ' style comments from SQL queries. + * Handles multiple comment lines and whitespace before/after comments. + * This is a simple pre-processing step to allow queries with leading comments. + * + * @param sql The SQL query that may have leading comments + * @return SQL query with leading comments removed + * + * @note Only removes comments from the START of the query + * @note Does not handle inline comments (comments within the query) + * @note Does not handle block comments + */ + std::string strip_leading_comments(const std::string& sql); + /** * @brief Explain a query (EXPLAIN/EXPLAIN ANALYZE) * @param sql SQL query to explain diff --git a/include/Query_Tool_Handler.h b/include/Query_Tool_Handler.h index 58877a030..9fcbbfe83 100644 --- a/include/Query_Tool_Handler.h +++ b/include/Query_Tool_Handler.h @@ -146,6 +146,22 @@ private: */ bool is_dangerous_query(const std::string& query); + /** + * @brief Strip simple SQL comments from the start of a query + * + * Removes leading '-- ' style comments from SQL queries. + * Handles multiple comment lines and whitespace before/after comments. + * This is a simple pre-processing step to allow queries with leading comments. + * + * @param sql The SQL query that may have leading comments + * @return SQL query with leading comments removed + * + * @note Only removes comments from the START of the query + * @note Does not handle inline comments (comments within the query) + * @note Does not handle block comments + */ + std::string strip_leading_comments(const std::string& sql); + // Friend function for tracking tool invocations friend void track_tool_invocation(Query_Tool_Handler*, const std::string&, const std::string&, const std::string&, unsigned long long); diff --git a/lib/MySQL_Tool_Handler.cpp b/lib/MySQL_Tool_Handler.cpp index 9209dd4d8..e66c83734 100644 --- a/lib/MySQL_Tool_Handler.cpp +++ b/lib/MySQL_Tool_Handler.cpp @@ -1049,6 +1049,52 @@ std::string MySQL_Tool_Handler::sample_distinct( return result.dump(); } +/** + * @brief Strip simple SQL comments from the start of a query + * + * Removes leading '-- ' style comments from SQL queries. + * Handles multiple comment lines and whitespace before/after comments. + * This is a simple pre-processing step to allow queries with leading comments. + * + * @param sql The SQL query that may have leading comments + * @return SQL query with leading comments removed + * + * @note Only removes comments from the START of the query + * @note Does not handle inline comments (comments within the query) + * @note Does not handle \/\* *\/ style comments + */ +std::string MySQL_Tool_Handler::strip_leading_comments(const std::string& sql) { + std::string result = sql; + size_t pos = 0; + size_t len = result.length(); + + // Skip any leading whitespace + while (pos < len && isspace(result[pos])) { + pos++; + } + + // Remove leading '-- ' comment lines + while (pos < len && result.substr(pos, 3) == "-- ") { + // Found a comment, skip to end of line + while (pos < len && result[pos] != '\n') { + pos++; + } + + // Skip the newline + if (pos < len && result[pos] == '\n') { + pos++; + } + + // Skip any leading whitespace before next comment + while (pos < len && isspace(result[pos])) { + pos++; + } + } + + // Return the query without leading comments + return result.substr(pos); +} + std::string MySQL_Tool_Handler::run_sql_readonly( const std::string& sql, int max_rows, @@ -1057,14 +1103,16 @@ std::string MySQL_Tool_Handler::run_sql_readonly( json result; result["success"] = false; + // Strip leading comments from the query + std::string query = strip_leading_comments(sql); + // Validate query is read-only - if (!validate_readonly_query(sql)) { + if (!validate_readonly_query(query)) { result["error"] = "Query validation failed: not SELECT-only or contains dangerous keywords"; return result.dump(); } // Add LIMIT if not present and not an aggregate query - std::string query = sql; std::string upper = sql; std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper); diff --git a/lib/Query_Tool_Handler.cpp b/lib/Query_Tool_Handler.cpp index c486525fe..c8a2bba88 100644 --- a/lib/Query_Tool_Handler.cpp +++ b/lib/Query_Tool_Handler.cpp @@ -610,6 +610,35 @@ bool Query_Tool_Handler::is_dangerous_query(const std::string& query) { return false; } +std::string Query_Tool_Handler::strip_leading_comments(const std::string& sql) { + std::string result = sql; + size_t pos = 0; + size_t len = result.length(); + + // Skip leading whitespace + while (pos < len && isspace(result[pos])) { + pos++; + } + + // Remove leading '-- ' comment lines + while (pos < len && result.substr(pos, 2) == "--") { + // Skip until end of line + while (pos < len && result[pos] != '\n') { + pos++; + } + // Skip the newline + if (pos < len && result[pos] == '\n') { + pos++; + } + // Skip leading whitespace after the comment + while (pos < len && isspace(result[pos])) { + pos++; + } + } + + return result.substr(pos); +} + json Query_Tool_Handler::create_tool_schema( const std::string& tool_name, const std::string& description, @@ -1749,6 +1778,9 @@ json Query_Tool_Handler::execute_tool(const std::string& tool_name, const json& delete qpo; + // Strip leading comments from query + sql = strip_leading_comments(sql); + // Continue with validation and execution if (!validate_readonly_query(sql)) { result = create_error_response("SQL is not read-only");