Jul 7, 2026·5 min read·2 visits
Better Auth failed to evict cached sessions from secondary storage (e.g., Redis) when deleting users via admin, anonymous, or SCIM endpoints. Deleted users could continue accessing the system with active cookies for up to 7 days.
A critical session persistence vulnerability exists within the Better Auth framework when configured to use external secondary storage (such as Redis or Cloudflare KV) with default database options. Due to four incomplete user-deletion code paths, active user sessions are not evicted from secondary storage caches during deletion events. As a result, deleted users retain full system access via their pre-existing session cookies until the Session Time-To-Live (TTL) expires.
The Better Auth ecosystem utilizes an abstraction layer to manage user registration, authentication, and session handling. To optimize session verification latency, developers frequently deploy a double-tier storage strategy. In this architecture, relational databases host long-term user records, while memory-efficient 'secondaryStorage' systems (such as Redis or Cloudflare KV) handle active session tokens.
By default, the session.storeSessionInDatabase configuration parameter is set to false. When configured in this manner, session data resides exclusively in secondary memory-based storage. In database-centric configurations, relational foreign-key cascading deletes automatically eliminate active session rows when a parent user row is deleted. However, when sessions reside exclusively in cache tiers, session eviction must be explicitly handled by application-level logic.
This vulnerability stems from a logical discrepancy in four separate user-deletion handlers. While core self-deletion routes safely trigger the required two-stage cleanup sequence, the Admin, Anonymous, and SCIM plugin endpoints execute the database-level user deletion without purging the active session cached inside the secondary storage system. This allows unauthorized API access via orphaned sessions.
The underlying cause is categorized under CWE-613 (Insufficient Session Expiration) and CWE-459 (Incomplete Cleanup). The Better Auth database adapter interface exposes two key operations: internalAdapter.deleteUser(userId) and internalAdapter.deleteSessions(userId). In standard deployment topologies using external Key-Value stores, these endpoints must be chained sequentially during deletion routines.
An investigation of the codebase reveals that four operational endpoints failed to orchestrate this chain. Specifically, the framework invoked the database layer to drop the user record but completely bypassed session revocation inside the cache. Because the external session store lacks transactional integrity with the main database, the session token remains active inside the Redis cache.
When a request containing a stale cookie reaches the authentication middleware, getSessionFromCtx queries the secondary storage via internalAdapter.findSession(token). Because the cached session is not deleted, the validation checks succeed. The framework reconstructs a valid user session context without referencing the primary database, permitting unauthorized read/write access until the cache item reaches its natural expiration (TTL), which defaults to 7 days.
An analysis of the vulnerable code paths demonstrates the implementation gap. In the Admin plugin (packages/better-auth/src/plugins/admin/routes.ts), the removeUser API route executed user deletion directly without clearing active cache entries.
// VULNERABLE IMPLEMENTATION (routes.ts)
const removeUser = createAuthRoute("/admin/remove-user", {
method: "POST",
// ... validation schema
}, async (ctx) => {
const { userId } = ctx.body;
// Missing session eviction step
await ctx.context.internalAdapter.deleteUser(userId);
return ctx.json({ success: true });
});The patched version integrates explicit, blocking session revocation prior to deleting user relational rows. This prevents orphaned session records from residing in secondary memory structures.
// PATCHED IMPLEMENTATION (routes.ts)
const removeUser = createAuthRoute("/admin/remove-user", {
method: "POST",
// ... validation schema
}, async (ctx) => {
const { userId } = ctx.body;
// Explicitly evict cached sessions first to guarantee cleanup
await ctx.context.internalAdapter.deleteSessions(userId);
await ctx.context.internalAdapter.deleteUser(userId);
return ctx.json({ success: true });
});This same programmatic deficiency was present across three other handlers. In @better-auth/scim (routes.ts), the SCIM deprovisioning endpoint DELETE /scim/v2/Users/:userId deleted user profiles but left their enterprise sessions intact. Additionally, within the Anonymous plugin, both the /delete-anonymous-user route and the post-link account hook failed to evict temporary sessions, leaving unauthenticated persistent endpoints exposed.
Exploiting this flaw does not require active technical bypasses or complex payload payloads. Instead, it relies on timing and state discrepancies during standard account deprovisioning sequences. The primary threat scenario involves insider threat actors, compromised accounts, or deprovisioned employees.
To demonstrate this flow, consider an enterprise setting using SCIM synchronization. An identity provider detects a terminated employee and issues an automated SCIM deletion request. The application processes the delete operation successfully, removing the database record. However, because the employee's browser has an active session cookie, the employee can continue to query internal API endpoints. The underlying cached session remains untouched, authorizing access to confidential resources until the session TTL expires.
The security implications of this session persistence vulnerability are significant, especially within enterprise environments. A CVSS v3.1 base score of 3.8 (Low) is assigned primarily due to the privileges required to trigger deletion flows (e.g., an Administrator or Identity Provider). However, the actual impact on confidentiality and integrity can be high depending on the application context.
If a malicious insider is terminated, or if an administrator deletes a compromised user account to contain an ongoing incident, the containment will fail. The attacker will maintain access to operational APIs and database records. The vulnerability also directly undermines compliance standards (such as SOC 2 or ISO 27001) that mandate immediate, absolute termination of logical access upon employee deprovisioning.
The primary resolution is to upgrade all dependencies to the patched version. The maintainers resolved these session lifecycle bugs in the official v1.6.11 release. Security teams should perform immediate package updates across affected microservices.
In scenarios where immediate upgrades are blocked by regression testing pipelines, developers can apply targeted database configuration changes. Setting session.storeSessionInDatabase to true acts as a highly effective workaround. This transfers session states to the primary database, where cascading foreign-key rules enforce session destruction at the schema layer.
Alternatively, custom middleware or wrappers around user-deletion tasks can explicitly invoke session revocation via auth.api.revokeUserSessions prior to calling deletion methods. For the Anonymous plugin, implementing manual token clearing inside the onLinkAccount lifecycle hook prevents leftover credentials from persisting.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
better-auth Better Auth | >= 0.3.4, < 1.6.11 | 1.6.11 |
@better-auth/scim Better Auth | >= 1.6.0, < 1.6.11 | 1.6.11 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | GHSA-2vg6-77g8-24mp |
| CWE ID | CWE-613, CWE-459, CWE-672 |
| Attack Vector | Network |
| CVSS v3.1 Score | 3.8 (Low) |
| Exploit Status | Conceptual / Logic Defect |
| Remediation Status | Patched in v1.6.11 |
The application does not invalidate the session cache inside secondary storage when the corresponding user account is deleted.
A stored cross-site scripting vulnerability exists in the oidc-provider and mcp plugins of Better Auth. Attackers can register malicious clients with javascript: redirect URIs, leading to origin takeover when users authorize the client.
The Better Auth framework's OIDC provider implementation (oidcProvider) contained insecure cryptographic defaults before version 1.6.11. It advertised the insecure alg=none signing algorithm and accepted plain PKCE challenges by default, leaving downstream clients vulnerable to token signature bypasses and authorization code interception attacks.
An authorization bypass vulnerability in the @better-auth/scim plugin allows authenticated attackers to hijack personal SCIM providers and subsequently perform full account takeovers.
A behavioral mismatch vulnerability (CWE-701) in the Rust-based uutils coreutils implementation of common command-line utilities allows silent data loss. When the --suffix argument is executed without explicit backup flags, the uucore library fails to enter backup mode, silently overwriting target files instead of creating preserving copies as expected under GNU standards.
Open WebUI versions prior to 0.6.6 contain a stored cross-site scripting (XSS) vulnerability that allows low-privileged users to upload malicious HTML files containing arbitrary JavaScript. When viewed by an administrator, the executed script can abuse administrative APIs to register malicious functions, leading to remote code execution on the underlying server host.
A critical Stored Cross-Site Scripting (XSS) vulnerability exists in Open WebUI versions prior to 0.6.6. The vulnerability resides in client-side Markdown rendering, where unvalidated iframe tags containing local API base URLs bypass DOMPurify sanitization. This flaw allows authenticated attackers to steal user session tokens. If an administrative session is compromised, the attacker can leverage the application's native Python execution capabilities ('Functions') to achieve arbitrary Remote Code Execution (RCE) on the hosting server.