GHSA-2vg6-77g8-24mp

Suggest an improvement
Source
https://github.com/advisories/GHSA-2vg6-77g8-24mp
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2vg6-77g8-24mp/GHSA-2vg6-77g8-24mp.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-2vg6-77g8-24mp
Aliases
Published
2026-07-07T20:56:45Z
Modified
2026-09-10T03:50:51Z
Severity
  • 3.8 (Low) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N CVSS Calculator
Summary
Better Auth: Stale sessions persist after user deletion across admin, anonymous, and SCIM flows
Details

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

Database specific
{
    "cwe_ids": [
        "CWE-459",
        "CWE-613",
        "CWE-672"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-07T20:56:45Z",
    "nvd_published_at": null,
    "severity": "LOW"
}
References

Affected packages

npm / better-auth

Package

Affected ranges

Type
SEMVER
Events
Introduced
0.3.4
Fixed
1.6.11

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2vg6-77g8-24mp/GHSA-2vg6-77g8-24mp.json"

npm / @better-auth/scim

Package

Name
@better-auth/scim
View open source insights on deps.dev
Purl
pkg:npm/%40better-auth/scim

Affected ranges

Type
SEMVER
Events
Introduced
1.6.0
Fixed
1.6.11

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2vg6-77g8-24mp/GHSA-2vg6-77g8-24mp.json"