mirror of
https://github.com/microsoft/mcp.git
synced 2026-11-04 12:01:31 +00:00
* Fixed input validation issue in MySql and Postgres * Added MySql validation test cases * Fix Postgres SQL validator: tokenize string-stripped content and add regex timeout - Tokenize withoutStrings instead of decodedForValidation to avoid false positives when dangerous keywords appear inside string literals (e.g., SELECT 'drop table' AS msg) - Only strip U&'...' (string literals) not U&"..." (identifiers) per PostgreSQL spec, so obfuscated function names in identifiers are still caught by tokenization - Add RegexHelper.DefaultRegexTimeout to DecodeUnicodeEscapes for consistency with other regex usage in the validator - Fix whitespace formatting in SqlQueryValidatorTests.cs closing brace - Move U&'...' test case to safe queries (string literals cannot invoke functions per PostgreSQL spec) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Marked MySql and Postgres query commands as destructive, not idempotent, and not read-only * Block the uescape command in validator * Remove SQL verb/keyword blocklists from MySQL and Postgres query validation The MySQL and Postgres database query tools are now marked destructive, so rejecting statements by SQL verb or dangerous-identifier blocklist is no longer meaningful - the signed-in user's database permissions are the authority. Validation is reduced to structural checks: empty input, max length, SQL comments, and multiple/stacked statements. The Unicode/National-string de-obfuscation logic (which only existed to feed the blocklists) is removed, along with the unfinished UESCAPE check that did not compile. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Reword changelog entries to fix cspell failure Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Move MySQL/Postgres query tools to a destructive consolidated tool The consolidated tool discovery strategy requires every mapped command's ToolMetadata to match its consolidated tool exactly. Marking the query commands destructive left them mismatched against the read-only get_azure_databases_details tool, which made the server fail to start in consolidated mode. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Test consolidated tool metadata mappings Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Vinay Gera <vigera@microsoft.com>
79 lines
3.3 KiB
C#
79 lines
3.3 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using Azure.Mcp.Tools.Postgres.Validation;
|
|
using Microsoft.Mcp.Core.Commands;
|
|
using Xunit;
|
|
|
|
namespace Azure.Mcp.Tools.Postgres.Tests.Validation;
|
|
|
|
public class SqlQueryValidatorTests
|
|
{
|
|
[Theory]
|
|
[InlineData("SELECT * FROM users LIMIT 100")]
|
|
[InlineData("SELECT COUNT(*) FROM products LIMIT 1")]
|
|
[InlineData("SELECT COUNT(*) FROM products;")]
|
|
[InlineData("SELECT * FROM users WHERE name = 'foo--bar'")]
|
|
[InlineData("SELECT * FROM users WHERE name = 'back\\\\slash'")]
|
|
[InlineData("SELECT * FROM users WHERE name = E'it\\'s a test'")]
|
|
[InlineData("SELECT * FROM users WHERE name = E'back\\\\slash'")]
|
|
[InlineData("SELECT * FROM users WHERE data = '/* not a comment */'")]
|
|
[InlineData("SELECT * FROM user_deletions")]
|
|
[InlineData("SELECT * FROM datasets")]
|
|
[InlineData("SELECT * FROM reunion_events")]
|
|
[InlineData("SELECT preset FROM config")]
|
|
[InlineData("SELECT * FROM intersections")]
|
|
[InlineData("SELECT * FROM exceptions")]
|
|
[InlineData("SELECT 'drop table' AS msg FROM users")]
|
|
[InlineData("SELECT * FROM users WHERE note = 'pg_sleep is bad'")]
|
|
[InlineData("SELECT 1 UNION SELECT 2")]
|
|
[InlineData("SELECT * FROM users WHERE id = 1 or 1=1")]
|
|
[InlineData("INSERT INTO users VALUES (1)")]
|
|
[InlineData("UPDATE users SET name = 'x'")]
|
|
[InlineData("DELETE FROM users WHERE id = 1")]
|
|
[InlineData("DROP TABLE users")]
|
|
public void ValidateQuery_WithAcceptedQueries_ShouldNotThrow(string query)
|
|
{
|
|
SqlQueryValidator.ValidateQuery(query);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("SELECT 1 -- line comment")]
|
|
[InlineData("SELECT 1 /* block comment */")]
|
|
[InlineData("SELECT 'foo\\' /* ' FROM pg_shadow --'")] // backslash does not escape quotes in standard strings
|
|
[InlineData("SELECT * FROM users WHERE name = 'it\\'s a test -- ok'")] // \' is not an escape in standard SQL
|
|
public void ValidateQuery_WithComments_ShouldThrow(string query)
|
|
{
|
|
var exception = Assert.Throws<CommandValidationException>(() => SqlQueryValidator.ValidateQuery(query));
|
|
Assert.Contains("Comments are not allowed", exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("SELECT * FROM users; DROP TABLE users")]
|
|
[InlineData("SELECT * FROM users; SELECT * FROM products")]
|
|
public void ValidateQuery_WithMultipleStatements_ShouldThrow(string query)
|
|
{
|
|
var exception = Assert.Throws<CommandValidationException>(() => SqlQueryValidator.ValidateQuery(query));
|
|
Assert.Contains("Multiple or stacked SQL statements are not allowed", exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
[InlineData(null)]
|
|
public void ValidateQuery_WithEmptyOrNullQuery_ShouldThrow(string? query)
|
|
{
|
|
var exception = Assert.Throws<CommandValidationException>(() => SqlQueryValidator.ValidateQuery(query));
|
|
Assert.Contains("Query cannot be empty", exception.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateQuery_WithLongQuery_ShouldThrow()
|
|
{
|
|
var longQuery = "SELECT * FROM users WHERE " + new string('X', 5001);
|
|
|
|
var exception = Assert.Throws<CommandValidationException>(() => SqlQueryValidator.ValidateQuery(longQuery));
|
|
Assert.Contains("Query length exceeds limit", exception.Message);
|
|
}
|
|
}
|