---
id: GHSA-2vg6-77g8-24mp
title: >-
  Better Auth: Stale sessions persist after user deletion across admin,
  anonymous, and SCIM flows
summary: >-
  Better Auth: Stale sessions persist after user deletion across admin,
  anonymous, and SCIM flows
severity: low
cvss: 3.8
cwe:
  - CWE-459
  - CWE-613
  - CWE-672
vendor: better-auth
product: better-auth
ecosystem: npm
affected:
  - 'better-auth >= 0.3.4, < 1.6.11'
  - '@better-auth/scim >= 1.6.0, < 1.6.11'
patched:
  - better-auth 1.6.11
  - '@better-auth/scim 1.6.11'
published: '2026-07-07'
updated: '2026-07-07'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-2vg6-77g8-24mp'
references:
  - url: >-
      https://github.com/better-auth/better-auth/security/advisories/GHSA-2vg6-77g8-24mp
  - url: 'https://github.com/better-auth/better-auth/releases/tag/v1.6.11'
  - url: 'https://github.com/advisories/GHSA-2vg6-77g8-24mp'
tags:
  - ghsa
  - npm
ingestedAt: '2026-07-07T21:43:23.230Z'
---

## Overview

### 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

- [CWE-613: Insufficient Session Expiration](https://cwe.mitre.org/data/definitions/613.html)
- [CWE-672: Operation on a Resource after Expiration or Release](https://cwe.mitre.org/data/definitions/672.html)
- [CWE-459: Incomplete Cleanup](https://cwe.mitre.org/data/definitions/459.html)

## Affected packages

- `better-auth >= 0.3.4, < 1.6.11`
- `@better-auth/scim >= 1.6.0, < 1.6.11`

## Remediation

Upgrade to a patched release:

- `better-auth 1.6.11`
- `@better-auth/scim 1.6.11`
