Cyberhaven is a data loss prevention (DLP) solution that helps companies protect their sensitive data from leaving their control. It works by tracking data as it moves through a company's machines and networks. To offer a comprehensive DLP solution, Cyberhaven integrates with various websites using a browser extension that has 500,000 users according to its Chrome Web Store page.
We discovered and reported a vulnerability in Cyberhaven's browser extension that allowed attackers to steal arbitrary cookies when the victim visited and clicked on a malicious website. The vulnerability has since been patched, but it once again shows that security products are attractive targets for malicious actors.
In this blog post, we will first cover the basics of web browser extensions and their security. We will then investigate the bug and understand how attackers could have abused it. Finally, we will learn how Cyberhaven patched the bug and how you can avoid similar issues in your code.
Impact
We identified the vulnerability in version 24.8.2 of Cyberhaven's browser extension. Since there is no accessible history of browser extensions, we couldn't identify when the vulnerability was first introduced. Cyberhaven notified us on November 8th that they fixed the vulnerability, which corresponds to version 24.9.3 of their extension.
The vulnerability allows an attacker-controlled website to steal any cookie from the victim's browser. The only requirement is that the victim visits the attacker's page. In our proof-of-concept, there is an additional requirement of performing a click on the malicious website, but we assume that there are other exploitation paths were this is not necessary. You can find a demonstration of the successful exploitation below:
Technical Details
Before we go into the details of the browser extension, let's understand what Cyberhaven's software does on a high level. Its purpose is to track data that flows across the user's device to detect and avoid the loss of sensitive company data. To do this, Cyberhaven ships a native agent and a browser extension. The browser extension tracks how data flows inside the browser across websites but also integrates with the native application to be able to track file downloads, clipboard access, etc.
Since browser extensions are just HTML and JavaScript files zipped together, we wanted to give the extension's code a closer look to see if there is any attack surface that a malicious website could target.
Chrome Extension Basics
As a starting point, we can peek into the extension's manifest.json
file:
{
"action": {
"default_popup": "popup.html",
// ...
},
"background": {
"service_worker": "js/worker.js"
},
"content_scripts": [
{
"all_frames": true,
"js": [ "js/apps.so.notion.index.js", "js/browser-polyfill.min.js", ],
"matches": [ "*://*.notion.so/*" ],
"run_at": "document_start"
},
// ...
],
// ...
}
It lists the components, permissions, and other metadata that may interest us. We can see that there are three main components:
- A popup (
popup.html
) - A background script (
js/worker.js
) - Content scripts (
js/apps.*.js
)
The popup page is what's rendered when you click the extension's icon:

