# Auth and tenant security fixes

Isolation gaps found before row-level security are closed, client IPs are trusted only where the deployment vouches for them, and failed sign-ins are counted.

<Callout title="Breaking: storage, admin and invitations">
  `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.
</Callout>

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.

- [Authentication](/docs/features/auth)
- [Environments](/docs/development/environments)
- [MCP server](/docs/features/mcp)
- [Files and uploads](/docs/features/files)

Sources: [isolation gaps](https://github.com/CarlosZiegler/template-cloudflare/commit/860fa26), [removed owners and failed credentials](https://github.com/CarlosZiegler/template-cloudflare/commit/608ad29), [trusted client IP](https://github.com/CarlosZiegler/template-cloudflare/commit/6bdc605), [limiter pool](https://github.com/CarlosZiegler/template-cloudflare/commit/f7b1958), [JWKS revocation](https://github.com/CarlosZiegler/template-cloudflare/commit/7d7c750), [session memo](https://github.com/CarlosZiegler/template-cloudflare/commit/503c0dc), [cross-tab sessions](https://github.com/CarlosZiegler/template-cloudflare/commit/cb08640).

