File size: 4,961 Bytes
310260a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | """
Input Validation Security Tests
Tests that all tools properly validate and sanitize inputs.
"""
import pytest
from src.tools.create_task import create_task_internal
from src.tools.update_task import update_task_internal
from src.tools.mark_complete import mark_complete_internal
from src.tools.delete_task import delete_task_internal
from src.tools.get_task import get_task_internal
@pytest.mark.security
@pytest.mark.asyncio
async def test_all_tools_validate_input_types_and_ranges(mock_mcp_context):
"""
Test: All tools validate input types and ranges
Verifies that tools reject invalid input types and out-of-range values.
"""
# Test create_task with invalid title length
result1 = await create_task_internal(
ctx=mock_mcp_context,
title="A" * 201 # Exceeds 200 char limit
)
assert result1["status"] == "error"
# Test create_task with invalid description length
result2 = await create_task_internal(
ctx=mock_mcp_context,
title="Valid",
description="B" * 2001 # Exceeds 2000 char limit
)
assert result2["status"] == "error"
# Test update_task with invalid title length
result3 = await update_task_internal(
ctx=mock_mcp_context,
task_id=1,
title="C" * 201
)
assert result3["status"] == "error"
# Test with negative task_id
result4 = await get_task_internal(
ctx=mock_mcp_context,
task_id=-1
)
assert result4["status"] == "error"
@pytest.mark.security
@pytest.mark.asyncio
async def test_all_tools_reject_sql_injection_attempts(mock_mcp_context, test_session):
"""
Test: All tools reject SQL injection attempts
Verifies that tools properly sanitize inputs to prevent SQL injection.
"""
# SQL injection attempts in title
sql_injection_payloads = [
"'; DROP TABLE tasks; --",
"1' OR '1'='1",
"admin'--",
"' UNION SELECT * FROM users--"
]
for payload in sql_injection_payloads:
# Test create_task
result = await create_task_internal(
ctx=mock_mcp_context,
title=payload
)
# Should either succeed (treating as literal string) or fail validation
# But should NOT execute SQL injection
if result["status"] == "success":
# Verify the payload was stored as literal text
assert result["task"]["title"] == payload
# Verify database integrity - tasks table should still exist
from tests.utils.task_helpers import count_tasks
count = count_tasks(test_session, mock_mcp_context.user_id)
assert isinstance(count, int) # Table exists and query works
@pytest.mark.security
@pytest.mark.asyncio
async def test_all_tools_handle_malformed_json_gracefully(mock_mcp_context):
"""
Test: All tools handle malformed JSON gracefully
Verifies that tools handle invalid JSON inputs without crashing.
"""
# Test with None values
result1 = await create_task_internal(
ctx=mock_mcp_context,
title=None
)
assert result1["status"] == "error"
# Test with empty string
result2 = await create_task_internal(
ctx=mock_mcp_context,
title=""
)
assert result2["status"] == "error"
# Test update with no fields
result3 = await update_task_internal(
ctx=mock_mcp_context,
task_id=1
)
assert result3["status"] == "error"
@pytest.mark.security
@pytest.mark.asyncio
async def test_tools_sanitize_special_characters(mock_mcp_context, test_session):
"""
Test: Tools sanitize special characters
Verifies that tools handle special characters safely.
"""
special_chars = [
"<script>alert('xss')</script>",
"Test\x00null\x00byte",
"Test\r\nNewline",
"Test\tTab"
]
for chars in special_chars:
result = await create_task_internal(
ctx=mock_mcp_context,
title=chars
)
# Should succeed and store safely
if result["status"] == "success":
# Verify stored correctly
from tests.utils.task_helpers import get_task_by_id
task = get_task_by_id(test_session, result["task"]["id"])
assert task is not None
@pytest.mark.security
@pytest.mark.asyncio
async def test_tools_validate_task_id_format(mock_mcp_context):
"""
Test: Tools validate task_id format
Verifies that tools reject invalid task_id formats.
"""
# Test with zero
result1 = await get_task_internal(ctx=mock_mcp_context, task_id=0)
assert result1["status"] == "error"
# Test with negative
result2 = await mark_complete_internal(ctx=mock_mcp_context, task_id=-999)
assert result2["status"] == "error"
# Test with very large number
result3 = await delete_task_internal(ctx=mock_mcp_context, task_id=999999999)
assert result3["status"] == "error"
|