From 930ec9eb80ed108399905e3fe54937c4fe9843a5 Mon Sep 17 00:00:00 2001 From: AlexanderWhitestone <8633216+AlexanderWhitestone@users.noreply.github.com> Date: Thu, 26 Feb 2026 02:07:54 -0500 Subject: [PATCH] Security: Fix XSS vulnerabilities in dashboard templates and improve mobile test UI safety --- .../20260226_070455_add_docstring.md | 19 +++++ .../20260226_070455_break_it.md | 31 +++++++++ .../20260226_070455_do_something_vague.md | 12 ++++ .../20260226_070455_fix_foo.md | 31 +++++++++ ...fix_foo_important_correction_from_previ.md | 34 +++++++++ src/dashboard/templates/mobile_test.html | 44 +++++++++--- .../templates/partials/agent_chat_msg.html | 6 +- .../templates/partials/agent_panel.html | 10 +-- .../partials/swarm_agents_sidebar.html | 4 +- tests/test_security_fixes_xss.py | 69 +++++++++++++++++++ 10 files changed, 241 insertions(+), 19 deletions(-) create mode 100644 data/self_modify_reports/20260226_070455_add_docstring.md create mode 100644 data/self_modify_reports/20260226_070455_break_it.md create mode 100644 data/self_modify_reports/20260226_070455_do_something_vague.md create mode 100644 data/self_modify_reports/20260226_070455_fix_foo.md create mode 100644 data/self_modify_reports/20260226_070455_fix_foo_important_correction_from_previ.md create mode 100644 tests/test_security_fixes_xss.py diff --git a/data/self_modify_reports/20260226_070455_add_docstring.md b/data/self_modify_reports/20260226_070455_add_docstring.md new file mode 100644 index 00000000..3eee61a7 --- /dev/null +++ b/data/self_modify_reports/20260226_070455_add_docstring.md @@ -0,0 +1,19 @@ +# Self-Modify Report: 20260226_070455 + +**Instruction:** Add docstring +**Target files:** src/foo.py +**Dry run:** True +**Backend:** ollama +**Branch:** N/A +**Result:** SUCCESS +**Error:** none +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- dry_run + +### LLM Response +``` +llm raw +``` diff --git a/data/self_modify_reports/20260226_070455_break_it.md b/data/self_modify_reports/20260226_070455_break_it.md new file mode 100644 index 00000000..be08013c --- /dev/null +++ b/data/self_modify_reports/20260226_070455_break_it.md @@ -0,0 +1,31 @@ +# Self-Modify Report: 20260226_070455 + +**Instruction:** Break it +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** Tests failed after 1 attempt(s). +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 1 + +``` + +### Test Result: FAILED +``` +1 failed +``` diff --git a/data/self_modify_reports/20260226_070455_do_something_vague.md b/data/self_modify_reports/20260226_070455_do_something_vague.md new file mode 100644 index 00000000..dce18a56 --- /dev/null +++ b/data/self_modify_reports/20260226_070455_do_something_vague.md @@ -0,0 +1,12 @@ +# Self-Modify Report: 20260226_070455 + +**Instruction:** do something vague +**Target files:** (auto-detected) +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** No target files identified. Specify target_files or use more specific language. +**Commit:** none +**Attempts:** 0 +**Autonomous cycles:** 0 diff --git a/data/self_modify_reports/20260226_070455_fix_foo.md b/data/self_modify_reports/20260226_070455_fix_foo.md new file mode 100644 index 00000000..9978258e --- /dev/null +++ b/data/self_modify_reports/20260226_070455_fix_foo.md @@ -0,0 +1,31 @@ +# Self-Modify Report: 20260226_070455 + +**Instruction:** Fix foo +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** Tests failed after 1 attempt(s). +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 2 + +``` + +### Test Result: FAILED +``` +FAILED +``` diff --git a/data/self_modify_reports/20260226_070455_fix_foo_important_correction_from_previ.md b/data/self_modify_reports/20260226_070455_fix_foo_important_correction_from_previ.md new file mode 100644 index 00000000..5cd91d3e --- /dev/null +++ b/data/self_modify_reports/20260226_070455_fix_foo_important_correction_from_previ.md @@ -0,0 +1,34 @@ +# Self-Modify Report: 20260226_070455 + +**Instruction:** Fix foo + +IMPORTANT CORRECTION from previous failure: +Fix: do X instead of Y +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** SUCCESS +**Error:** none +**Commit:** abc123 +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 2 + +``` + +### Test Result: PASSED +``` +PASSED +``` diff --git a/src/dashboard/templates/mobile_test.html b/src/dashboard/templates/mobile_test.html index e0662ae7..c6d31cca 100644 --- a/src/dashboard/templates/mobile_test.html +++ b/src/dashboard/templates/mobile_test.html @@ -343,15 +343,41 @@ const pct = TOTAL ? Math.round((passed / TOTAL) * 100) : 0; const color = failed > 0 ? "var(--red)" : "var(--green)"; - summaryBody.innerHTML = ` -
${passed} / ${TOTAL}
-
${pct}% pass rate
-
-
PASSED${passed}
-
FAILED${failed}
-
SKIPPED${skipped}
-
- `; + // Safely build summary UI using DOM API to avoid XSS from potentially untrusted variables + summaryBody.innerHTML = ''; + + const scoreDiv = document.createElement('div'); + scoreDiv.className = 'mt-summary-score'; + scoreDiv.style.color = color; + scoreDiv.textContent = passed + ' / ' + TOTAL; + summaryBody.appendChild(scoreDiv); + + const pctDiv = document.createElement('div'); + pctDiv.className = 'mt-summary-pct'; + pctDiv.textContent = pct + '% pass rate'; + summaryBody.appendChild(pctDiv); + + const statsContainer = document.createElement('div'); + statsContainer.style.marginTop = '16px'; + + const createRow = (label, value, colorVar) => { + const row = document.createElement('div'); + row.className = 'mt-summary-row'; + const labelSpan = document.createElement('span'); + labelSpan.textContent = label; + const valSpan = document.createElement('span'); + valSpan.style.color = 'var(--' + colorVar + ')'; + valSpan.style.fontWeight = '700'; + valSpan.textContent = value; + row.appendChild(labelSpan); + row.appendChild(valSpan); + return row; + }; + + statsContainer.appendChild(createRow('PASSED', passed, 'green')); + statsContainer.appendChild(createRow('FAILED', failed, 'red')); + statsContainer.appendChild(createRow('SKIPPED', skipped, 'amber')); + summaryBody.appendChild(statsContainer); const statusMsg = document.createElement('p'); statusMsg.style.marginTop = '12px'; diff --git a/src/dashboard/templates/partials/agent_chat_msg.html b/src/dashboard/templates/partials/agent_chat_msg.html index 9d05a120..8c6a222f 100644 --- a/src/dashboard/templates/partials/agent_chat_msg.html +++ b/src/dashboard/templates/partials/agent_chat_msg.html @@ -1,18 +1,18 @@
YOU // {{ timestamp }}
-
{{ message }}
+
{{ message | e }}
{% if response %}
{{ agent.name | upper }} // {{ timestamp }}
-
{{ response }}
+
{{ response | e }}
{% elif error %}
SYSTEM // {{ timestamp }}
-
{{ error }}
+
{{ error | e }}
{% elif task_id %} diff --git a/src/dashboard/templates/partials/agent_panel.html b/src/dashboard/templates/partials/agent_panel.html index 0451d301..d37d4be1 100644 --- a/src/dashboard/templates/partials/agent_panel.html +++ b/src/dashboard/templates/partials/agent_panel.html @@ -7,9 +7,9 @@
- // {{ agent.name | upper }} + // {{ agent.name | upper | e }} - {{ agent.capabilities or "no capabilities listed" }} + {{ agent.capabilities | e or "no capabilities listed" }}
@@ -57,7 +57,7 @@ - {{ agent.name | upper }} + {{ agent.name | upper | e }}
@@ -21,7 +21,7 @@ {{ agent.status }}
{% if agent.capabilities %} CAPS - {{ agent.capabilities }}
+ {{ agent.capabilities | e }}
{% endif %} SEEN {{ agent.last_seen[:19].replace("T"," ") if agent.last_seen else "—" }} diff --git a/tests/test_security_fixes_xss.py b/tests/test_security_fixes_xss.py new file mode 100644 index 00000000..ba074484 --- /dev/null +++ b/tests/test_security_fixes_xss.py @@ -0,0 +1,69 @@ +import pytest +from fastapi.templating import Jinja2Templates + +def test_agent_chat_msg_xss_prevention(): + """Verify XSS prevention in agent_chat_msg.html.""" + templates = Jinja2Templates(directory="src/dashboard/templates") + payload = "" + class MockAgent: + def __init__(self): + self.name = "TestAgent" + self.id = "test-agent" + + response = templates.get_template("partials/agent_chat_msg.html").render({ + "message": payload, + "response": payload, + "error": payload, + "agent": MockAgent(), + "timestamp": "12:00:00" + }) + + # Check that payload is escaped + assert "<script>alert('xss')</script>" in response + assert payload not in response + +def test_agent_panel_xss_prevention(): + """Verify XSS prevention in agent_panel.html.""" + templates = Jinja2Templates(directory="src/dashboard/templates") + payload = "" + class MockAgent: + def __init__(self): + self.name = payload + self.id = "test-agent" + self.status = "idle" + self.capabilities = payload + + class MockTask: + def __init__(self): + self.id = "task-1" + self.status = type('obj', (object,), {'value': 'completed'}) + self.created_at = "2026-02-26T12:00:00" + self.description = payload + self.result = payload + + response = templates.get_template("partials/agent_panel.html").render({ + "agent": MockAgent(), + "tasks": [MockTask()] + }) + + assert "<script>alert('xss')</script>" in response + assert payload not in response + +def test_swarm_sidebar_xss_prevention(): + """Verify XSS prevention in swarm_agents_sidebar.html.""" + templates = Jinja2Templates(directory="src/dashboard/templates") + payload = "" + class MockAgent: + def __init__(self): + self.name = payload + self.id = "test-agent" + self.status = "idle" + self.capabilities = payload + self.last_seen = "2026-02-26T12:00:00" + + response = templates.get_template("partials/swarm_agents_sidebar.html").render({ + "agents": [MockAgent()] + }) + + assert "<script>alert('xss')</script>" in response + assert payload not in response