How do you use Sentry?
Sentry SaaS (sentry.io)
Version
2.64.0 (also reproduces on current master, HEAD 44fb035)
Steps to Reproduce
With AuthenticationMiddleware + StarletteIntegration + send_default_pii=True, the integration extracts the user from the ASGI request and calls
scope.set_user(...) after the request handler has run (patch_authentication_middleware calls _add_user_to_sentry_scope after await old_call(...), sentry_sdk/integrations/starlette.py:411-412).
Because set_user replaces the scope user (starlette.py:392), any user the application set during the request via sentry_sdk.set_user(...) is discarded.
And Starlette's SimpleUser/BaseUser only exposes username, so id/email are lost.
repro:
import sentry_sdk
from sentry_sdk.integrations.starlette import StarletteIntegration
from starlette.applications import Starlette
from starlette.authentication import AuthCredentials, AuthenticationBackend, SimpleUser
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient
captured = []
class TokenBackend(AuthenticationBackend):
async def authenticate(self, conn):
# request principal only carries the token subject (username)
return AuthCredentials(["authenticated"]), SimpleUser("token-subject-123")
async def endpoint(request):
# app enriches the Sentry user during the request
sentry_sdk.set_user({"id": "user_42", "email": "real@user.com", "username": "token-subject-123"})
return PlainTextResponse("ok")
def capture(event, hint):
captured.append(event.get("user"))
return None # drop, don't send
sentry_sdk.init(
dsn="https://public@o0.ingest.sentry.io/0",
send_default_pii=True,
traces_sample_rate=1.0,
integrations=[StarletteIntegration()],
before_send_transaction=capture,
)
app = Starlette(
routes=[Route("/", endpoint)],
middleware=[Middleware(AuthenticationMiddleware, backend=TokenBackend())],
)
TestClient(app).get("/")
print(captured[-1])
pip freeze --all:
sentry-sdk==2.64.0
starlette==0.48.0
Expected Result
The request-derived user augments what the app set; app-set values are kept:
{'username': 'token-subject-123', 'id': 'user_42', 'email': 'real@user.com'}
Actual Result
The app-set id/email are clobbered — only the request principal survives:
{'username': 'token-subject-123'}
Real-world impact: on a FastMCP-based MCP server (bearer auth, stateless_http) every transaction loses user.email/user.id; only user.username (the token subject) survives, so traces can't be attributed to real users.
How do you use Sentry?
Sentry SaaS (sentry.io)
Version
2.64.0 (also reproduces on current
master, HEAD 44fb035)Steps to Reproduce
With
AuthenticationMiddleware+StarletteIntegration+send_default_pii=True, the integration extracts the user from the ASGI request and callsscope.set_user(...)after the request handler has run (patch_authentication_middlewarecalls_add_user_to_sentry_scopeafterawait old_call(...),sentry_sdk/integrations/starlette.py:411-412).Because
set_userreplaces the scope user (starlette.py:392), any user the application set during the request viasentry_sdk.set_user(...)is discarded.And Starlette's
SimpleUser/BaseUseronly exposesusername, soid/emailare lost.repro:
pip freeze --all:Expected Result
The request-derived user augments what the app set; app-set values are kept:
{'username': 'token-subject-123', 'id': 'user_42', 'email': 'real@user.com'}Actual Result
The app-set
id/emailare clobbered — only the request principal survives:{'username': 'token-subject-123'}Real-world impact: on a FastMCP-based MCP server (bearer auth,
stateless_http) every transaction losesuser.email/user.id; onlyuser.username(the token subject) survives, so traces can't be attributed to real users.