Packages / better-auth / Security

LowVulnerabilityGHSA-2vg6-77g8-24mpCVE-2026-67334Published 7 Jul 2026

Better Auth: Stale sessions persist after user deletion across admin, anonymous, and SCIM flows

Affected releases · better-auth

  • 1.x1.0.0 – 1.6.10Fixed in 1.6.11
  • 0.8.x and older0.3.4 – 0.8.8No fix, no longer updated

On 1.x, the newest line:

npm install better-auth@1.6.11
1.x
  1. Affected

    1.0.0 – 1.6.10

    133 releases

  2. Fixed

    1.6.11 – 1.7.12

    36 releases, latest

0.x
  1. Not affected

    0.0.1 – 0.3.3

    22 releases

  2. Affected

    0.3.4 – 0.8.8

    39 releases

230 stable releases of better-auth, oldest to newest, one row per major.

Is your project affected?

Paste your package.json, or type the range you declare for better-auth. It is read in your browser and sent nowhere.

What happens

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@<patched-version> or later (and @better-auth/scim@<patched-version> 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@<patched-version> and @better-auth/scim@<patched-version>. 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