Cursor and Windsurf are still running vulnerable Chromium versions, creating a classic supply chain security crisis where patches exist upstream but haven’t reached end users
Chromium isn’t just Google Chrome. It’s the foundation for an entire ecosystem of widely used software: Microsoft Edge, Electron apps like Visual Studio Code (VSCode), and even AI-focused forks like Cursor and Windsurf, a developer IDE gaining rapid popularity.
On August 11, 2025, a critical Chromium bug (CVE-2025-4609) was disclosed – a sandbox escape that can lead to remote code execution (RCE).
This vulnerability earned a $250,000 payout, one of the largest single bug bounties in Chrome’s history. What’s even more interesting is that this significant reward was for a partial exploit, not a complete exploit chain, underscoring the critical nature of sandbox escape vulnerabilities.
A sandbox escape vulnerability is a security flaw that allows an attacker to execute code outside of a restricted, unprivileged environment. This enables them to gain unauthorized access and control over a system beyond the intended security boundaries.
CVE-2025-4609 stems from a flaw in Chromium’s IPC (Inter Process Communication) mechanism called ipcz, that allows a compromised renderer process to gain privileged browser process handles.
This is a textbook supply chain security problem: the patch exists in Chromium, but downstream applications like Cursor and Windsurf have not yet integrated it – leaving developers exposed.
The current fixed version of this CVE is 136.0.7103.113/.114 for Windows and Mac, and version 136.0.7103.113 for Linux
Cursor and Windsurf as of today are running a vulnerable version of chromium that’s affected by CVE-2025-4609:
- Cursor: 132.0.6834.210
- Windsurf: 132.0.6834.210
As can be seen from the Cursor and Windsurf version info:
As of today, both Edge and VSCode have released updated versions that contain the patch for the CVE.
Before we get into the details, here is a visual overview of the vulnerability and the attack paths for Cursor and Windsurf, based on the public information:
Technical Deep Dive: Understanding CVE-2025-4609
Background: Understanding Mojo and Transport Objects
Chromium uses the Mojo IPC system to facilitate secure communication between different processes (browser, renderer, GPU, etc.). The IPCZ driver serves as the underlying transport mechanism, handling the serialization and deserialization of objects passed between processes.
The Transport class in transport.cc (mojo\core\ipcz_driver\transport.cc) is responsible for managing communication endpoints and ensuring proper security boundaries between trusted and untrusted processes.
This vulnerability assumes an attacker has already compromised the Chromium renderer. By exploiting this flaw, they can elevate the renderer’s privileges to execute commands on the victim’s Windows machine.
The Vulnerability: Trust Boundary Bypass
The vulnerability stems from insufficient validation in the Transport::Deserialize function. Let’s examine a part of the vulnerable code:
| scoped_refptr<Transport> Transport::Deserialize( Transport& from_transport, base::span<const uint8_t> data, base::span<PlatformHandle> handles) { if (data.size() < sizeof(TransportHeader) || handles.size() < 1) { return nullptr; } |
As can be seen, Transport::Deserialize can be used for creating a transport while using the header.destination_type residing inside the Transport& from_transport used in this function.
In the version before the patch, there was no checking on whether or not the header.destination_type parameter was passed while creating the Transport object.
Meaning, that by injecting into Transport::Serialize, inside our path, we can manipulate the header.destination_type value to be kBroker as follows:
| + if (InRendererProcess()) { + LOG(ERROR) << “renderer process send transport”; + header.destination_type = kBroker; + } |
It is important to emphasize that the broker is responsible for managing the communication between processes, and that the broker has higher privileges than the current renderer process. Due to this, the only allowed broker referenced from the renderer should be of type kNonBroker, but this type check wasn’t made in the vulnerable version.
When passing the destination type value, it was possible for a renderer process to inject this header while creating the Transport.
This header is important, since while looking into the file transport.cc we will find DecodeHandle:
| std::optional<PlatformHandle> DecodeHandle(HandleData data, const base::Process& remote_process, HandleOwner handle_owner, Transport& from_transport) { const HANDLE handle = DataToHandle(data); if (handle == nullptr) { return std::nullopt; } // Do not decode sentinel values used by Windows (INVALID_HANDLE_VALUE & // GetCurrentThread()). if (base::win::IsPseudoHandle(handle)) { return std::nullopt; } if (handle_owner == HandleOwner::kRecipient) { if (from_transport.destination_type() != Transport::kBroker && !from_transport.is_peer_trusted() && !remote_process.is_current()) { // Do not trust non-broker endpoints to send handles which already belong // to us, unless the transport is explicitly marked as trustworthy (e.g. // is connected to a known elevated process.) return std::nullopt; } |
From analyzing the function above, we understand that it was possible to bypass the security check of from_transport.destination_type(). By setting the destination type to kBroker, the condition from_transport.destination_type() != Transport::kBroker evaluates to False. Due to the AND logic in the if statement, this prevents the function from returning std::nullopt, thereby allowing the attacker to later duplicate privileged handles from the browser process.
Before starting to explain about the flow of the exploit itself, we will follow the intended usage:
While the renderer requests Transport to another node, the Broker creates the transport pair and each node gets one endpoint.
While making use of Transport::Serialize, the header.destination_type is a legitimate one, for example: kNonBroker
Then the Transport::Deserialize making use of the source and destination while creating the transport at:
| auto transport = Create({.source = from_transport.source_type(), .destination = header.destination_type}, PlatformChannelEndpoint(std::move(handles[0])), std::move(process), from_transport.remote_process_trust()); transport->set_is_peer_trusted(is_new_peer_trusted); transport->set_is_trusted_by_peer(is_trusted_by_peer); |
While researching the vulnerable version (before the patch), a node was able to send a RequestIntroduction with it self’s node name, while making use of node_link.cc (third_party\ipcz\src\ipcz\node_link.cc):
| void NodeLink::RequestIntroduction(const NodeName& name) { ABSL_ASSERT(remote_node_type_ == Node::Type::kBroker); msg::RequestIntroduction request; request.v0()->name = name; Transmit(request); } |
Which led it to get both transport1 and transport2 endpoints. This leads to the fact that the same node controls both sides of the communication.
Since the renderer controls both transport1 and transport2, the renderer will be able to make use of transport1, while setting header.destination_type = kBroker when sending a ReferNonBroker request.
Since no check was made in the Transport::Deserialize function (which was later introduced in the patch):
| if (header.destination_type == kBroker && !is_source_trusted) { // Do not accept broker connections from untrusted transports. return nullptr; } |
It was possible to use such header, in order to send RelayMessage while requesting privileged handles since the Deserialized Transport object is being able to bypass the checks:
| if (handle_owner == HandleOwner::kRecipient) { if (from_transport.destination_type() != Transport::kBroker && !from_transport.is_peer_trusted() && !remote_process.is_current()) { // Do not trust non-broker endpoints to send handles which already belong // to us, unless the transport is explicitly marked as trustworthy (e.g. // is connected to a known elevated process.) return std::nullopt; } |
This makes it possible to send the RelayMessage multiple times with different handle values ranging from 4 to 1000, resulting in the browser process returning any handle whose value is successfully hit during the brute force process. Starting from 4 stems from the fact that in Windows, the previous values (0-3) are reserved sentinel values and cannot be used as valid handles, therefore they are excluded from the brute force attack.
While getting the privileged browser process handle, it was possible to escape the sandbox and get a Remote Code Execution (RCE) while making use of this highly privileged handle.
The Fix: Trust Validation
The vulnerability was patched by adding proper validation in the Transport::Deserialize function:
| if (header.destination_type == kBroker && !is_source_trusted) { // Do not accept broker connections from untrusted transports. return nullptr;} |
This fix ensures that only trusted sources can create transports with destination_type = kBroker, effectively closing the privilege escalation path.
Attack Flow Summary
- Initial Setup: Renderer process sends RequestIntroduction with its own node name
- Transport Control: Obtains control of both transport endpoints
- Header Injection: Modifies destination_type to kBroker during serialization
- Deserialization: Malicious transport is created without proper validation
- Handle Acquisition: Uses the fake broker transport to bypass security checks
- Privilege Escalation: Obtains privileged browser process handles
- Sandbox Escape: Leverages privileged handles for remote code execution
Summary
This vulnerability demonstrates the critical importance of validating trust boundaries in IPC systems. The attack exploited a seemingly minor oversight in input validation that had severe security implications. The fix, while simple, effectively prevents untrusted processes from masquerading as trusted brokers.
How to protect yourself
To maintain a secure development environment, follow these guidelines:
- Avoid Untrusted Deep Links: Do not click on deep links from untrusted sources, particularly those for Cursor and Windsurf (e.g., cursor:// or windsurf://). These links can automatically open applications and may pose a security risk.
- Verify Links and Repositories: Exercise caution with all links and repositories. Never clone or work on code from unknown or untrusted sources. Always verify the legitimacy of a source before interacting with it.
- Software Updates: Keep your Integrated Development Environment (IDE) and web browsers updated to the latest versions. This helps patch known security vulnerabilities. This is a general recommendation, however, until Cursor and Windsurf update their versions this is not relevant. (Disclaimer: This is a general best practice; however, until Cursor and Windsurf update their versions, it will not help in this case)


