fix(starlette): Stop duplicating scope["root_path"] in URLs#6579
6 issues
Medium
Quart integration silently drops all monitoring when `package_version()` returns None - `sentry_sdk/integrations/quart.py:95-98`
If package_version("quart") returns None (e.g. package metadata is unavailable in a bundled/frozen app), the new or version is None guard causes the patched ASGI app to silently bypass the Sentry middleware entirely, losing all request monitoring. Consider falling back to a safe default (e.g. path_includes_root_path=False) instead of skipping the middleware.
test_request_url fails on Django 3.0–5.0 due to version-unaware path assertion - `tests/integrations/django/asgi/test_asgi.py:1085-1101`
The test passes scope["path"] = "/root/nomessage" together with scope["root_path"] = "/root", but for Django < 5.1 the SDK uses path_includes_root_path=False, which prepends root_path to path, yielding "/root/root/nomessage" — not the asserted "/root/nomessage". Either restrict the test to django.VERSION >= (5, 1) or use path "/nomessage" (without the root prefix) so that root_path + path gives the expected URL on all versions.
Also found at:
sentry_sdk/integrations/django/asgi.py:103
test_request_url does not exercise the root_path duplication fix for Starlette - `tests/integrations/starlette/test_starlette.py:1486-1525`
The test uses TestClient with its default root_path="", so the Sentry middleware (which wraps Starlette.__call__ and captures the outer ASGI scope) always sees root_path="". With an empty root_path, the path_includes_root_path flag makes no difference and the duplication bug is never triggered. Consider using TestClient(starlette_app, root_path="/root") with a route at /nomessage (similar to the Quart test which passes root_path="/root" in the request) to actually exercise the fix.
Also found at:
tests/integrations/quart/test_quart.py:1021
Low
Default `path_includes_root_path=True` changes URL behavior for direct `SentryAsgiMiddleware` users on non-spec-compliant frameworks - `sentry_sdk/integrations/_asgi_common.py:35`
The new default path_includes_root_path=True makes _get_url() use scope["path"] directly instead of always prepending scope["root_path"]. This is correct for ASGI-spec-compliant frameworks (it fixes the previous root_path duplication). However, a user who wires SentryAsgiMiddleware directly around a non-spec-compliant framework (where scope["path"] does NOT contain scope["root_path"]) will now see root_path dropped from reported URLs/transaction names — a behavioral change. The built-in integrations for the known non-compliant frameworks (Starlite, Litestar, old Starlette/Quart) pass path_includes_root_path=False, so they are unaffected. The mitigation for direct users is to pass path_includes_root_path=False explicitly, but this is not documented as a migration step in the PR. Consider noting this in the changelog/docs.
Also found at:
sentry_sdk/integrations/asgi.py:462
Quart ASGI middleware silently skipped when version is unparseable - `sentry_sdk/integrations/quart.py:20`
In patch_asgi_app(), the newly introduced guard or version is None (line 97) causes sentry_patched_asgi_app to fall through to old_app on every request when package_version("quart") returns None. Previously the SentryAsgiMiddleware was applied unconditionally. Because package_version returns None when the installed version string is unparseable, an edge-case Quart install would silently lose ASGI-level transaction/tracing instrumentation (other signal-based capture remains connected). Unlike the Starlette integration, which raises DidNotEnable on an unparseable version, Quart fails open silently and the guard could instead default path_includes_root_path to the pre-0.19 behavior (False).
Also found at:
sentry_sdk/integrations/quart.py:97-98
test_request_url asserts wrong URL for Django 3.0–5.0, where path_includes_root_path=False causes double root_path prefix - `tests/integrations/django/asgi/test_asgi.py:1093-1101`
The test sets scope["path"] = "/root/nomessage" (spec-compliant, root_path already included) and scope["root_path"] = "/root", then asserts the recorded URL is /root/nomessage. But for Django < 5.1, path_includes_root_path=False is used (see django/asgi.py line 103), so _get_url() returns root_path + path = "/root" + "/root/nomessage" = "/root/root/nomessage". The assertions at lines 1093 and 1101 will fail for all Django 3.0–5.0 versions; the test should either be skipped for django.VERSION < (5, 1) or use scope["path"] = "/nomessage" when testing old-Django behavior.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| security-review | 0 | 17.5s | $0.05 |
| code-review | 4 | 9m 11s | $3.80 |
| find-bugs | 2 | 20m 30s | $5.19 |
| skill-scanner | 0 | 11m 59s | $0.04 |
⏱ 41m 57s · 6.5M in / 282.3k out · $9.07