[Security Audit] Stored XSS in stats dashboard - unescaped URL paths #3

Closed
opened 2026-06-29 04:03:16 -05:00 by akiba · 7 comments
Owner

Severity: HIGH

Description

The stats dashboard at /stats renders user-requested URL paths in HTML without any HTML entity encoding, creating a stored XSS vulnerability.

Affected Files

  • app.py line 309: p["path"] is interpolated directly into HTML via f-string
  • stats.py line 94: base_path is extracted from user-controlled request paths and stored

Reproduction

  1. Make a request to //anything
  2. Visit /stats — the script executes in the context of agento3.miscs.dev

Impact

An attacker can craft URLs that, when proxied through the mirror and viewed by an admin on the stats dashboard, execute arbitrary JavaScript. This can steal cookies, session tokens, or perform actions as the admin.

Fix

Escape HTML entities in path values before rendering. Use html.escape(p["path"]) in app.py line 309, or use a proper template engine (Jinja2) with auto-escaping.

## Severity: HIGH ### Description The stats dashboard at /stats renders user-requested URL paths in HTML without any HTML entity encoding, creating a stored XSS vulnerability. ### Affected Files - app.py line 309: p["path"] is interpolated directly into HTML via f-string - stats.py line 94: base_path is extracted from user-controlled request paths and stored ### Reproduction 1. Make a request to /<img src=x onerror=alert(1)>/anything 2. Visit /stats — the script executes in the context of agento3.miscs.dev ### Impact An attacker can craft URLs that, when proxied through the mirror and viewed by an admin on the stats dashboard, execute arbitrary JavaScript. This can steal cookies, session tokens, or perform actions as the admin. ### Fix Escape HTML entities in path values before rendering. Use html.escape(p["path"]) in app.py line 309, or use a proper template engine (Jinja2) with auto-escaping.
akiba added the bugpriority:high labels 2026-06-29 04:03:16 -05:00
akiba self-assigned this 2026-06-29 05:01:19 -05:00
Author
Owner

Auto-Triage Report for Issue #3

Status: Open
Existing Labels: bug, priority:high (correct)
Assignee: @akiba (newly assigned)

Summary: [Security Audit] Stored XSS in stats dashboard — unescaped URL paths rendered in HTML at /stats. Severity: HIGH.

Assessment: Legitimate security finding. The issue is well-documented with reproduction steps and a clear fix (use html.escape() or a template engine with auto-escaping). Priority:high label is appropriate given the XSS vector.

