| """ |
| Security Tests for mark_complete Tool |
| |
| Validates security aspects of mark_complete tool: |
| - Task ownership enforcement |
| - Cross-user access prevention |
| """ |
|
|
| import pytest |
|
|
| from src.tools.mark_complete import mark_complete_internal |
| from tests.utils.task_helpers import create_test_task, get_task_by_id |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_mark_complete_enforces_task_ownership(mock_mcp_context, mock_mcp_context_user2, test_session): |
| """ |
| Test: mark_complete enforces task ownership |
| |
| Verifies that users can only mark their own tasks as complete. |
| """ |
| |
| user1_task = create_test_task(test_session, mock_mcp_context.user_id, title="User 1 Task") |
| user2_task = create_test_task(test_session, mock_mcp_context_user2.user_id, title="User 2 Task") |
|
|
| |
| result1 = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=user1_task.id |
| ) |
| assert result1["status"] == "success" |
|
|
| |
| result2 = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=user2_task.id |
| ) |
| assert result2["status"] == "error" |
| assert "not found" in result2["error"].lower() |
|
|
| |
| unchanged_task = get_task_by_id(test_session, user2_task.id) |
| assert unchanged_task.completed is False |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_mark_complete_with_user1_context_cannot_modify_user2_task(mock_mcp_context, mock_mcp_context_user2, test_session): |
| """ |
| Test: mark_complete with user1 context cannot modify user2 task |
| |
| Verifies complete isolation - user 1 cannot modify user 2's tasks. |
| """ |
| |
| user2_task = create_test_task( |
| test_session, |
| mock_mcp_context_user2.user_id, |
| title="User 2 Private Task", |
| completed=False |
| ) |
|
|
| |
| result = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=user2_task.id |
| ) |
|
|
| |
| assert result["status"] == "error" |
| assert "not found" in result["error"].lower() |
|
|
| |
| unchanged_task = get_task_by_id(test_session, user2_task.id) |
| assert unchanged_task.completed is False |
| assert unchanged_task.title == "User 2 Private Task" |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_mark_complete_error_message_prevents_information_disclosure(mock_mcp_context, mock_mcp_context_user2, test_session): |
| """ |
| Test: mark_complete error message prevents information disclosure |
| |
| Verifies that error messages don't reveal whether a task exists for another user. |
| """ |
| |
| user2_task = create_test_task(test_session, mock_mcp_context_user2.user_id, title="Secret Task") |
|
|
| |
| result_unauthorized = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=user2_task.id |
| ) |
|
|
| |
| result_not_found = await mark_complete_internal( |
| ctx=mock_mcp_context, |
| task_id=99999 |
| ) |
|
|
| |
| assert result_unauthorized["status"] == "error" |
| assert result_not_found["status"] == "error" |
|
|
| |
| assert "not found" in result_unauthorized["error"].lower() |
| assert "not found" in result_not_found["error"].lower() |
|
|