CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2025-66803

The Undead Session: Explaining the Race Condition in Hotwired Turbo

Alon Barad
Alon Barad
Software Engineer

Jan 20, 2026·5 min read·45 visits

Executive Summary (TL;DR)

If you use Turbo Frames with cookie-based sessions (like default Rails), a slow network request from a frame can arrive *after* a user logs out. Because the browser processes the 'Set-Cookie' header from the delayed response immediately, the old session cookie is resurrected, and the user is re-authenticated without their knowledge.

A critical race condition in Hotwired Turbo allows delayed background requests to restore destroyed session cookies, effectively logging a user back in after they have signed out.

The Hook: Turbo's Need for Speed

Hotwired Turbo (formerly Turbolinks) is the darling of the Ruby on Rails world. It promises the snappy feel of a Single Page Application (SPA) without the crushing complexity of React or Vue state management. It does this by intercepting links and form submissions, fetching HTML in the background, and swapping out the body <body> or specific <turbo-frame> elements.

But here's the thing about doing everything in the background: asynchronicity is hard. When you fire off a dozen requests to populate different frames on a dashboard, you lose the linear guarantee of a traditional page load. You are effectively throwing packets at the server and hoping they come back in an order that makes sense.

CVE-2025-66803 describes a scenario where that hope dies. It's a classic race condition, but it's not happening in your database or your threads—it's happening between your user's mouse click and the browser's network stack. It turns out, making things feel "fast" can sometimes make security remarkably loose.

The Flaw: The Browser's Dirty Secret

To understand this vulnerability, you have to understand a fundamental behavior of web browsers that most developers ignore: The Network Stack and the DOM are decoupled.

When a JavaScript application (like Turbo) initiates a fetch() request, the browser handles the networking. If the server responds with HTTP headers, the browser processes them immediately. Crucially, if the response includes a Set-Cookie header, the browser updates its cookie jar before the JavaScript runtime even gets a chance to look at the response body or decide if it still cares about the data.

In the context of Turbo, imagine a <turbo-frame> requesting a slow widget. While that request is hanging in limbo, the user clicks "Logout". The application processes the logout, the server destroys the session, and the browser clears the cookie. Clean and secure, right?

Wrong. That slow widget request is still alive. A second later, it finally completes. The server, which processed that request before the session was destroyed (or concurrently), sends back a 200 OK with the Set-Cookie header containing the old, valid session ID. The browser, being a helpful idiot, sees the cookie and dutifully writes it back into the jar. The zombie session is born.

The Code: Failing to Abort

The fix for this is elegant but highlights exactly what was missing. The FrameController class in Turbo manages the lifecycle of frame elements. In versions prior to 8.0.21, when a frame was removed from the DOM (disconnected)—for example, when the page body was replaced during a logout redirect—the pending fetch request was simply orphaned.

The patch introduces the AbortController API to explicitly kill these requests. By calling .cancel(), Turbo signals the browser to sever the connection immediately, preventing the processing of late response headers.

Here is the critical diff in src/core/frames/frame_controller.js:

// BEFORE: The disconnect method didn't clean up network traffic
disconnect() {
  // ... code removing event listeners ...
}
 
// AFTER: Explicitly cancelling the fetch request
disconnect() {
  // ... 
  if (!this.element.hasAttribute("recurse")) {
    this.#currentFetchRequest?.cancel() // <--- The Kill Switch
  }
}
 
