fix(mcp): Block destructive DDL (DROP, TRUNCATE, ALTER) in execute_sql (#39621)

This commit is contained in:
Alexandru Soare
2026-05-20 14:29:15 +03:00
committed by GitHub
parent 0a3a35018c
commit b98bd2a07a
4 changed files with 310 additions and 3 deletions

View File

@@ -439,6 +439,14 @@ class BaseSQLStatement(Generic[InternalRepresentation]):
"""
raise NotImplementedError()
def is_destructive(self) -> bool:
"""
Check if the statement is destructive DDL (DROP, TRUNCATE, ALTER).
:return: True if the statement is destructive DDL.
"""
raise NotImplementedError()
def optimize(self) -> BaseSQLStatement[InternalRepresentation]:
"""
Return optimized statement.
@@ -719,6 +727,31 @@ class SQLStatement(BaseSQLStatement[exp.Expression]):
return False
def is_destructive(self) -> bool:
"""
Check if the statement is destructive DDL (DROP, TRUNCATE, ALTER).
Unlike ``is_mutating()``, this excludes non-destructive DML
(INSERT, UPDATE, DELETE, MERGE) and CREATE.
:return: True if the statement is destructive DDL.
"""
destructive_nodes = (
exp.Drop,
exp.TruncateTable,
exp.Alter,
)
for node_type in destructive_nodes:
if self._parsed.find(node_type):
return True
# Handle ALTER parsed as Command (Oracle, MS SQL dialects)
if isinstance(self._parsed, exp.Command) and self._parsed.name == "ALTER":
return True # pragma: no cover
return False
def format(self, comments: bool = True) -> str:
"""
Pretty-format the SQL statement.
@@ -1175,6 +1208,18 @@ class KustoKQLStatement(BaseSQLStatement[str]):
"""
return self._parsed.startswith(".") and not self._parsed.startswith(".show")
def is_destructive(self) -> bool:
"""
Check if the statement is destructive DDL.
Kusto KQL uses dot-commands for management operations. Destructive
operations start with ``.drop`` or ``.alter``.
:return: True if the statement is destructive DDL.
"""
lower = self._parsed.lower()
return lower.startswith(".drop") or lower.startswith(".alter")
def optimize(self) -> KustoKQLStatement:
"""
Return optimized statement.
@@ -1321,6 +1366,14 @@ class SQLScript:
"""
return any(statement.is_mutating() for statement in self.statements)
def has_destructive(self) -> bool:
"""
Check if the script contains destructive DDL (DROP, TRUNCATE, ALTER).
:return: True if any statement is destructive DDL.
"""
return any(statement.is_destructive() for statement in self.statements)
def optimize(self) -> SQLScript:
"""
Return optimized script.