Skip to content

CRITICAL: Debug mode defaults to enabled - Reflected XSS plus full stack trace information disclosure #2022

Description

@namann5

Severity: Critical (CVSS ~8.1)

Summary

Debug mode is enabled by default (debug: bool = True), and when an unhandled exception occurs, the full Python traceback — including internal file paths, variable values, and any data captured in exception messages — is served as raw, unescaped HTML. This creates a Reflected XSS vulnerability and full stack trace information disclosure in production deployments.

Vulnerable Code

backend/secuscan/config.py:22 — Debug defaults to True:

debug: bool = True

backend/secuscan/main.py:277-289 — Unescaped traceback in HTML response:

@app.exception_handler(Exception)
async def custom_unhandled_exception_handler(request: Request, exc: Exception):
    logger.exception("Unhandled exception in request lifecycle")

    if settings.debug:
        import traceback
        html = f"<html><body><h1>500 Internal Server Error</h1><pre>{traceback.format_exc()}</pre></body></html>"
        response = HTMLResponse(html, status_code=500)
    else:
        response = PlainTextResponse("Internal Server Error", status_code=500)

Attack Vector

Reflected XSS

  1. Attacker crafts a request that causes an unhandled exception where the exception message contains attacker-controlled HTML/JS (e.g., via a plugin ID, target URL, or header value that propagates into an exception)
  2. The traceback.format_exc() output is embedded directly into HTML with no escaping
  3. If any variable in the traceback chain contains attacker-controlled content, arbitrary JavaScript executes in the victim's browser

Information Disclosure (independent of XSS)

Even without XSS exploitation, the stack trace exposes:

  • Full server filesystem paths (e.g., /app/secuscan/executor.py)
  • Python version and installed packages
  • Internal variable names and data structures
  • Potentially sensitive values captured in local variables at exception time

Additional Debug Mode Risks

  • main.py:332 — API docs URL exposed in root response when debug=True
  • main.py:171-173 — OpenAPI schema publicly accessible (/openapi.json, /docs, /redoc) when debug is enabled
  • routes.py:241-242 — Plugin metadata reloaded from disk per-request in debug mode — filesystem manipulation risk

Impact

  • Reflected XSS: Arbitrary JavaScript execution in victim's browser context
  • Full information disclosure: Internal paths, variable names, package versions leaked
  • Production risk: Default debug=True means production deployments are vulnerable unless explicitly configured
  • Attack surface expansion: Exposed OpenAPI docs reveal all endpoints for further attacks

Recommended Fix

  1. Set debug: bool = False as the default — debug mode should be opt-in via explicit environment variable
  2. HTML-escape traceback.format_exc() before embedding in HTML response:
    import html
    escaped = html.escape(traceback.format_exc())
    html_content = f"<html><body><h1>500 Internal Server Error</h1><pre>{escaped}</pre></body></html>"
  3. Use a structured error response instead of raw HTML in the exception handler, even in debug mode
  4. Gate OpenAPI/docs endpoints behind debug mode — disable /docs, /redoc, /openapi.json when debug=False

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:backendBackend API, database, or service workarea:securitySecurity-sensitive implementation or testslevel:critical80 pts difficulty label for critical or high-impact PRspriority:highHigh-priority issuetype:securitySecurity work category bonus label

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions