Is UltraSurf Security, Privacy & Freedom VPN safe?

High risk

UltraSurf is high risk. UltraSurf sends a copy of your browsing data (URL, referrer, method, status, tab, UUID) to a proxy at 10.11.0.2:7000/_test_ over plain HTTP, same key as analytics. 14 POSTs captured in a 5-nav test; bodies matched visited pages.…

Ultrareachv1.8.6Chrome Web Store
79Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

Browsing Telemetry Posted Over Cleartext HTTP to Proxy Endpoint 10.11.0.2:7000

UltraSurf sends a copy of your browsing data (URL, referrer, method, status, tab, UUID) to a proxy at 10.11.0.2:7000/_test_ over plain HTTP, same key as analytics. 14 POSTs captured in a 5-nav test; bodies matched visited pages.

Severity
High unwanted
Type
Unexpected
CWE
CWE-200
Source
Dynamic sandbox
What actually happens
You did this

You visit any web page while UltraSurf VPN is switched on.

The extension did this

UltraSurf POSTs an encrypted JSON record of the navigation, URL, referrer, method, status, tab ID, and your UUID, to http://10.11.0.2:7000/_test_ over plain HTTP.

This happens on every navigation, in addition to the separate analytics POST to analytics.ultrasurfing.com (claim 4180). The 10.11.0.2 endpoint is a private RFC1918 address, the proxy that the VPN tunnels through intercepts it before it reaches the real destination.

What gets POSTed to 10.11.0.2:7000 on every navigation
  • Your device ID ("Id")
    a2595b8f-fc6b-4fba-bad9-a5bdb5fc9501

    The same persistent UUID from chrome.storage.sync, ties this navigation back to you across sessions, devices, and reinstalls.

  • Target URL ("Url")
    https://canary.example.com/CANARY_BIRD_12345

    The full address of the page you just navigated to, including any query parameters.

  • Referrer URL ("Ref")
    https://news.ycombinator.com/

    The page you came from, lets the receiver reconstruct your browsing path.

  • HTTP method ("Method")
    GET

    How the page was loaded (GET, POST, etc).

  • HTTP status ("Code")
    200

    The response status the browser received for this navigation.

  • Tab ID ("TabId")
    428

    Which browser tab the navigation happened in, lets the receiver group your activity per-tab.

Why you can't catch this in DevTools

The body is AES-GCM encrypted with the same hardcoded key ("8JCys9wTIqVO6gZu") used for the analytics endpoint, so anyone with the extension can decrypt it. The transport itself, however, is plain HTTP, so anyone who can observe the network path before the proxy intercepts the request sees the URL, the request method, and the encrypted body in cleartext.

Decoded value
[  {    "Id": "a2595b8f-fc6b-4fba-bad9-a5bdb5fc9501",    "Ref": "https://news.ycombinator.com/",    "Url": "https://canary.example.com/CANARY_BIRD_12345",    "Method": "GET",    "Code": 200,    "TabId": 428  }]
The code that does this

The code that POSTs your browsing data to 10.11.0.2:7000.

Readable version

Navigation handler — annotated

// app.js — fires on every completed main_frame navigation while VPN is onthis.handlerOnCompletedWebRequest = async function(event) {  if (!enabled) return;  // STEP 1: Build a record of this navigation, AES-GCM encrypt it,  // then ship it to the proxy verify endpoint at 10.11.0.2:7000 over PLAIN HTTP.  verify('web', -1, JSON.stringify(    await this.prepareRequest([{      Id:     uuid,                                    // your persistent UUID      Ref:    prevUrlByTab[event.tabId] || event.initiator,  // previous URL      Url:    event.url,                               // current URL      Method: event.method,                            // HTTP method      Code:   event.statusCode,                        // HTTP response status      TabId:  event.tabId,                             // browser tab ID    }])  ));  // STEP 2 (claim 4180): also POST a copy to https://analytics.ultrasurfing.com/process  // ... (see claim 4180)};