// Managing the loading state
sourceURLChanged() {
   if (this.#isIgnoringChangesTo("src")) return
 
   if (!this.sourceURL) {
     this.#currentFetchRequest?.cancel() // <--- Stop fetching if src is cleared
   }
   // ...
}

This .cancel() method bubbles down to trigger AbortController.abort(). This is the digital equivalent of hanging up the phone before the telemarketer (or in this case, the server) can finish their sentence.

The Exploit: Reanimating the Dead

Let's construct a realistic attack scenario. This works best against users on slow connections or applications with heavy server-side processing.

The Setup:

  1. Target: A Rails app using CookieStore (client-side session storage) and Turbo.
  2. Victim: Logged in on a public terminal or shared computer.
  3. Vulnerable Element: A dashboard with a generic <turbo-frame id="notifications" src="/notifications/recent"> that loads automatically.

The Attack Chain:

  1. The victim visits the dashboard. The notifications frame initiates a fetch request.
  2. Lag: The network is slow, or the /notifications/recent endpoint takes 2 seconds to query the database.
  3. Action: The victim clicks "Sign Out" immediately. The app redirects to the landing page. The session cookie is cleared by the logout response.
  4. The Race: The victim walks away.
  5. The Event: 500ms later, the /notifications/recent response arrives. It was generated while the user was still logged in, so it contains the Set-Cookie header with the valid encrypted session string.
  6. Restoration: The browser re-saves the session cookie.
  7. Access: The attacker walks up to the computer, hits "Back" or refreshes the page, and is fully authenticated as the victim.

> [!WARNING] > This is particularly dangerous because from the user's perspective, the UI confirmed they were logged out. The betrayal happens entirely invisibly in the background.

The Fix: Mitigation & Defense

If you are running @hotwired/turbo below version 8.0.21, you are vulnerable. The primary mitigation is obviously to upgrade.

However, this vulnerability also serves as a stark reminder of why Server-Side Sessions are superior to Client-Side Cookie Stores (like Rails' default).

If you use a server-side store (Redis, Memcached, Database):

  1. When the user logs out, you delete the Session ID from Redis.
  2. Even if the race condition occurs and the browser restores the old Session ID cookie, that ID is now invalid on the server.
  3. The next request will fail authentication.

If you stick with stateless cookie sessions (JWTs or encrypted cookies), you are trusting the client to hold the state. If the client (browser) gets confused, your security model collapses. Upgrade Turbo, but also consider architecting your auth system to not trust the browser quite so much.

Official Patches

HotwiredPull Request #1399: Abort Frame requests on disconnect

Fix Analysis (1)

Technical Appendix

CVSS Score
High/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N

Affected Systems

Ruby on Rails applications using TurboAny web application using @hotwired/turbo < 8.0.21Single Page Applications (SPAs) relying on Turbo Frames

Affected Versions Detail

Product
Affected Versions
Fixed Version
@hotwired/turbo
Hotwired
< 8.0.218.0.21
AttributeDetail
CWE IDCWE-362 (Race Condition)
Attack VectorNetwork / User Interaction
CVSSHigh (Estimated)
Vulnerability TypeSession Restoration
ImpactAuthentication Bypass
Patch StatusReleased (v8.0.21)

MITRE ATT&CK Mapping

T1550.004Use Existing Session Material: Session Sidejacking
Defense Evasion
T1539Steal Web Session Cookie
Credential Access
CWE-362
Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')

The software does not properly cancel asynchronous tasks when the state changes, leading to a race condition where a delayed response affects the application state after it has transitioned.

Known Exploits & Detection

GitHub Security AdvisoryOfficial advisory describing the race condition scenario

Vulnerability Timeline

Fix committed to main branch
2025-11-15
CVE Published / Public Disclosure
2026-01-20

References & Sources

  • [1]GHSA-qppm-g56g-fpvp
  • [2]Hotwired Turbo Documentation

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 9 hours ago•CVE-2026-39922
6.3

CVE-2026-39922: Server-Side Request Forgery in GeoNode Service Registration Endpoint

GeoNode versions prior to 4.4.5 and 5.0.2 are vulnerable to Server-Side Request Forgery (SSRF) in the service registration endpoint. Authenticated attackers with low privileges can exploit insufficient input validation in the Web Map Service (WMS) registration module to force the application server to make outbound network queries to loopback addresses, private RFC1918 subnets, link-local scopes, and cloud metadata endpoints. This technical report details the mechanics of the vulnerability, the underlying architectural flaw, and how to effectively remediate and mitigate the associated security risks.

Alon Barad
Alon Barad
4 views•7 min read
•about 19 hours ago•CVE-2022-0492
7.8

CVE-2022-0492: Privilege Escalation and Container Escape via cgroups v1 release_agent

CVE-2022-0492 is a high-severity missing authorization vulnerability in the Linux kernel's Control Groups (cgroups) v1 implementation. The flaw resides within the cgroup_release_agent_write function in kernel/cgroup/cgroup-v1.c, where the kernel fails to validate if the process writing to the release_agent file possesses administrative capabilities in the initial user namespace. This allows a local attacker inside a container with root privileges (UID 0) to abuse user namespaces, mount a cgroups v1 directory, modify the release_agent parameter, and execute arbitrary commands on the host system as host root, effectively achieving a complete container escape.

Amit Schendel
Amit Schendel
8 views•7 min read
•3 days ago•GHSA-G72G-R7M4-9X4G
6.3

GHSA-G72G-R7M4-9X4G: Insufficient Session Expiration of OAuth Tokens in NocoDB

NocoDB is subject to an insufficient session expiration vulnerability where OAuth access and refresh tokens are not invalidated or revoked during security-sensitive actions such as password changes, forgot-password requests, or password resets. This allows an attacker possessing an active OAuth token to maintain unauthorized persistence.

Amit Schendel
Amit Schendel
11 views•6 min read
•3 days ago•GHSA-FGMC-2HQJ-86V4
6.9

GHSA-FGMC-2HQJ-86V4: Default Administrative Credentials in vantage6-server

A vulnerability in the vantage6 federated learning framework allows unauthenticated remote attackers to gain administrative control of the server via hardcoded default credentials (root/root) when deployed under default configurations in versions 4.2.3 and below.

Amit Schendel
Amit Schendel
8 views•5 min read
•3 days ago•GHSA-X9F6-9RVM-MMRG
6.9

GHSA-X9F6-9RVM-MMRG: Improper Access Control and Volume Mount Isolation Bypass in vantage6 Node

An improper access control vulnerability in the vantage6 node component allows concurrently running algorithm containers to read and modify sensitive input and output files of other tasks. The lack of strict workspace directory isolation exposes a significant attack surface in multi-tenant or federated environments where untrusted algorithms are executed.

Amit Schendel
Amit Schendel
3 views•4 min read
•3 days ago•CVE-2026-47760
8.7

CVE-2026-47760: Cross-Site Scripting (XSS) via SVG Namespace Sanitizer Bypass in TinyMCE

TinyMCE versions 6.8.0 through 7.0.1 contain a high-severity Cross-Site Scripting (XSS) vulnerability. The flaw exists in the custom HTML parser and sanitizer module, which incorrectly manages SVG namespace scopes when parsing nested elements. A low-privileged or unauthenticated attacker can submit a crafted HTML payload containing nested SVG structures to bypass sanitization filters, leading to arbitrary JavaScript execution in the context of the victim's browser session.

Alon Barad
Alon Barad
30 views•7 min read