STORAGE_PUBLIC_BASE_URL is gone: every file URL is presigned, with an expiry capped by the files gateway, instead of a permanent public link. The admin session list no longer returns session tokens, and admin.revokeUserSession takes userId and sessionId instead of sessionToken. Accepting an Invitation now requires a verified email.
Client IP addresses, used by the Better Auth rate limiter, audit records and MCP JWKS limits, now come from one rule. On Workers the template reads cf-connecting-ip, set by Cloudflare’s edge. On a Node deployment behind a proxy, set TRUSTED_PROXY_HEADER (for example x-forwarded-for) and TRUSTED_PROXIES (hops from the right, default 1). Without them the socket address is used and client headers are ignored, so forged headers no longer pick a fresh rate-limit bucket.
Failed sign-ins and failed second factors are now counted: the rate limiter commits on its own connection, so the counter survives the rolled-back auth request. On Node the limiter has a small pool of its own, so concurrent sign-ins no longer hang waiting for a connection. A User removed from their personal Organization is no longer made its owner again on the next sign-in. A removed MCP signing key stops validating within 60 seconds. The per-request session memo is keyed by the presented cookie or bearer token, so an inner request with other credentials reads its own session.
Uploads recheck the uploader’s membership before committing a file, document submission checks ownership before locking the queue row, and integrations hide deliveries from Organizations the User has left. A saved billing customer link now wins over provider user hints, and the seed refuses non-local hosts. When another tab switches Organization or account, each open tab resets its query cache and reloads its routes instead of showing the previous identity’s rows.
Sources: isolation gaps, removed owners and failed credentials, trusted client IP, limiter pool, JWKS revocation, session memo, cross-tab sessions.