GitHub Security Advisory: GHSA-c3gc-9pf2-84gg
Package: pyload-ng (PIP)
Severity: MODERATE (CVSS 5.3)
CWE: CWE-209: Generation of Error Message Containing Sensitive Information
Vulnerable versions: < 0.5.0b3.dev100
Patched in: 0.5.0b3.dev100

Summary: PyLoad vulnerable to unauthenticated traceback disclosure via global exception handler in WebUI

Details:
### Summary
`pyload-ng` WebUI returns full Python traceback details to clients on unhandled exceptions.

Because `/web/<path:filename>` is reachable without authentication and renders attacker-controlled template names, an unauthenticated user can reliably trigger a server exception (for example by requesting a non-existent template) and receive internal stack traces in the HTTP response.

### Details
The issue is caused by the combination of:

1. Unauthenticated template-render route:
- `src/pyload/webui/app/blueprints/app_blueprint.py:32-36`
  - `@bp.route("/web/<path:filename>", endpoint="web")`
  - `data = render_template(filename)` with user-controlled `filename`
  - no `@login_required(...)` on this route

2. Global exception handler exposes traceback to response:
- `src/pyload/webui/app/handlers.py:14-27`
  - `tb = traceback.format_exc()`
  - `messages.extend(tb.split('\n'))`
  - returned in rendered error page for all exceptions

3. Error page renders all `messages`:
- `src/pyload/webui/app/themes/modern/templates/base.html:217-219`
  - loops over `messages` and prints them in response HTML

So any unhandled exception can disclose internal implementation details (stack frames, source paths, exception metadata) to remote unauthenticated clients. 

This is a core behavior issue in default WebUI error handling

### PoC
```python
#!/usr/bin/env python3
from __future__ import annotations

import re
import shutil
import tempfile
import traceback
from pathlib import Path


ROOT = Path(__file__).resolve().parent / "pyload" / "src" / "pyload"


def read_text(rel: str) -> str:
    return (ROOT / rel).read_text(encoding="utf-8")


def route_has_no_login_required(app_blueprint: str) -> bool:
    m = re.search(
        r'@bp\\.route\\("/web/<path:filename>", endpoint="web"\\)\\s*'
        r"def render\\(filename\\):(?P<body>.*?)(?:\\n\\n@bp\\.route|\\Z)",
        app_blueprint,
        re.DOTALL,
    )
    if not m:
        return False
    block_start = max(0, m.start() - 200)
    block = app_blueprint[block_start:m.end()]
    return "@login_required(" not in block


def main() -> None:
    workdir = Path(tempfile.mkdtemp(prefix="pyload-traceback-infoleak-"))
    try:
        app_blueprint = read_text("webui/app/blueprints/app_blueprint.py")
        handlers = read_text("webui/app/handlers.py")
        base_template = read_text("webui/app/themes/modern/templates/base.html")

        unauth_web_route = '/web/<path:filename>' in app_blueprint and route_has_no_login_required(app_blueprint)
        user_controlled_template_name = "render_template(filename)" in app_blueprint
        handler_uses_traceback = "traceback.format_exc()" in handlers
        handler_appends_trace = "messages.extend(tb.split('\\n'))" in handlers
        global_exception_handler = "(Exception, handle_exception_error)" in handlers
        template_renders_messages = "{% for message in messages %}" in base_template and "{{message}}" in base_template

        leaked_traceback_keyword