-
Notifications
You must be signed in to change notification settings - Fork 17.4k
fix(mcp): Block destructive DDL (DROP, TRUNCATE, ALTER) in execute_sql #39621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1218,3 +1218,153 @@ def test_model_validate_unknown(self): | |
| {"name": "c", "type": "int", "is_nullable": "UNKNOWN"} | ||
| ) | ||
| assert col.is_nullable is None | ||
|
|
||
|
|
||
| class TestDestructiveDDLBlocking: | ||
| """Tests for destructive DDL blocking in execute_sql.""" | ||
|
|
||
| @pytest.fixture | ||
| def ddl_mocks(self): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Add explicit type annotations to the fixture method parameters and return type so the new test fixture follows the typing rule for newly added functions. [custom_rule] Severity Level: Minor Why it matters? 🤔The newly added fixture method is unannotated: Fix in Cursor | Fix in VSCode Claude (Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py
**Line:** 1227:1227
**Comment:**
*Custom Rule: Add explicit type annotations to the fixture method parameters and return type so the new test fixture follows the typing rule for newly added functions.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
| """Common mock wiring for DDL blocking tests.""" | ||
| with ( | ||
| patch("superset.db") as mock_db, | ||
| patch("superset.security_manager") as mock_sm, | ||
| ): | ||
| mock_database = _mock_database() | ||
| mock_database.db_engine_spec.engine = "postgresql" | ||
| query_chain = mock_db.session.query.return_value | ||
| query_chain.filter_by.return_value.first.return_value = mock_database | ||
| mock_sm.can_access_database.return_value = True | ||
| yield mock_database | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_drop_table_blocked(self, ddl_mocks, mcp_server): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Add explicit type hints for all parameters and the return type on this newly added async test method. [custom_rule] Severity Level: Minor Why it matters? 🤔This added async test method has no type annotations for its parameters and no return type annotation. The suggestion correctly identifies a real typing omission in the new code. Fix in Cursor | Fix in VSCode Claude (Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py
**Line:** 1241:1241
**Comment:**
*Custom Rule: Add explicit type hints for all parameters and the return type on this newly added async test method.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
| """DROP TABLE is blocked before reaching the executor.""" | ||
| async with Client(mcp_server) as client: | ||
| result = await client.call_tool( | ||
| "execute_sql", | ||
| {"request": {"database_id": 1, "sql": "DROP TABLE birth_names"}}, | ||
| ) | ||
| data = result.structured_content | ||
| assert data["success"] is False | ||
| assert "Destructive DDL" in data["error"] | ||
| assert "DROP" in data["error"] | ||
| ddl_mocks.execute.assert_not_called() | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_truncate_blocked(self, ddl_mocks, mcp_server): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Annotate this new async test method with explicit parameter types and a return type to satisfy the typing requirement. [custom_rule] Severity Level: Minor Why it matters? 🤔The test method is newly introduced and untyped, with no parameter annotations and no return annotation. This is a genuine match for the stated typing requirement. Fix in Cursor | Fix in VSCode Claude (Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py
**Line:** 1255:1255
**Comment:**
*Custom Rule: Annotate this new async test method with explicit parameter types and a return type to satisfy the typing requirement.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
| """TRUNCATE TABLE is blocked before reaching the executor.""" | ||
| async with Client(mcp_server) as client: | ||
| result = await client.call_tool( | ||
| "execute_sql", | ||
| {"request": {"database_id": 1, "sql": "TRUNCATE TABLE birth_names"}}, | ||
| ) | ||
| data = result.structured_content | ||
| assert data["success"] is False | ||
| assert "Destructive DDL" in data["error"] | ||
| ddl_mocks.execute.assert_not_called() | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_alter_table_blocked(self, ddl_mocks, mcp_server): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Add type annotations for each argument and the return type on this newly introduced async method. [custom_rule] Severity Level: Minor Why it matters? 🤔This new async test function lacks both parameter type hints and a return type. The suggestion is therefore addressing a real violation of the typing rule. Fix in Cursor | Fix in VSCode Claude (Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py
**Line:** 1268:1268
**Comment:**
*Custom Rule: Add type annotations for each argument and the return type on this newly introduced async method.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
| """ALTER TABLE is blocked before reaching the executor.""" | ||
| async with Client(mcp_server) as client: | ||
| result = await client.call_tool( | ||
| "execute_sql", | ||
| { | ||
| "request": { | ||
| "database_id": 1, | ||
| "sql": "ALTER TABLE birth_names ADD COLUMN x INT", | ||
| } | ||
| }, | ||
| ) | ||
| data = result.structured_content | ||
| assert data["success"] is False | ||
| assert "Destructive DDL" in data["error"] | ||
| ddl_mocks.execute.assert_not_called() | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_drop_in_multi_statement_blocked(self, ddl_mocks, mcp_server): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Update this new async test method to include explicit parameter type hints and an annotated return type. [custom_rule] Severity Level: Minor Why it matters? 🤔The method is newly added and untyped, with no annotations on its parameters or return value. This is a valid typing-related issue under the stated rule. Fix in Cursor | Fix in VSCode Claude (Use Cmd/Ctrl + Click for best experience) Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py
**Line:** 1286:1286
**Comment:**
*Custom Rule: Update this new async test method to include explicit parameter type hints and an annotated return type.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix |
||
| """DROP TABLE hidden in a multi-statement query is blocked.""" | ||
| async with Client(mcp_server) as client: | ||
| result = await client.call_tool( | ||
| "execute_sql", | ||
| { | ||
| "request": { | ||
| "database_id": 1, | ||
| "sql": "DROP TABLE birth_names; SELECT 1", | ||
| } | ||
| }, | ||
| ) | ||
| data = result.structured_content | ||
| assert data["success"] is False | ||
| assert "Destructive DDL" in data["error"] | ||
| ddl_mocks.execute.assert_not_called() | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_select_allowed(self, ddl_mocks, mcp_server): | ||
| """SELECT queries pass through the DDL check.""" | ||
| ddl_mocks.execute.return_value = _create_select_result( | ||
| rows=[{"x": 1}], columns=["x"], original_sql="SELECT 1" | ||
| ) | ||
|
|
||
| async with Client(mcp_server) as client: | ||
| result = await client.call_tool( | ||
| "execute_sql", | ||
| {"request": {"database_id": 1, "sql": "SELECT 1"}}, | ||
| ) | ||
| data = result.structured_content | ||
| assert data["success"] is True | ||
| ddl_mocks.execute.assert_called_once() | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_insert_allowed(self, ddl_mocks, mcp_server): | ||
| """INSERT queries pass through the DDL check (DML is allowed).""" | ||
| ddl_mocks.execute.return_value = _create_dml_result( | ||
| affected_rows=1, | ||
| original_sql="INSERT INTO t VALUES (1)", | ||
| ) | ||
|
|
||
| async with Client(mcp_server) as client: | ||
| result = await client.call_tool( | ||
| "execute_sql", | ||
| {"request": {"database_id": 1, "sql": "INSERT INTO t VALUES (1)"}}, | ||
| ) | ||
| data = result.structured_content | ||
| assert data["success"] is True | ||
| ddl_mocks.execute.assert_called_once() | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_parse_failure_blocks_query(self, ddl_mocks, mcp_server): | ||
| """When SQL parsing fails, the query is blocked (fail-closed).""" | ||
| import sys | ||
|
|
||
| execute_sql_mod = sys.modules["superset.mcp_service.sql_lab.tool.execute_sql"] | ||
| with patch.object( | ||
| execute_sql_mod, | ||
| "SQLScript", | ||
| side_effect=Exception("parse error"), | ||
| ): | ||
| async with Client(mcp_server) as client: | ||
| result = await client.call_tool( | ||
| "execute_sql", | ||
| {"request": {"database_id": 1, "sql": "DROP TABLE birth_names"}}, | ||
| ) | ||
| data = result.structured_content | ||
| assert data["success"] is False | ||
| assert "could not be parsed" in data["error"] | ||
| ddl_mocks.execute.assert_not_called() | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_drop_table_blocked_mysql(self, ddl_mocks, mcp_server): | ||
| """DROP TABLE is blocked for non-PostgreSQL dialects too.""" | ||
| ddl_mocks.db_engine_spec.engine = "mysql" | ||
|
|
||
| async with Client(mcp_server) as client: | ||
| result = await client.call_tool( | ||
| "execute_sql", | ||
| {"request": {"database_id": 1, "sql": "DROP TABLE users"}}, | ||
| ) | ||
| data = result.structured_content | ||
| assert data["success"] is False | ||
| assert "Destructive DDL" in data["error"] | ||
| ddl_mocks.execute.assert_not_called() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if do we need to be more specific about the error here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried narrowing to SqlglotError but the disallowed-sql-import pylint rule blocks sqlglot imports outside superset/sql/.