Skip to content

Migrate TemplateResponse to Starlette 1.x signature (unpin starlette, clear CVEs) #181

Description

@beorngb

Summary

The codebase is pinned to starlette<0.51.0 because every templates.TemplateResponse(...) call uses the pre-1.0 positional signature (name, context), which Starlette 1.x removed in favour of (request, name, context). On Starlette 1.x the old form raises TypeError: unhashable type: 'dict' (the context dict is treated as the template name). This blocks upgrading past Starlette 0.50.0.

Starlette 0.50.0 has 5 known vulnerabilities (PYSEC-2026-161/248/249/2280/2281), all fixed in Starlette 1.x. They are currently ignored in pip-audit (.github/workflows/ci.yml, "Dependency vulnerability scan") with a per-CVE reachability justification — none are reachable in the running app today, but that is a point-in-time audit, not a fix.

This is not blocked upstream: FastAPI ≥ 0.135 already requires starlette>=0.46.0 with no upper bound, so it accepts Starlette 1.x. The only blocker is our own TemplateResponse usage.

Scope

  • 109 TemplateResponse call sites across 33 files (ui/routes/*, ui/app.py, and ~15 core plugins/*/admin/routes.py + portal/routes.py).
  • All are the same multiline shape; request is available in every case (in the context dict or via a get_template_context(request, ...) / get_ctx(request, ...) helper). No call uses status_code=/headers= kwargs.
  • The transform is mechanical: insert request as the first positional argument.
templates.TemplateResponse(           templates.TemplateResponse(
    "name.html",              →           request,
    {"request": request, ...},            "name.html",
)                                         {"request": request, ...},
                                      )

Risk — this is a Starlette major upgrade, not just a rename

The 109 call sites are the known break. A 0.50 → 1.x major bump can carry others, and must be regression-tested across every admin/portal/plugin page. Specific things to check:

  • ui/app.py monkeypatches anyio internals (_anyio_asyncio.CancelScope._deliver_cancellation, the CPU-leak fix). A Starlette 1.x bump pulls a newer anyio that may break this patch.
  • Middleware stack (CSRF, Session, ProxyHeaders, GZip, context) and custom static mounts.
  • Any other removed/renamed Starlette 1.0 APIs.

Acceptance criteria

  • Migrate all 109 TemplateResponse calls to the (request, name, context) signature.
  • Raise the pin to a Starlette 1.x range (and align the FastAPI floor if needed).
  • Verify the anyio monkeypatch in ui/app.py still applies (or replace it).
  • Full regression of admin UI, /me portal, and all plugin admin pages.
  • Remove the 6 starlette --ignore-vuln flags from .github/workflows/ci.yml.

Interim state

  • starlette>=0.40.0,<0.51.0 pinned in pyproject.toml.
  • The 5 CVEs ignored in CI with a documented reachability audit (unmounted OAuth2BearerMiddleware is the only request.url.path security check; no request.url.hostname usage; no configured form limits; no HTTPEndpoint subclasses; Linux-only deploy).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions