| """ |
| Error Message Security Tests |
| |
| Tests that error messages don't expose sensitive internal information. |
| """ |
|
|
| import pytest |
|
|
| from src.tools.create_task import create_task_internal |
| from src.tools.get_task import get_task_internal |
| from src.tools.update_task import update_task_internal |
| from tests.utils.task_helpers import create_test_task |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_error_messages_do_not_expose_database_schema(mock_mcp_context): |
| """ |
| Test: Error messages do not expose database schema |
| |
| Verifies that error messages don't reveal table names, column names, or schema details. |
| """ |
| |
| result1 = await create_task_internal(ctx=mock_mcp_context, title="A" * 201) |
| result2 = await get_task_internal(ctx=mock_mcp_context, task_id=99999) |
| result3 = await update_task_internal(ctx=mock_mcp_context, task_id=99999, title="Test") |
|
|
| |
| for result in [result1, result2, result3]: |
| if result["status"] == "error": |
| error_msg = result["error"].lower() |
|
|
| |
| assert "table" not in error_msg |
| assert "column" not in error_msg |
| assert "schema" not in error_msg |
| assert "constraint" not in error_msg |
| assert "foreign key" not in error_msg |
| assert "primary key" not in error_msg |
| assert "index" not in error_msg |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_error_messages_do_not_expose_internal_paths(mock_mcp_context): |
| """ |
| Test: Error messages do not expose internal paths |
| |
| Verifies that error messages don't reveal file system paths or internal structure. |
| """ |
| |
| result1 = await create_task_internal(ctx=mock_mcp_context, title="") |
| result2 = await get_task_internal(ctx=mock_mcp_context, task_id=-1) |
|
|
| |
| for result in [result1, result2]: |
| if result["status"] == "error": |
| error_msg = result["error"] |
|
|
| |
| assert "/" not in error_msg or "not found" in error_msg.lower() |
| assert "\\" not in error_msg |
| assert "src/" not in error_msg |
| assert "backend/" not in error_msg |
| assert ".py" not in error_msg |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_error_messages_do_not_expose_stack_traces(mock_mcp_context): |
| """ |
| Test: Error messages do not expose stack traces |
| |
| Verifies that error messages don't include stack traces or exception details. |
| """ |
| |
| result1 = await create_task_internal(ctx=mock_mcp_context, title=None) |
| result2 = await update_task_internal(ctx=mock_mcp_context, task_id=99999, title="Test") |
|
|
| |
| for result in [result1, result2]: |
| if result["status"] == "error": |
| error_msg = result["error"].lower() |
|
|
| |
| assert "traceback" not in error_msg |
| assert "file \"" not in error_msg |
| assert "line " not in error_msg |
| assert "exception" not in error_msg |
| assert "error:" not in error_msg or error_msg.count("error:") == 1 |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_error_messages_are_user_friendly(mock_mcp_context): |
| """ |
| Test: Error messages are user-friendly |
| |
| Verifies that error messages provide helpful information without technical details. |
| """ |
| |
| result1 = await create_task_internal(ctx=mock_mcp_context, title="") |
| assert result1["status"] == "error" |
| assert len(result1["error"]) > 0 |
| assert "title" in result1["error"].lower() or "required" in result1["error"].lower() |
|
|
| |
| result2 = await get_task_internal(ctx=mock_mcp_context, task_id=99999) |
| assert result2["status"] == "error" |
| assert "not found" in result2["error"].lower() |
|
|
|
|
| @pytest.mark.security |
| @pytest.mark.asyncio |
| async def test_error_messages_consistent_across_tools(mock_mcp_context, mock_mcp_context_user2, test_session): |
| """ |
| Test: Error messages consistent across tools |
| |
| Verifies that similar errors produce consistent messages across different tools. |
| """ |
| |
| user2_task = create_test_task(test_session, mock_mcp_context_user2.user_id, title="User 2 Task") |
|
|
| |
| result1 = await get_task_internal(ctx=mock_mcp_context, task_id=user2_task.id) |
| result2 = await update_task_internal(ctx=mock_mcp_context, task_id=user2_task.id, title="Test") |
|
|
| |
| assert result1["status"] == "error" |
| assert result2["status"] == "error" |
| assert "not found" in result1["error"].lower() |
| assert "not found" in result2["error"].lower() |
|
|