Skip to content

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

Low severity GitHub Reviewed Published May 31, 2026 in better-auth/better-auth • Updated Jul 7, 2026

Package

npm @better-auth/scim (npm)

Affected versions

>= 1.6.0, < 1.6.11

Patched versions

1.6.11
npm better-auth (npm)
>= 0.3.4, < 1.6.11
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@<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

References

@gustavovalverde gustavovalverde published to better-auth/better-auth May 31, 2026
Published to the GitHub Advisory Database Jul 7, 2026
Reviewed Jul 7, 2026
Last updated Jul 7, 2026

Severity

Low

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
High
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
Low
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N

EPSS score

Weaknesses

Incomplete Cleanup

The product does not properly clean up and remove temporary or supporting resources after they have been used. Learn more on MITRE.

Insufficient Session Expiration

According to WASC, Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization. Learn more on MITRE.

Operation on a Resource after Expiration or Release

The product uses, accesses, or otherwise operates on a resource after that resource has been expired, released, or revoked. Learn more on MITRE.

CVE ID

No known CVE

GHSA ID

GHSA-2vg6-77g8-24mp

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.