| """ |
| Security Tests for create_task Tool |
| |
| Validates security aspects of create_task tool: |
| - User_id scoping enforcement |
| - Error message sanitization |
| """ |
|
|
| import pytest |
|
|
| from src.tools.create_task import create_task_internal |
| from tests.utils.task_helpers import get_task_by_id, count_tasks |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_create_task_enforces_user_id_scoping(mock_mcp_context, mock_mcp_context_user2, test_session): |
| """ |
| Test: create_task enforces user_id scoping |
| |
| Verifies that tasks are created with user_id from MCPContext, |
| ensuring proper data isolation. |
| """ |
| |
| result1 = await create_task_internal( |
| ctx=mock_mcp_context, |
| title="User 1 Task" |
| ) |
| assert result1["status"] == "success" |
| task1_id = result1["task"]["id"] |
|
|
| |
| result2 = await create_task_internal( |
| ctx=mock_mcp_context_user2, |
| title="User 2 Task" |
| ) |
| assert result2["status"] == "success" |
| task2_id = result2["task"]["id"] |
|
|
| |
| task1 = get_task_by_id(test_session, task1_id) |
| task2 = get_task_by_id(test_session, task2_id) |
|
|
| assert task1.user_id == mock_mcp_context.user_id |
| assert task2.user_id == mock_mcp_context_user2.user_id |
| assert task1.user_id != task2.user_id |
|
|
| |
| user1_count = count_tasks(test_session, mock_mcp_context.user_id) |
| user2_count = count_tasks(test_session, mock_mcp_context_user2.user_id) |
|
|
| assert user1_count == 1 |
| assert user2_count == 1 |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_create_task_sanitizes_error_messages(mock_mcp_context): |
| """ |
| Test: create_task sanitizes error messages |
| |
| Verifies that error messages don't expose internal system details. |
| """ |
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title="" |
| ) |
|
|
| assert result["status"] == "error" |
| error_msg = result["error"] |
|
|
| |
| assert "database" not in error_msg.lower() |
| assert "sql" not in error_msg.lower() |
| assert "table" not in error_msg.lower() |
| assert "column" not in error_msg.lower() |
| assert "exception" not in error_msg.lower() |
| assert "traceback" not in error_msg.lower() |
| assert "stack" not in error_msg.lower() |
|
|
| |
| assert len(error_msg) > 0 |
| assert error_msg[0].isupper() |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_create_task_handles_database_errors_safely(mock_mcp_context, monkeypatch): |
| """ |
| Test: create_task handles database errors safely |
| |
| Verifies that database errors are caught and sanitized. |
| """ |
| |
| def mock_get_session_error(*args, **kwargs): |
| raise Exception("Database connection failed") |
|
|
| |
| |
| |
|
|
| |
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title="Test task" |
| ) |
|
|
| |
| |
| assert result["status"] in ["success", "error"] |
|
|
| if result["status"] == "error": |
| |
| error_msg = result["error"] |
| assert "Database error" in error_msg or "error" in error_msg.lower() |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_create_task_prevents_xss_in_title(mock_mcp_context, test_session): |
| """ |
| Test: create_task prevents XSS in title |
| |
| Verifies that potentially malicious input is stored safely. |
| """ |
| |
| xss_title = "<script>alert('XSS')</script>" |
|
|
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title=xss_title |
| ) |
|
|
| assert result["status"] == "success" |
|
|
| |
| |
| task_id = result["task"]["id"] |
| task = get_task_by_id(test_session, task_id) |
|
|
| assert task.title == xss_title |
| |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_create_task_prevents_sql_injection_in_title(mock_mcp_context, test_session): |
| """ |
| Test: create_task prevents SQL injection in title |
| |
| Verifies that SQL injection attempts are safely handled by parameterized queries. |
| """ |
| |
| sql_injection_title = "'; DROP TABLE tasks; --" |
|
|
| result = await create_task_internal( |
| ctx=mock_mcp_context, |
| title=sql_injection_title |
| ) |
|
|
| assert result["status"] == "success" |
|
|
| |
| task_id = result["task"]["id"] |
| task = get_task_by_id(test_session, task_id) |
|
|
| assert task.title == sql_injection_title |
|
|
| |
| task_count = count_tasks(test_session, mock_mcp_context.user_id) |
| assert task_count == 1 |
|
|