GitHub Advisoryadvisory · npm

GHSA-2vg6-77g8-24mp Better Auth: Stale sessions persist after user deletion across admin, anonymous, and SCIM flows

LOW Published 2026-07-07 Modified 2026-07-07 Official page ↗
Ecosystem
npm
Packages
better-auth, @better-auth/scim
Severity
Low
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N
Weaknesses
CWE-459, CWE-613, CWE-672
Reviewed
2026-07-07

Affected packages

• npm better-auth: >= 0.3.4, < 1.6.11 → fixed in 1.6.11 • npm @better-auth/scim: >= 1.6.0, < 1.6.11 → fixed in 1.6.11

Description

Am I affected? Users are affected if all of the following are true: • They configure `secondaryStorage` on `betterAuth(...)` (Redis, KV, or any external session cache). • `session.storeSessionInDatabase` is left unset or set to `false` (the default). • Their application's deployment uses one or more of: • The `admin` plugin and calls `auth.api.removeUser(...)` or `authClient.admin.removeUser(...)`. • The `anonymous` plugin and exposes `/delete-anonymous-user` or relies on the after-link hook to clean up the anonymous user. • The `@better-auth/scim` plugin and exposes `DELETE /scim/v2/Users/:userId`. If `storeSessionInDatabase` is `true`, sessions are also written to the database, and the database delete cascades; users are not affected. Fix: 1. Upgrade to `better-auth@` or later (and `@better-auth/scim@` if they use SCIM). 2. If they cannot upgrade, see workarounds below. Summary When `secondaryStorage` is configured and `storeSessionInDatabase` is `false`, three user-deletion endpoints in `better-auth` plus one in `@better-auth/scim` call `internalAdapter.deleteUser(userId)` without first calling `internalAdapter.deleteSessions(userId)`. The deleted user's session payload (which carries a cached user object) remains in secondary storage, and `internalAdapter.findSession(token)` keeps returning it as a valid session until the session TTL elapses (default 7 days). Details The vulnerable call sites are: • `admin` plugin's `removeUser` (`packages/better-auth/src/plugins/admin/routes.ts:1463`). • `anonymous` plugin's self-delete endpoint (`packages/better-auth/src/plugins/anonymous/index.ts:222`). • `anonymous` plugin's after-link hook (`packages/better-auth/src/plugins/anonymous/index.ts:325`). • `@better-auth/scim`'s `DELETE /scim/v2/Users/:userId` (`packages/scim/src/routes.ts:1019`). Working callers that already do the right thing: the core `/delete-user` self-delete and `/delete-user/callback` (`packages/better-auth/src/api/routes/update-user.ts:551`). The fix shape extends each vulnerable caller to invoke `deleteSessions(userId)` before `deleteUser(userId)`. The architectural follow-up centralizes the cleanup inside `deleteUser` itself or introduces a single `deleteUserAndSessions` orchestrator so future callers cannot regress this contract. Patches Fixed in `better-auth@` and `@better-auth/scim@`. All four user-deletion call sites now invoke `deleteSessions(userId)` before `deleteUser(userId)` so sessions are evicted from secondary storage at the same time the user row is removed. Workarounds If users cannot upgrade immediately: • Configuration-level: set `session.storeSessionInDatabase: true`. Subsequent user-delete writes reach the session table and the database cascade removes rows. Increases write volume for high-throughput sessions but eliminates the gap. • Code-level (admin path): when calling `auth.api.removeUser`, also call `auth.api.revokeUserSessions({ body: { userId } })`, which uses `deleteSessions` internally. • Code-level (SCIM path): wrap their SCIM provider's deprovisioning hook to call `auth.api.revokeUserSessions(...)` after the SCIM DELETE. • Code-level (anonymous path): in `onLinkAccount`, explicitly call `internalAdapter.deleteSessions(anonymousUser.user.id)` before allowing the new session to be issued. Impact • Stale session validity: a deleted user's existing session cookie continues to authenticate against `getSessionFromCtx` until the session TTL elapses (default 7 days). Within that window, the deleted user retains their pre-existing read and write surface. • SCIM-driven deprovisioning gap: organizations using SCIM to revoke offboarded employees' access do not, in fact, revoke active sessions. The deleted account remains usable for up to 7 days after deprovisioning. Credit Reported by @iruizsalinas. Resources • CWE-613: Insufficient Session Expiration • CWE-672: Operation on a Resource after Expiration or Release • CWE-459: Incomplete Cleanup

Related

Weakness · not in database (1)

Weakness · CWE (2)

  • CWE-459 Incomplete Cleanup weakness
  • CWE-672 Operation on a Resource after Expiration or Release weakness

References