Next Steps:

  1. Apply fix: escape HTML entities in path values before rendering (e.g., html.escape(p["path"]) in app.py line 309)
  2. Add CSP header as defense-in-depth (see Issue #5)
  3. Verify fix by reproducing the attack vector against the updated endpoint
## Auto-Triage Report for Issue #3 **Status**: Open **Existing Labels**: bug, priority:high (correct) **Assignee**: @akiba (newly assigned) **Summary**: [Security Audit] Stored XSS in stats dashboard — unescaped URL paths rendered in HTML at /stats. Severity: HIGH. **Assessment**: Legitimate security finding. The issue is well-documented with reproduction steps and a clear fix (use html.escape() or a template engine with auto-escaping). Priority:high label is appropriate given the XSS vector. **Next Steps**: 1. Apply fix: escape HTML entities in path values before rendering (e.g., html.escape(p["path"]) in app.py line 309) 2. Add CSP header as defense-in-depth (see Issue #5) 3. Verify fix by reproducing the attack vector against the updated endpoint
Author
Owner

Triage Summary

Status: 🟢 Confirmed — Stored XSS in stats dashboard
Severity: HIGH
Priority: 🔴 Critical — fix immediately

Assessment

This is a legitimate stored XSS vulnerability. The p["path"] value on app.py:309 is interpolated directly into HTML via f-string without escaping. An attacker can craft a URL like /<img src=x onerror=alert(1)>/anything that, when proxied through the mirror and viewed on the stats dashboard by an admin, will execute arbitrary JavaScript in the admin's browser session.

  1. Immediate fix: Use html.escape(p["path"]) in app.py line 309
  2. Defense-in-depth: Also address issue #5 (CSP header) to block XSS even if another vector appears
  3. Consider migrating to a template engine (Jinja2) with auto-escaping
  4. Add unit tests for HTML escaping in path rendering

Triage Actions Taken

  • Labels: bug, priority:high (already set)
  • Assigned to: @akiba (already assigned)
  • Linked issue: #5 (CSP) is a related defense-in-depth measure
## Triage Summary **Status:** 🟢 Confirmed — Stored XSS in stats dashboard **Severity:** HIGH **Priority:** 🔴 Critical — fix immediately ### Assessment This is a legitimate stored XSS vulnerability. The `p["path"]` value on app.py:309 is interpolated directly into HTML via f-string without escaping. An attacker can craft a URL like `/<img src=x onerror=alert(1)>/anything` that, when proxied through the mirror and viewed on the stats dashboard by an admin, will execute arbitrary JavaScript in the admin's browser session. ### Recommended Next Steps 1. **Immediate fix:** Use `html.escape(p["path"])` in app.py line 309 2. **Defense-in-depth:** Also address issue #5 (CSP header) to block XSS even if another vector appears 3. Consider migrating to a template engine (Jinja2) with auto-escaping 4. Add unit tests for HTML escaping in path rendering ### Triage Actions Taken - Labels: `bug`, `priority:high` (already set) ✅ - Assigned to: @akiba (already assigned) ✅ - Linked issue: #5 (CSP) is a related defense-in-depth measure
Author
Owner

🤖 Triage Summary

Classification: Bug / Security (HIGH severity)
Labels: bug, priority:high
Assignee: akiba

Assessment: This is a legitimate stored XSS vulnerability. The stats dashboard renders unescaped URL paths directly into HTML via f-string interpolation, allowing arbitrary JavaScript execution from crafted request URLs.

Recommended Next Steps:

  1. Immediate: Apply html.escape() to path values in app.py (line 309) and stats.py (line 94) as a hotfix
  2. Short-term: Consider migrating the stats dashboard to a template engine with auto-escaping (Jinja2)
  3. Related: Issue #5 (Missing CSP header) would provide defense-in-depth — prioritize both together

Priority rationale: HIGH — an attacker can steal admin cookies/session tokens via crafted URLs viewed on the stats page.

## 🤖 Triage Summary **Classification:** Bug / Security (HIGH severity) **Labels:** bug, priority:high ✅ **Assignee:** akiba ✅ **Assessment:** This is a legitimate stored XSS vulnerability. The stats dashboard renders unescaped URL paths directly into HTML via f-string interpolation, allowing arbitrary JavaScript execution from crafted request URLs. **Recommended Next Steps:** 1. **Immediate:** Apply html.escape() to path values in app.py (line 309) and stats.py (line 94) as a hotfix 2. **Short-term:** Consider migrating the stats dashboard to a template engine with auto-escaping (Jinja2) 3. **Related:** Issue #5 (Missing CSP header) would provide defense-in-depth — prioritize both together **Priority rationale:** HIGH — an attacker can steal admin cookies/session tokens via crafted URLs viewed on the stats page.
Author
Owner

🔄 Follow-up Triage (2026-06-30)

Status: Still open. Labels (bug, priority:high) and assignee (akiba) remain correct — this is the highest-priority issue.

Action needed: Apply html.escape() to path values in app.py:309 and stats.py:94. This is an active stored XSS vector. No new activity since last triage.

## 🔄 Follow-up Triage (2026-06-30) **Status:** Still open. Labels (`bug`, `priority:high`) and assignee (akiba) remain correct — this is the highest-priority issue. **Action needed:** Apply `html.escape()` to path values in app.py:309 and stats.py:94. This is an active stored XSS vector. No new activity since last triage.
Author
Owner

🤖 Triage Summary

Status: Confirmed — legitimate HIGH severity security issue.
Labels: bug, priority:high
Assignee: akiba

Next Steps

  1. Apply the fix described in the issue: replace p["path"] f-string interpolation with html.escape(p["path"]) in app.py line 309
  2. Sanitize base_path extraction in stats.py line 94
  3. Once fixed, consider adding a Content-Security-Policy header (see issue #5) as defense-in-depth

Severity Assessment

This is a critical security finding. Stored XSS can compromise admin sessions on the stats dashboard. Recommend prioritizing this fix before deploying other changes.

## 🤖 Triage Summary **Status:** Confirmed — legitimate HIGH severity security issue. **Labels:** bug, priority:high ✅ **Assignee:** akiba ✅ ### Next Steps 1. Apply the fix described in the issue: replace `p["path"]` f-string interpolation with `html.escape(p["path"])` in `app.py` line 309 2. Sanitize `base_path` extraction in `stats.py` line 94 3. Once fixed, consider adding a Content-Security-Policy header (see issue #5) as defense-in-depth ### Severity Assessment This is a **critical** security finding. Stored XSS can compromise admin sessions on the stats dashboard. Recommend prioritizing this fix before deploying other changes.
Author
Owner

Triage Assessment: HIGH severity

This is a critical stored XSS vulnerability. User-controlled URL paths are rendered unescaped in the /stats dashboard, allowing arbitrary script execution.

Labels: bug, priority:high
Assignee: akiba

Recommended next steps:

  • HTML-encode p["path"] in app.py line 309 before interpolation
  • Sanitize base_path in stats.py line 94 at storage time
  • Add a CSP header (see issue #5) as defense-in-depth
  • Write a regression test with a malicious path like /<img src=x onerror=alert(1)>/anything
  • Priority: HIGH — should be fixed urgently before deployment to production
**Triage Assessment: HIGH severity** This is a critical stored XSS vulnerability. User-controlled URL paths are rendered unescaped in the /stats dashboard, allowing arbitrary script execution. **Labels:** bug, priority:high ✅ **Assignee:** akiba ✅ **Recommended next steps:** - HTML-encode `p["path"]` in app.py line 309 before interpolation - Sanitize `base_path` in stats.py line 94 at storage time - Add a CSP header (see issue #5) as defense-in-depth - Write a regression test with a malicious path like `/<img src=x onerror=alert(1)>/anything` - **Priority: HIGH — should be fixed urgently before deployment to production**
Author
Owner

FIXED — Commit 122c408

Fix: Added import html and replaced p["path"] with html.escape(p["path"]) in the stats dashboard HTML template (app.py:390).

Deployed via start.sh — workers restarted, Caddy reloaded.

Verification: Path values in /stats are now HTML-escaped, so <img src=x onerror=alert(1)> renders as literal text instead of executing JS.

**FIXED** — Commit 122c408 Fix: Added `import html` and replaced `p["path"]` with `html.escape(p["path"])` in the stats dashboard HTML template (app.py:390). Deployed via start.sh — workers restarted, Caddy reloaded. Verification: Path values in /stats are now HTML-escaped, so `<img src=x onerror=alert(1)>` renders as literal text instead of executing JS.
akiba closed this issue 2026-06-30 09:04:43 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: akiba/agento3#3