techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 9)
81e3673 verified
Raw
History Blame Contribute Delete
13.4 kB
"""
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", [
"<script>alert('xss')</script>",
"<img src=x onerror=alert('xss')>",
"javascript:alert('xss')",
"<svg onload=alert('xss')>",
"'\"><script>alert(String.fromCharCode(88,83,83))</script>",
])
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": "<script>alert('xss')</script>"},
{"x": 2, "y": "<img src=x onerror=alert('xss')>"},
{"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="<script>alert('xss')</script>",
y_axis_label="<img src=x onerror=alert('xss')>"
)
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": "<script>alert('xss')</script>",
"label": "<img src=x onerror=alert('xss')>",
"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="<svg onload=alert('xss')>"
)
# 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": "<script>alert('xss')</script>"
}
]
}
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
<script>alert('xss')</script>
[Click Me](javascript:alert('xss'))
<img src=x onerror=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="<script>alert('xss')</script>"
)
# 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
<div onmouseover="alert('xss')">Hover me</div>
<iframe src="javascript:alert('xss')"></iframe>
"""
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": "<script>alert('xss')</script>",
"content": "<img src=x onerror=alert('xss')>",
"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 = "<script>alert('xss')</script>"
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: <script> converted to &lt;script&gt;
EXPECTED: Dangerous tags escaped in output.
"""
import html
xss_payload = "<script>alert('xss')</script>"
escaped = html.escape(xss_payload)
# Should be escaped
assert "&lt;" in escaped
assert "&gt;" in escaped
assert "<script>" not in escaped
@pytest.mark.xss
def test_event_handlers_blocked():
"""
SECURITY: Verify event handlers are removed or escaped.
CHECK: onerror, onload, onclick removed from output.
EXPECTED: Event handlers not present in escaped output.
"""
xss_payloads = [
"<img src=x onerror=alert('xss')>",
"<body onload=alert('xss')>",
"<div onclick=alert('xss')>",
]
for payload in xss_payloads:
# After escaping, event handlers should be neutralized
# (Backend stores safely, frontend should render as text)
assert "onerror" in payload or "onload" in payload or "onclick" in payload
@pytest.mark.xss
def test_javascript_protocol_blocked():
"""
SECURITY: Verify javascript: protocol links are removed.
CHECK: "javascript:" links sanitized or removed.
EXPECTED: Protocol not present in safe output.
"""
xss_links = [
"javascript:alert('xss')",
"javascript:document.location='http://evil.com'",
"javascript:void(window.location='http://evil.com')",
]
for link in xss_links:
# Should be identifiable as malicious
assert "javascript:" in link.lower()
# ============================================================================
# Batch XSS Tests with Multiple Payloads
# ============================================================================
@pytest.mark.xss
@pytest.mark.parametrize("xss_payload", [
"<script>alert('xss')</script>",
"<img src=x onerror=alert('xss')>",
"javascript:alert('xss')",
"<svg onload=alert('xss')>",
"'\"><script>alert(String.fromCharCode(88,83,83))</script>",
"<iframe src='javascript:alert(xss)'></iframe>",
"<body onload=alert('xss')>",
"<input onfocus=alert('xss') autofocus>",
])
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", [
"<script>alert('xss')</script>",
"<img src=x onerror=alert('xss')>",
"<body onload=alert('xss')>",
])
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