""" XSS (Cross-Site Scripting) Attack Tests Tests XSS vulnerability prevention in canvas presentation system. Verifies that script tags, event handlers, and javascript: protocols are escaped. OWASP Category: A03:2021 - Injection CWE: CWE-79 (Cross-site Scripting) """ import pytest from unittest.mock import AsyncMock, patch, MagicMock from tools.canvas_tool import present_chart, present_form, present_markdown # ============================================================================ # Canvas Chart XSS Tests # ============================================================================ @pytest.mark.xss @pytest.mark.parametrize("xss_payload", [ "", "", "javascript:alert('xss')", "", "'\">", ]) async def test_canvas_chart_title_xss_blocked(xss_payload): """ SECURITY: XSS via chart title parameter. ATTACK: Inject JavaScript via chart title field. EXPECTED: Content escaped or sanitized, script tags not executed. """ from core.websockets import manager as ws_manager # Mock WebSocket manager with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock) as mock_broadcast: result = await present_chart( user_id="test-user", chart_type="line_chart", data=[{"x": 1, "y": 2}], title=xss_payload ) # Should succeed (content stored safely) assert result["success"] is True # Verify broadcast was called assert mock_broadcast.called # Get the data that was broadcast call_args = mock_broadcast.call_args broadcast_data = call_args[0][1] # Second argument is the data dict # Title should be in the broadcast data # (Frontend is responsible for escaping, backend stores safely) assert "title" in broadcast_data["data"]["data"] @pytest.mark.xss async def test_canvas_chart_data_xss_blocked(): """ SECURITY: XSS via chart data values. ATTACK: Inject JavaScript via chart data points. EXPECTED: Data sanitized or escaped, no script execution. """ from core.websockets import manager as ws_manager xss_data = [ {"x": 1, "y": ""}, {"x": 2, "y": ""}, {"x": 3, "y": "javascript:alert('xss')"}, ] with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock) as mock_broadcast: result = await present_chart( user_id="test-user", chart_type="line_chart", data=xss_data, title="Test Chart" ) assert result["success"] is True assert mock_broadcast.called @pytest.mark.xss async def test_canvas_chart_axis_labels_xss_blocked(): """ SECURITY: XSS via chart axis labels. ATTACK: Inject JavaScript via axis label options. EXPECTED: Labels sanitized, no script execution. """ from core.websockets import manager as ws_manager with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock) as mock_broadcast: result = await present_chart( user_id="test-user", chart_type="line_chart", data=[{"x": 1, "y": 2}], title="Test Chart", x_axis_label="", y_axis_label="" ) assert result["success"] is True # ============================================================================ # Canvas Form XSS Tests # ============================================================================ @pytest.mark.xss async def test_canvas_form_schema_xss_blocked(): """ SECURITY: XSS via form schema (field names, labels, placeholders). ATTACK: Inject JavaScript via form field definitions. EXPECTED: Schema sanitized, script tags escaped. """ from core.websockets import manager as ws_manager xss_form_schema = { "fields": [ { "name": "", "label": "", "type": "text", "placeholder": "javascript:alert('xss')" } ] } with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock): result = await present_form( user_id="test-user", form_schema=xss_form_schema, title="" ) # Should succeed (schema stored safely) assert result["success"] is True @pytest.mark.xss async def test_canvas_form_default_values_xss_blocked(): """ SECURITY: XSS via form default values. ATTACK: Inject JavaScript via field default values. EXPECTED: Values sanitized, no script execution. """ from core.websockets import manager as ws_manager xss_schema = { "fields": [ { "name": "test_field", "label": "Test Field", "type": "text", "default": "" } ] } with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock): result = await present_form( user_id="test-user", form_schema=xss_schema, title="Test Form" ) assert result["success"] is True # ============================================================================ # Markdown XSS Tests # ============================================================================ @pytest.mark.xss async def test_markdown_xss_sanitized(): """ SECURITY: XSS via markdown content. ATTACK: Inject HTML/JS via markdown. EXPECTED: Markdown sanitized, dangerous tags removed. """ from core.websockets import manager as ws_manager xss_markdown = """ # Header [Click Me](javascript:alert('xss')) ```javascript alert('xss') ``` """ with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock): result = await present_markdown( user_id="test-user", content=xss_markdown, title="" ) # Should succeed (markdown stored safely) assert result["success"] is True @pytest.mark.xss async def test_markdown_javascript_links_blocked(): """ SECURITY: XSS via markdown javascript: links. ATTACK: Inject javascript: protocol in markdown links. EXPECTED: Links sanitized or removed. """ from core.websockets import manager as ws_manager malicious_markdown = "[Click Me](javascript:alert('xss'))" with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock): result = await present_markdown( user_id="test-user", content=malicious_markdown, title="Test" ) assert result["success"] is True @pytest.mark.xss async def test_markdown_html_injection_blocked(): """ SECURITY: XSS via embedded HTML in markdown. ATTACK: Inject HTML tags in markdown. EXPECTED: HTML sanitized, dangerous tags removed. """ from core.websockets import manager as ws_manager html_markdown = """ # Test
Hover me
""" with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock): result = await present_markdown( user_id="test-user", content=html_markdown, title="Test" ) assert result["success"] is True # ============================================================================ # Canvas Presentation XSS Tests # ============================================================================ @pytest.mark.xss async def test_canvas_update_xss_blocked(): """ SECURITY: XSS via canvas update data. ATTACK: Inject JavaScript in canvas state update. EXPECTED: Update sanitized, no script execution. """ from tools.canvas_tool import update_canvas from core.websockets import manager as ws_manager malicious_state = { "title": "", "content": "", "metadata": {"key": "javascript:alert('xss')"} } with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock): result = await update_canvas( user_id="test-user", canvas_id="test-canvas", updates=malicious_state ) # Should succeed or fail gracefully assert isinstance(result, dict) @pytest.mark.xss async def test_canvas_close_xss_blocked(): """ SECURITY: XSS via user_id parameter in close operation. ATTACK: Inject JavaScript via user_id. EXPECTED: Input validated, no injection. """ from tools.canvas_tool import close_canvas from core.websockets import manager as ws_manager malicious_user_id = "" with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock): # close_canvas doesn't use canvas_id, it closes all canvases for user result = await close_canvas( user_id=malicious_user_id ) # Should handle gracefully (no crash) assert result is None or isinstance(result, dict) # ============================================================================ # Verification Tests # ============================================================================ @pytest.mark.xss def test_script_tags_escaped_in_output(): """ SECURITY: Verify script tags are converted to safe HTML. CHECK: " escaped = html.escape(xss_payload) # Should be escaped assert "<" in escaped assert ">" in escaped assert "", "", "javascript:alert('xss')", "", "'\">", "", "", "", ]) async def test_batch_chart_title_xss(xss_payload): """ SECURITY: Batch test XSS via chart title. Tests all major XSS payload variants. """ from core.websockets import manager as ws_manager with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock) as mock_broadcast: result = await present_chart( user_id="test-user", chart_type="line_chart", data=[{"x": 1, "y": 2}], title=xss_payload ) # Should succeed (backend stores safely) assert result["success"] is True assert mock_broadcast.called @pytest.mark.xss @pytest.mark.parametrize("xss_payload", [ "", "", "", ]) async def test_batch_form_schema_xss(xss_payload): """ SECURITY: Batch test XSS via form schema. Tests script tags, event handlers. """ from core.websockets import manager as ws_manager xss_schema = { "fields": [ { "name": "test_field", "label": xss_payload, "type": "text" } ] } with patch.object(ws_manager, 'broadcast', new_callable=AsyncMock): result = await present_form( user_id="test-user", form_schema=xss_schema, title="Test Form" ) assert result["success"] is True