Is Hola VPN - Your Website Unblocker safe?
Hola VPN captures Chrome-internal identity headers from your Google requests and sends them to its servers every 10 minutes.
When the A/B test activates (affecting approximately 51% of Chrome desktop users), the extension registers a listener on all Google requests and reads privileged headers including X-Client-Data — a unique per-install Chrome fingerprint — along with browser channel, validation token, and user-agent. These are batched and uploaded via POST to client.hola.org every 10 minutes. No user action beyond installing the extension is required for collection to begin.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Hola VPN Harvests Chrome Identity Headers from Google Traffic
Every ten minutes, Hola VPN collects private Chrome headers Google attaches to your requests, including X-Client-Data (a unique install fingerprint), uploaded to Hola's servers.
Gated by an A/B test; starts after install with no prompt.
You install Hola VPN and visit any Google page.
No action beyond install is needed, collection starts automatically once the A/B test activates.
Hola captures private Chrome identity headers from your Google requests and uploads them to its servers every 10 minutes.
X-Client-Data is a unique per-install Chrome fingerprint; Hola collects it without a consent prompt, alongside your browser channel and validation token.
| Field | Value | Why it matters | |
|---|---|---|---|
Chrome Install ID | CJOHywE= | A unique identifier tied to your Chrome install. Google uses it for experiments; Hola collecting it fingerprints your browser. | |
Browser Channel | stable | Identifies whether you are on Chrome Stable, Beta, Dev, or Canary. | |
Browser Validation Token | T/cMGWcFBicTCYvDwxRmMQ== | A cryptographic token Chrome sends to Google to prove the request came from a legitimate Chrome build. | |
User-Agent | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 | Your full browser version string including OS and Chrome version. |
| Content-Type | application/json |
{
"X-Client-Data": "CJOHywE=",
"X-Browser-Channel": "stable",
"X-Browser-Validation": "T/cMGWcFBicTCYvDwxRmMQ==",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
}Chrome_headers class: header collection and upload logic
// Chrome_headers: captures Chrome identity headers from Google requests without a consent prompt
class Chrome_headers {
constructor(conf) {
this.conf = conf;
this.data = {}; // accumulates captured headers between flushes
this.listener = this._listener.bind(this);
}
// Called by A/B gate when test_send_chrome_headers value = 'on'
activate() {
chrome.webRequest.onSendHeaders.addListener(
this.listener,
{ urls: ['*://*.google.com/*'] },
// 'extraHeaders' is required to access privileged Chrome-internal headers
// (X-Client-Data is not accessible to extensions without this flag)
['requestHeaders', 'extraHeaders']
);
}
deactivate() {
try { chrome.webRequest.onSendHeaders.removeListener(this.listener); }
catch (e) { /* ignore */ }
}
// Generator: POSTs accumulated headers to Hola backend every ~10 min
*_send() {
yield be_bg_ajax.ccgi_ajax({
url: conf.url_ccgi + '/chrome_headers?be_ver=' + version(),
method: 'POST',
data: this.data // { 'X-Client-Data': 'CJOHywE=', 'User-Agent': '...', ... }
});
}
}Captured headers are flushed to Hola servers on a recurring 10-minute timer. Each flush uploads the accumulated header data from your Google browsing since the last send.
- client.hola.org
Hola Networks' primary API server. Receives harvested Chrome identity headers via POST /client_cgi/chrome_headers. Historically tied to the Bright Data (Luminati) proxy network.
Google x-client-data install ID and headers forwarded to a Hola server
Browsing *.google.com, Hola VPN reads Chrome-internal headers from outgoing requests, x-client-data, four x-browser-* headers, your User-Agent, then POSTs them to client.hola.org/client_cgi/chrome_headers.
New in 1.253.755.
You load any page on a *.google.com domain.
For example google.com, mail.google.com, or drive.google.com in a normal browsing tab.
The extension reads Chrome's internal request headers off that request and sends them to a Hola server.
It captures x-client-data, x-browser-channel, x-browser-copyright, x-browser-validation, x-browser-year and User-Agent, then POSTs them to client.hola.org/client_cgi/chrome_headers.
| Field | Value | Why it matters | |
|---|---|---|---|
Chrome install ID (x-client-data) | X-Client-Data: CJOHywE= | A short token Chrome attaches only to Google requests. Forwarding it to a third party lets that party tie your separate visits together. | |
Full browser User-Agent | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 | Your exact browser version and platform, with the full Chrome build number spliced in from a Client Hint. | |
Chrome build/validation headers | X-Browser-Channel: stable; X-Browser-Year: 2025 | Four Google-internal headers describing your Chrome channel, copyright year, build year and a validation token. | |
Google location-cookie shape (UULE) | uule_type: "w+CAIQICI", uule_length: 88 | The first 10 characters and total length of your Google UULE cookie, which encodes a location signal, are appended to the payload. |
| Content-Type | application/json |
{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
"X-Client-Data": "CJOHywE="
}The header-collection class in the background service worker
const chrome_header_names = ["x-browser-channel", "x-browser-copyright", "x-browser-validation", "x-browser-year", "x-client-data", "user-agent"];
const google_url_pattern = "*://*.google.com/*";
// _add_listener(): capture matching headers off every outgoing Google request
this.listener = details => {
if (!details.requestHeaders) return;
const headers = {};
for (const hdr of details.requestHeaders) {
if (chrome_header_names.includes(hdr.name.toLowerCase())) {
headers[hdr.name] = hdr.value;
}
}
if (!Object.keys(headers).length) return;
if (ua_full_ver) {
const ua_str = headers["User-Agent"] || headers["user-agent"] || "";
if (ua_str) headers["User-Agent"] = ua_str.replace(/Chrome\/[\d.]+/, "Chrome/" + ua_full_ver);
}
this.data = headers;
this.send();
};
self.chrome.webRequest.onSendHeaders.addListener(
this.listener,
{ urls: [google_url_pattern] },
["requestHeaders", "extraHeaders"]
);
// send(): POST the captured headers to the Hola endpoint
yield E.be_bg_ajax.ccgi_ajax({
url: conf.url_ccgi + "/chrome_headers?be_ver=" + version(),
method: "POST",
data: this.data,
timeout: 10 * ms.SEC
});The server-side A/B flag that gates the feature
// default extension config: collection wiring is present and enabled...
collect_chrome_headers: { enabled: true, interval: 6e5 }, // 600,000 ms = 10 min
// ...but a server-controlled A/B test decides who actually runs it:
test_send_chrome_headers: {
ver: 2,
min_ver: "1.251.527",
value: [[0, 1, "off"], [0, 0, "on"]],
filter: { browser: "chrome" }
}
// gate at update_config():
if (ch_ab.test_send_chrome_headers != "on") ch_conf.enabled = false;- client.hola.org
Receives the POSTed Chrome request headers at /client_cgi/chrome_headers. Operated by Hola Networks Ltd., the extension's publisher.
This collection is controlled by a server-side A/B flag (`test_send_chrome_headers`). In our first dynamic-analysis sessions on a fresh profile, the Hola server assigned the flag to `off` and no headers were collected. After we set the flag to `on` and visited google.com, the collection fired and we captured the POST requests shown above. Because the flag is decided server-side per install, whether any given user is currently in the collecting group is not visible from the extension UI.
Bright Data Affiliate Domains Exempted From the VPN Tunnel
Hola VPN routes some domains DIRECT, outside the tunnel, exposing your real IP.
Version 1.251.527 added bvpn_domains, ten domains tied to Bright Data/Luminati products (earnapp.com, bright-sdk.com), routed like Hola's own infrastructure.
You turn on Hola VPN and visit one of ten Bright Data affiliate sites, such as earnapp.com or bright-sdk.com.
You expect every site to load through the VPN exit node while the VPN is on.
Hola routes that connection DIRECT instead of through the tunnel, so the site sees your real IP address rather than the VPN's.
These ten domains are treated identically to Hola's own infrastructure, which is also exempted from the tunnel.
| Field | Value | Why it matters | |
|---|---|---|---|
earnapp.com | earnapp.com | Bright Data's bandwidth-sharing / passive income product. Reached directly from your real IP. | |
bright-sdk.com | bright-sdk.com | Bright Data's SDK distribution domain. Connections to it leave your VPN tunnel. | |
brightvpn.com | brightvpn.com | BrightVPN consumer product domain, owned by Bright Data / Luminati. | |
brightvideo.tv | brightvideo.tv | Bright Data affiliate media domain, exempted from the tunnel. | |
bright4good.eco | bright4good.eco | Bright Data affiliate domain, exempted from the tunnel. | |
thepiggybox.net | thepiggybox.net | Bright Data affiliate domain, exempted from the tunnel. | |
freemovielibrary.tv | freemovielibrary.tv | Bright Data affiliate media domain, exempted from the tunnel. | |
rewardgum.com | rewardgum.com | Bright Data affiliate rewards domain, exempted from the tunnel. | |
boostnet.info | boostnet.info | Bright Data affiliate domain, exempted from the tunnel. | |
screensavers.app | screensavers.app | Bright Data affiliate domain, exempted from the tunnel. |
How the ten domains get added to the always-DIRECT list, then drive the routing decision.
// js/bg.bg.bundle.js (zurl module), line ~54857
E.bvpn_domains = [
"bright-sdk.com", "brightvpn.com", "earnapp.com", "brightvideo.tv",
"bright4good.eco", "thepiggybox.net", "freemovielibrary.tv",
"rewardgum.com", "boostnet.info", "screensavers.app"
];
function init_hola_domains_re() {
// bvpn_domains are concatenated alongside Hola's own infrastructure domains.
var domains = E.hola_domains
.concat(additional_domains)
.concat(E.hola_browser_internal_domains)
.concat(E.hola_ext_domains)
.concat(E.bvpn_domains); // <-- the new Bright Data list
return domains_re(domains);
}
var hola_domain_re = init_hola_domains_re();
E.is_hola_domain = function(domain) {
return E.is_valid_domain(domain) && domain.search(hola_domain_re) != -1;
};// js/bg.bg.bundle.js, is_vpn_allowed, line ~36527
if (is_all_browser_rule_active() || is_debug_peer_rule_active()) {
return !bg_util.is_private_network(hostname, browser.isInNet)
&& !unblocker_lib.is_agent(hostname)
&& !/zagent\d+\.hola\.org/.test(hostname)
&& (is_debug_peer_rule_active() || !zurl.is_hola_domain(hostname));
// is_hola_domain(hostname) === true => returns false => DIRECT (no VPN)
}- earnapp.com
Bright Data / Luminati consumer bandwidth-sharing product. Reached from the user's real IP.
- bright-sdk.com
Bright Data SDK distribution domain.
- brightvpn.com
BrightVPN consumer product, Bright Data / Luminati.
Reproduces the routing decision using the exact domain lists and regex from the extension's zurl module. Confirms that the ten bvpn_domains are classed as hola domains and get DIRECT (non-VPN) routing, while ordinary third-party sites route through the VPN.
// hola-bvpn-pac-poc.js
// Reproduces Hola VPN's PAC-style routing decision for bvpn_domains.
// Source: bg.bg.bundle.js zurl module (v1.253.755).
const hola_domains = ["hola.org","zspeed-cdn.com","h-vpn.org","holavpn.com","holavpnworld.com",
"holavpnextension.com","holavpninstaller.com","holasof.com","holabrowser.com","holafreevpn.com",
"holavpnrussia.com","hola-vpn.com","holax.io","holavpn.net","holavpnandroid.com"];
// NEW in v1.251.527 — absent from v1.249.511
const bvpn_domains = ["bright-sdk.com","brightvpn.com","earnapp.com","brightvideo.tv",
"bright4good.eco","thepiggybox.net","freemovielibrary.tv","rewardgum.com",
"boostnet.info","screensavers.app"];
const additional_domains = ["haffnetwork\\w{2}.com"];
const hola_ext_domains = ["shoopit.com","toolip.io","oculusproxies.com","browser.ai"];
const hola_browser_internal_domains = ["new-tab-page","hola-new-tab-page","hola-diagnostics","hola-settings","settings"];
function domains_re(arr) {
const domains = arr.join("|").replace(/\./g, "\\.");
return new RegExp("^(.*\\.)?(" + domains + ")$");
}
function init_hola_domains_re() {
return domains_re(hola_domains
.concat(additional_domains)
.concat(hola_browser_internal_domains)
.concat(hola_ext_domains)
.concat(bvpn_domains));
}
const hola_domain_re = init_hola_domains_re();
function is_hola_domain(domain) {
return /^([a-z0-9]([a-z0-9-_]*[a-z0-9])?\.)+[a-z]{2,63}$/.test(domain)
&& domain.search(hola_domain_re) !== -1;
}
// When all-browser VPN mode is active, is_vpn_allowed returns !is_hola_domain(host).
function vpn_allowed(host) { return !is_hola_domain(host); }
console.log("Bright Data domains (expected: DIRECT, real IP exposed):");
for (const d of bvpn_domains)
console.log(` ${d}: vpn_allowed=${vpn_allowed(d)} (${vpn_allowed(d) ? "VPN" : "DIRECT - real IP"})`);
console.log("\nControl third-party sites (expected: VPN):");
for (const d of ["google.com","amazon.com","example.com"])
console.log(` ${d}: vpn_allowed=${vpn_allowed(d)} (${vpn_allowed(d) ? "VPN" : "DIRECT"})`);
- 1Save as hola-bvpn-pac-poc.js.
- 2Run `node hola-bvpn-pac-poc.js`.
- 3All ten bvpn_domains print vpn_allowed=false (DIRECT - real IP); control hosts print vpn_allowed=true (VPN).