feat(django): Add user attributes in span streaming - #6541
5 issues
Medium
Span-streaming path uses `set_user()` which overwrites custom user fields, diverging from static-mode `setdefault` behavior - `sentry_sdk/integrations/django/__init__.py:499`
In _after_get_response, the span-streaming branch calls sentry_sdk.set_user(user_info), which routes to Scope.set_user and does a full replacement (self._user = value). Because _after_get_response runs after old_get_response() (the view/middleware), any user fields the application set during the request (e.g. segment, custom attributes, or an ip_address set elsewhere) are discarded. This diverges from the static-mode event processor _set_user_info(), which uses event.setdefault("user", {}) and user_info.setdefault(...) so application-provided fields win. Consider merging into the existing scope user (or matching the setdefault pattern) so span-streaming and static modes behave consistently.
No test covering the early return for un-materialized `SimpleLazyObject` user - `sentry_sdk/integrations/django/__init__.py:476`
The fix for issue #5274 (returning early when is_lazy=True and _cached_user is absent) has no test, so it could silently regress and re-introduce the SynchronousOnlyOperation crash in async views.
Also found at:
sentry_sdk/integrations/django/__init__.py:43
Low
SimpleLazyObject early-exit misses already-evaluated users when `_cached_user` is absent - `sentry_sdk/integrations/django/__init__.py:43`
The guard in _after_get_response checks hasattr(request, '_cached_user') to decide whether the lazy user has been materialized, but a SimpleLazyObject can be fully evaluated (its _wrapped attribute set) without _cached_user ever being set on the request — for example, with custom auth backends that wrap the user in their own lazy object. In that case elif is_lazy: return bails early and silently drops user attributes for an already-safe user object. Consider checking user._wrapped is not empty (importable from django.utils.functional) instead, which can be read without triggering evaluation.
`sentry_sdk.set_user({})` overwrites isolation-scope user when all attribute extractions fail - `sentry_sdk/integrations/django/__init__.py:500`
In _after_get_response, when span streaming and PII are enabled, the integration extracts user.pk, user.email, and user.get_username(), each wrapped in a silent try/except. If all three raise (e.g. a custom user model, deferred fields, or a SynchronousOnlyOperation when lazily evaluating attributes in an async context), user_info stays {} and sentry_sdk.set_user(user_info) is still called unconditionally. Unlike the event-based _set_user_info, which merges via event.setdefault/user_info.setdefault, this path calls Scope.set_user, which does self._user = value, fully replacing any user context an application previously set on the isolation scope with an empty dict. Adding an if user_info: guard before sentry_sdk.set_user would avoid clobbering existing user data.
Early return skips user attributes for an evaluated `SimpleLazyObject` lacking `request._cached_user` - `sentry_sdk/integrations/django/__init__.py:476`
In _after_get_response, when span streaming and PII are enabled, the code guards against evaluating an unmaterialized SimpleLazyObject (which can raise SynchronousOnlyOperation in async views) by checking hasattr(request, "_cached_user"). That attribute is set by Django's standard django.contrib.auth.middleware.get_user(). If a custom auth middleware sets request.user = SimpleLazyObject(custom_fn) where custom_fn never writes request._cached_user, then even after the object is fully materialized the elif is_lazy: return branch fires and silently skips setting user attributes on the span. Impact is limited to missing id/email/username attributes in non-standard configurations; there is no exception or security impact. In the standard Django auth flow _cached_user is populated, so the common case behaves correctly.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| security-review | 0 | 18.8s | $0.04 |
| code-review | 3 | 10m 26s | $2.27 |
| find-bugs | 2 | 7m 4s | $1.75 |
| skill-scanner | 0 | 19.5s | $0.01 |
⏱ 18m 9s · 4.6M in / 102.3k out · $4.07