The background script always runs in the background and has access to higher-privileged browser APIs. The exact set of APIs depends on the permissions declared in the extension's manifest. For Cyberhaven, we can see the following permissions:
{
// ...
"permissions": [
"alarms",
"tabs",
"downloads",
"webNavigation",
"webRequest",
"storage",
"cookies",
"scripting"
],
"host_permissions": [ "<all_urls>" ],
// ...
}
The extension has access to sensitive information like cookies, and the access is not limited to specific domains since the host_permissions
are set to the wildcard <all_urls>
.
Next to the popup and the background script, there are also so-called content scripts. These run for each web page the extension has access to and can modify and interact with the actual website being loaded. However, they run in a separate JavaScript context from the page's scripts to avoid malicious websites from hijacking the extension. The content script doesn't have access to privileged browser APIs but it can communicate with the background script.
Cyberhaven's Inner Workings
When a user visits a website, Cyberhaven's content script runs before the page's scripts start to execute. Depending on the website, the content script used is either generic (js/apps.common.index.js
) or specialized for that domain. Cyberhaven comes with a series of specialized content scripts for popular domains such as google.com
, github.com
, or dropbox.com
.
In general, the content scripts hook certain website events, such as file uploads or clipboard access. When these actions happen while the user is using the website, they are forwarded to the background script, which in turn forwards them to the native application for logging purposes. The extension also seems to have the capability of blocking certain events, but we weren't able to test this out.
The specialized content scripts have some additional functionality to integrate better with a website, such as identifying the currently logged-in user. How they implement this depends on the website, but for some of them, they get this information from a cookie. In the version of Cyberhaven's extension that we tested, the cookie-based identification was implemented for GitHub, Reddit, and Notion. Let's have a closer look at the GitHub content script:
// ...
onInjectScript(
{
service: "github",
scriptId: "gihub_cloud_data",
scriptUrl: "src/apps/com.github/cloud_data.web.ts"
},
(() => {
proxy("Cyberhaven_WebAppStorage", "webapp-storage"),
proxy("Cyberhaven_WebAppCookies", "webapp-cookies");
})
)
// ...
When the page loads, the content script injects another script into the page. In contrast to the content script, this cloud_data
script runs in the same context as the page's own scripts. The content script then also sets up an event-proxying mechanism that forwards DOM events from the website to the background script.
When a custom DOM event called Cyberhaven_WebAppCookies
is raised on the page, the content script detects it, converts it to a webapp-cookies
message, and sends it to the background script. The background script then handles the event as follows:
onMessage("webapp-cookies", (
async ({data: { key, url }}) => (await browser.cookies.get({
name: key,
url,
}))?.value
)
)
The data from the event is used to query the cookie matching a name and URL. The cookie's value is then returned to the content script, which in turn sends it to the page as a Cyberhaven_WebAppCookies_reply
DOM event. The injected script then processes the response accordingly.
However, since the injected script runs within the website's context, this also means that the website itself can send and receive such DOM events! Since neither the content script's proxy nor the background script checks that the cookie being requested belongs to the currently loaded domain, this essentially allows the website to steal arbitrary cookies from the user!
Stealing Cookies
This capability is of course limited to those pages where Cyberhaven implements such a custom integration, i.e., GitHub, Reddit, and Notion. But what if it's not the website itself that would abuse this functionality, but an attacker-controlled script that runs on the page?
At first glance, this would require an attacker to find a Cross-Site Scripting (XSS) vulnerability in one of the pages, which is a significant hurdle. But if we look at the extension's manifest again, we can see that the integration is loaded for all subdomains of those pages too:
*://*.github.com/*
*://*.githubusercontent.com/*
*://*.github.dev/*
*://*.reddit.com/*
*://*.notion.so/*
To exploit the cookie vulnerability, an attacker just has to find a matching subdomain that contains user-controlled HTML and JavaScript. Since GitHub handles a lot of code-related data, we investigated those domains first and found a good candidate while playing with GitHub Codespaces.
When starting a GitHub Codespace, the integrated IDE runs under <codespace-id>.github.dev
, which matches the third domain pattern. However, all the hosted HMTL and JS are controlled by GitHub, not by the user.
When a user starts a web server inside the codespace, they have the option to expose the server's port to the public:

This creates a domain in the form of <codespace-id>-<port>.app.github.dev
which still matches the subdomain pattern. When anybody visits this domain for the first time, they have to click a button to acknowledge the risk of visiting a user-controlled page that's not part of GitHub:

After clicking the Continue button, all subsequent requests are directly forwarded to the HTTP server running inside the codespace. An attacker could use this to host a simple payload that abuses the cookie fetching mechanism of Cyberhaven's extension to steal any cookie from the victim.
If the attacker wants to make the attack more stealthy, they can use UI redressing to hide the confirmation page. To do this, they can load the target URL in an iframe, place fake UI elements above the iframe to cause the victim to click, and set the pointer-events: none
CSS directive to make clicks go through the fake UI.

Left: Fake UI to cause the user to click.
Right: Showing what's hidden underneath the fake UI.
When an attacker successfully tricks a victim into visiting and clicking on their page, the attack runs silently in the background without the victim noticing what's happening. The attacker can steal any of the victim's cookies from any page, which even works for HTTP-only cookies. These stolen cookies can be used to authenticate as and impersonate the victim.
While the attack requires some user interaction, it is likely possible to find other matching subdomains that can host attacker-controlled pages that require less user interaction.
Patch
To avoid exposing cookies to malicious scripts, the Cyberhaven team decided to rework the user identification mechanism. They switched to methods that don't require cookie access where possible and also restricted the cookie fetching functionality by only allowing certain pages to access their own cookies.
Timeline
Date | Action |
2024-10-08 | We report the vulnerability to Cyberhaven via email |
2024-11-07 | We ask Cyberhaven for an update |
2024-11-07 | We also create a support ticket with Cyberhaven about the vulnerability |
2024-11-07 | Cyberhaven acknowledges the ticket |
2024-11-08 | Cyberhaven thanks us and releases patched versions of the extension |
Summary
In this blog post, we learned how browser extensions work on a high level, and what some of their risks are. When developing an extension yourself, make sure to treat all websites and their content as untrusted!
This vulnerability showed that security products are a double-edged sword. While they try to protect their users, they usually need elevated privileges to do so. This makes them an interesting target for attackers as vulnerabilities have a higher impact.
Finally, we would like to thank the Cyberhaven team for their fast remediation and good communication. Bugs can happen, and how vendors respond to them shows how much they care about security.