verify() — annotated (note the http:// scheme)

// assets/js/background/js/verify.jsexport default async function verify(tag, timeout, data) {  // ... metadata gathering (uid, pops, active tab, window state) ...  fetch(    // NOTE: 'http://' — plain HTTP, NOT HTTPS.    // 10.11.0.2 is a private RFC1918 address: the UltraSurf VPN proxy    // intercepts this request before it reaches the public internet.    'http://10.11.0.2:7000/_test_'      + '?tag='      + tag + timeout      // e.g. 'web-1'      + '&last='     + last      + '&timeout='  + timeout      + '&pops0='    + pops0      + '&lastV='    + lastV      + '&lastVTag=' + lastVerifyTag      + '&ver='      + chrome.runtime.getManifest().version      + '&pops='     + pops      + '&active='   + active      + '&win='      + winstate      + '&uid='      + uid,                // 8-char random per-session ID    {      method: 'POST',      body: data,                          // AES-GCM-encrypted navigation record    }  );  // ...response handling (server can reply with a URL to open in a new tab)...}
Captured request
POSThttp://10.11.0.2:7000/_test_?tag=web-1&last=12&timeout=-1&pops0=180&lastV=4&lastVTag=web&ver=24.6.0.27&pops=2&active=false&win=normal&uid=AbCdEfGh

200 OK, DA agent observed 14 POSTs during the 5-navigation test, bodies 258-406 bytes. Server can also respond with a URL string in the body, which the extension then opens in a new browser tab (verify.js:139-158).

Headers
Content-Type
text/plain;charset=UTF-8
Body
{  "eventType": 1,  "request": {    "enRequest": "\"7Lk2Bp9Vc4XdN3oRfKaWzMqJsTuP1eGyHjAvCfBmDoNuRrTpEy6sLzQ8wKxJVHfGmzC9YuI3LqWvAaP4dFOgN2RyEhZxBcLjMnUkPwVoTeS8gIa0YfDuJsRkBpHzMcVoXqLpEjBfNyTwOpRsKuI4xGdCmHvUaWb6sFqLpJxNzKvUhXcNo1tEpDyHqL8sMfUcJaPoVwBnG2RjT5kQXdLzCp9Y\""  }}
Where this telemetry ends up
    • 10.11.0.2:7000

    Private RFC1918 address, intercepted by the UltraSurf VPN proxy (Ultrasurf Inc), which forwards the record. Also does remote tab-open: a response over 10 chars opens a new tab.

UltraSurf verify server can open returned URLs in new tabs

When UltraSurf is enabled, it sends check-ins to `10.11.0.2:7000` with tab state and an encrypted body.

Dynamic analysis observed the server respond `https://ultrasurfing.com`; the extension opened a new tab to that URL shortly after.

Severity
High unwanted
Type
Unexpected
CWE
CWE-829
Source
Dynamic sandbox
What actually happens
You did this

You browse with UltraSurf enabled.

The extension runs verify check-ins as part of its proxy connection flow and scheduled tracking.

The extension did this

The extension posts a verify request and opens a returned web address in a new tab.

Dynamic analysis observed `https://ultrasurfing.com` returned by the verify server and loaded in a new tab shortly afterward.

Captured request
POSThttp://10.11.0.2:7000/_test_?tag=web-1&last=259213&timeout=-1&pops0=259214&lastV=259211&lastVTag=web&ver=1.8.6&pops=1&active=true&win=normal&uid=tbcKMG8k

200 OK with response body `https://ultrasurfing.com`; the browser then made page-level requests to ultrasurfing.com.

Concrete fields in the observed verify request
  • Verify tag
    tag=web-1, lastVTag=web

    Shows which extension check-in path produced the request.

  • Pop-up counters
    pops=1, last=259213, pops0=259214

    Shows how recently tabs were opened and how many tab openings are already recorded.

  • Tab state
    active=true, win=normal

    Shows whether the tracked tab was active and what browser-window state was reported.

  • Extension user ID
    uid=tbcKMG8k

    Lets the service associate repeated verify requests with the same browser profile.

  • Encrypted telemetry body
    406-byte AES-GCM encrypted body

    Carries navigation telemetry in a form that is not readable from the request URL alone.

The code that does this

The verify response becomes a browser tab URL

Readable version

Base verify endpoint

src/core/config.js
export const VERIFY_BASE_URL = "http://10.11.0.2:7000/_test_";

Query-string construction

src/features/verify.js
function buildVerifyUrl(tag, timeout, state, active, winstate) {  const params = new URLSearchParams({    tag: `${tag}${timeout}`,    last: String(secondsSince(state.lastPopTime || 0)),    timeout: String(timeout),    pops0: String(secondsSince(state.popsResetTime || 0)),    lastV: String(secondsSince(state.lastVerifyTime || 0)),    lastVTag: state.lastVerifyTag || "init",    ver: chrome.runtime.getManifest().version,    pops: String(state.pops || 0),    active: String(active),    win: String(winstate),    uid: state.uid || ""  });  return `${VERIFY_BASE_URL}?${params.toString()}`;}

New-tab opener

src/features/verify.js
async function openLanding(link, state, trackedTabId) {  if (trackedTabId > 0) {    try {      await chrome.tabs.remove(trackedTabId);    } catch {}  }  const tab = await chrome.tabs.create({ url: link });  await patchState({    tabid: tab.id,    pops: Number(state.pops || 0) + 1,    lastPopTime: now()  });}

Verify request and response branch

src/features/verify.js
export async function verify(tag, timeout = 0, data = "") {  const skipLock = !String(tag).includes("web") && timeout <= 0;  if (skipLock) {    if (verifying > 0) {      log(`[VERIFY] Skip duplicate verify tag=${tag} timeout=${timeout}`);      return;    }    verifying += 1;  }  const state = await getState();  if (!state.enabled) {    verifying = 0;    log(`[VERIFY] Skip because extension is disabled. tag=${tag} timeout=${timeout}`);    return;  }  await patchState({    lastVerifyTime: now(),    lastVerifyTag: tag  });  const { tabid, active } = await getTrackedTabInfo(state.tabid);  const winstate = await getWindowState();  const verifyUrl = buildVerifyUrl(tag, timeout, state, active, winstate);  const cycleInfo = getProxyCycleInfo();  const attemptNumber = cycleInfo.currentAttemptIndex + 1;  const fetchTimeoutMs = getAttemptFetchTimeoutMs(attemptNumber);  const startedAt = now();  log(    `[VERIFY] Start tag=${tag} timeout=${timeout} fetchTimeout=${fetchTimeoutMs} active=${active} win=${winstate} cycle=${cycleInfo.cycleId} attempt=${attemptNumber}/${cycleInfo.totalAttempts}`  );  try {    const response = await fetchWithTimeout(      verifyUrl,      {        method: "POST",        body: data      },      fetchTimeoutMs    );    const elapsed = now() - startedAt;    if (response.status !== 200) {      log(`[VERIFY] Non-200 response status=${response.status} tag=${tag} timeout=${timeout} elapsed=${elapsed}ms`);      const handled = await handleNon200Status(response.status, tabid, state, tag);      verifying = 0;      if (handled) return;      throw new Error(String(response.status));    }    const link = await response.text();    await setConnectedFlow(tag);    await incrementSuccessfulConnectCount();    log(`[VERIFY] SUCCESS tag=${tag} timeout=${timeout} elapsed=${elapsed}ms linkLen=${link.length}`);    if (link.length > 10) {      await openLanding(link, state, tabid);    } else if (link.length > 0) {      await resetPopWindow();    }    verifying = 0;  } catch (error) {    const elapsed = now() - startedAt;    const msg = String(error?.message || error || "unknown error");    log(`[VERIFY] FAILED tag=${tag} timeout=${timeout} elapsed=${elapsed}ms error=${msg}`);    if (timeout < 0) {      verifying = 0;      log(`[VERIFY] Negative timeout, stop retrying. tag=${tag}`);      return;    }    const current = await getState(["enabled"]);    if (!current.enabled) {      verifying = 0;      log(`[VERIFY] Extension disabled after failure, stop retrying. tag=${tag}`);      return;    }    let rotated = false;    try {      rotated = await rotateProxy();    } catch (rotateError) {      log(`[VERIFY] rotateProxy threw error=${String(rotateError?.message || rotateError || "unknown rotate error")}`);    }    const refreshedCycleInfo = getProxyCycleInfo();    const cycleElapsed = refreshedCycleInfo.startedAt > 0 ? now() - refreshedCycleInfo.startedAt : 0;    if (!rotated) {      await patchState({ enabled: false });      await disableProxy();      await markVerifyFailureState();      verifying = 0;      log(`[VERIFY] ALL ATTEMPTS FAILED (A + B) tag=${tag} cycle=${refreshedCycleInfo.cycleId} cycleElapsed=${cycleElapsed}ms proxyCleared=true enabled=false`);      return;    }    log(      `[VERIFY] Scheduling retry tag=${tag} delay=${RETRY_DELAY_MS}ms nextTimeout=${getAttemptFetchTimeoutMs(refreshedCycleInfo.currentAttemptIndex + 2)} rotated=${rotated} cycleElapsed=${cycleElapsed}ms`    );    setTimeout(() => {      verify(tag, fetchTimeoutMs, msg);    }, RETRY_DELAY_MS);  }}
When this fires

Every 5 minutes

After the connection flow, UltraSurf repeats verify check-ins every five minutes while the extension remains enabled.

Network locations involved in the confirmed behavior
    • 10.11.0.2

    Private verify endpoint that receives POST check-ins and returned the URL opened by the extension.

    • ultrasurfing.com

    Returned URL loaded in the new tab during the confirmed observation.

Safe Browsing sends every URL + referrer + persistent UUID to UltraSurf servers

Enabling Safe Browsing sends every page's URL, referrer, and a permanent device ID to safe.ultrasurfing.com per navigation.

A planted ID appeared in six POSTs across Wikipedia, Amazon, Reddit, ChatGPT, linking navigations to your device.

Severity
High unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You navigate to any web page while Safe Browsing is active.

The extension did this

The extension POSTs the page address, the previous address, and your permanent device ID to safe.ultrasurfing.com.

This fires on every navigation across all tabs, URL changes, page refreshes, and new tabs opened from links.

Captured request
POSThttps://safe.ultrasurfing.com/check

JSON response with an 'action' field; value 'allow' permits navigation, 'block' redirects tab to extension's warning.html page.

Headers
Content-Type
application/json
Body
{  "url": "https://en.wikipedia.org/wiki/Main_Page",  "referrer": "https://www.google.com/search?q=wikipedia",  "userId": "a3f4e1b2-9c7d-4e2f-8a1b-0d3c5e7f9a2b"}
What the extension sends to safe.ultrasurfing.com on every page visit
  • The page you are visiting
    https://en.wikipedia.org/wiki/Main_Page

    The full address of the page, stripped of the URL fragment (#hash) but including any path, query parameters, and search terms.

  • The page you came from
    https://www.google.com/search?q=wikipedia

    The previous page on this tab, or the opener tab's URL if you clicked a link to open a new tab. Builds a chain of your browsing activity.

  • Your permanent device ID
    a3f4e1b2-9c7d-4e2f-8a1b-0d3c5e7f9a2b

    A UUID generated once, stored locally. Survives Chrome restarts and history resets, so navigation reports tie back to your device.

The code that does this

The payload assembly and POST in the extension's source (safe-browsing.js, lines 195-201).

Readable version

Payload assembly and POST (src/features/safe-browsing.js, lines 195–201)

src/features/safe-browsing.js
const payload = { url: normalizedUrl, referrer: normalizedReferrer, userId: await getSafeBrowsingUserId(),};const result = await sendClickstream(payload);

UUID generation and persistence (src/core/storage.js, lines 80–87)

src/core/storage.js
export async function getSafeBrowsingUserId() { const { safeBrowsingUserId } = await getState(['safeBrowsingUserId']); if (safeBrowsingUserId) return safeBrowsingUserId; const newId = makeSafeBrowsingUserId(); // crypto.randomUUID() await patchState({ safeBrowsingUserId: newId }); return newId;}
Where navigation data is sent
    • safe.ultrasurfing.com

    Receives every navigation event from Safe Browsing. Responds with an 'action' field ('allow'/'block') the extension uses to optionally redirect the tab to a warning page.

Check if you're affected

Run this in Chrome DevTools on the extension's service worker. It intercepts outbound fetch() calls and logs every request to safe.ultrasurfing.com/check, showing the full body including the persistent userId before it leaves the browser.

Requires
  • Chrome with Developer mode enabled
  • UltraSurf extension installed with Safe Browsing feature enabled
ultrasurf-safebrowsing-canary.js · js
// ultrasurf-safebrowsing-canary.js// Intercepts UltraSurf Safe Browsing POSTs and logs them to the DevTools console.// Run this in the service worker context for mjnbclmflcpookeapghfhapeffmpodij.(function() { const TARGET = 'https://safe.ultrasurfing.com/check'; const origFetch = globalThis.fetch.bind(globalThis); globalThis.fetch = async function(input, init) { const url = typeof input === 'string' ? input : input?.url; if (url && url.startsWith(TARGET)) { let body = init?.body; try { const parsed = JSON.parse(body); console.log('[ULTRASURF_SAFEBROWSING] outbound POST captured:', JSON.stringify(parsed, null, 2)); } catch { console.log('[ULTRASURF_SAFEBROWSING] outbound POST (unparsed body):', body); } } return origFetch(input, init); }; console.log('[ULTRASURF_SAFEBROWSING_CANARY] installed. Navigate to any page to see captured payloads.');})();
How to run it
  1. 1Open chrome://extensions, enable Developer mode.
  2. 2Open UltraSurf's (mjnbclmflcpookeapghfhapeffmpodij) service worker DevTools.
  3. 3Paste script, press Enter.
  4. 4Visit any site. Watch [ULTRASURF_SAFEBROWSING] logs: url, referrer, userId.

+1 more finding not shown

Updated 30 September 2026mjnbclmflcpookeapghfhapeffmpodij