RC RANDOM CHAOS

Your VPN extension trusts every website you visit

A hardcoded trigger word in a million-install Chrome VPN extension let any website disable the tunnel, change exit nodes, and read open tabs.

· 7 min read

A researcher typed the string toad into a webpage’s JavaScript. The page then told the user’s VPN extension to disable itself, change servers, exfiltrate the user’s real IP, and read the contents of every tab the browser had open. The extension had over a million installs and a four-star rating. The trigger word was hardcoded. Any site on the open web could send it.

This is what happened to one of the most-installed VPN extensions on the Chrome Web Store, and the bug is worth understanding even if you never installed that particular product. The mistake was generic. Other extensions almost certainly make the same one right now.

What the extension actually did wrong

Browser extensions run in a separate, privileged context from the websites you visit. That separation is the whole point. The extension can do things a webpage cannot: read all your tabs, modify network requests, route your traffic through a proxy, see cookies across origins. A VPN extension needs most of those powers to function.

To bridge the gap between the page and the extension, developers commonly use a content script. The content script runs inside the page but can talk to the extension’s background worker through a private channel (chrome.runtime.sendMessage). The page itself cannot use that channel directly, so developers often pipe messages through window.postMessage, which any script in the page can call.

This is where the discipline matters. The content script must verify the message’s origin, validate its shape, and refuse anything it did not expect. The vulnerable extension did none of those. It listened for any postMessage event and, if the payload contained the string toad, treated the message as a trusted command from its own UI.

The word was a leftover. Probably a debug marker, possibly an internal name for the extension’s settings panel. Once it shipped, it became a master key that any attacker-controlled page could mint.

What an attacker got from one string

The message handler exposed the full command surface the extension used internally. That meant:

  • Turning the VPN off without changing the UI indicator, so the user kept browsing under the assumption the tunnel was up.
  • Changing the exit node to a server the attacker controlled, which lets the attacker observe DNS, see destination IPs, and inject content into unencrypted traffic.
  • Reading the user’s true IP address and geolocation, defeating the entire reason most users install a VPN in the first place.
  • Enumerating open tabs, including the URLs of sites the user was currently on.
  • In some configurations, triggering the extension to fetch arbitrary URLs server-side from the attacker’s chosen vantage point.

None of this required a phishing page, a fake login, or social engineering. A user visiting any compromised ad network, any embedded iframe, any site running attacker-controlled JavaScript would be exposed silently. The user would see no prompt, no warning, no indication that anything had changed.

Why VPN extensions are a uniquely bad place to put this bug

A VPN sits between you and the internet by design. If the VPN lies about its state, the user has no other signal to check. The browser shows the same address bar. The OS shows the same network. The extension icon stays green because the extension itself was told to fake the indicator.

The trust model of a consumer VPN is already weak. You are trading the visibility of your ISP for the visibility of the VPN provider, and you are running their code with broad browser permissions. When that code has a remote command injection in it, you have effectively installed an attacker-controlled man-in-the-middle that activates on any page they choose.

The damage is also persistent across browsing sessions. Most VPN extensions remember their last configuration. If the attacker switches your exit node to their server and you close the browser, the extension may reconnect to the attacker’s server the next time it starts.

The class of bug, not the individual mistake

Replace toad with any magic string. The pattern is the problem. It shows up in three forms across the extension ecosystem:

Unauthenticated postMessage handlers. A content script listens to window.addEventListener('message', ...) without checking event.origin or event.source. Any frame on the page, including ad iframes the user did not knowingly load, can post messages that look identical to the extension’s own UI traffic.

Externally connectable extensions with permissive matches. Manifest V3 lets extensions declare which web origins can call them via chrome.runtime.connect and sendMessage. A wildcard pattern like *://*/* or a long allowlist of unrelated marketing domains turns the extension into a public API.

Debug or telemetry endpoints left enabled in production. A developer-only command channel is gated by a feature flag, the feature flag defaults to off, and the off state is never enforced in a build the user receives. The trigger word toad reads like exactly this kind of artifact.

Any extension you have installed today could have one of these. The Chrome Web Store does not audit for them in any meaningful way.

How to check what you have installed

Open chrome://extensions and look at each extension you have. For every one, ask three questions:

  1. Does it need the permissions it has? An ad blocker probably needs host_permissions: <all_urls>. A unit converter does not. Permissions you cannot justify are attack surface you do not need.

  2. Who publishes it, and is the publisher the same entity as the brand on the icon? Many popular extensions have been quietly sold to companies that monetize them by injecting affiliate codes, harvesting browsing data, or shipping ad fraud. The new owner often keeps the original name and reviews.

  3. When was it last updated? An extension that has not shipped a release in 18 months is either abandoned or stable. You can usually tell which by looking at the support forum.

If you use a VPN, treat the browser extension as the weakest deployment option. A system-level VPN client (WireGuard, OpenVPN, the provider’s desktop app) is harder to compromise from a webpage because it does not expose a browser-accessible command surface. The browser extension exists because it is convenient, not because it is the correct architecture.

What the vendor should have shipped

The fix in this specific case was a single commit: check event.origin against the extension’s own origin, drop messages that do not match, and remove the hardcoded trigger word entirely. That patch took the vendor under 48 hours from disclosure to release. The longer fix is harder.

A VPN extension’s command channel should require a per-session token that the extension generates, stores in its own storage, and shares only with its own UI pages. Messages without the token are rejected. The token rotates on extension restart. This pattern is well documented and rarely implemented in consumer extensions because it adds engineering work and breaks nothing visible when omitted.

The vendor also did not have a security contact listed. The researcher who found the bug spent four days trying to reach a human before posting publicly. That is a normal experience reporting bugs to extension publishers, and it is why coordinated disclosure for browser extensions tends to fail more often than for traditional software vendors.

What to do this week

If you installed a Chrome VPN extension before this disclosure, remove it and reinstall after confirming the publisher has shipped a post-disclosure update. Removing and reinstalling clears any persisted attacker-set configuration. Changing your password on the VPN service is worth doing if the service has accounts; the extension’s stored session token should be considered compromised if the extension was vulnerable during the window the bug was public.

If you build extensions, audit every message listener you have. The check is mechanical: does the handler validate event.origin against a strict allowlist, does it validate the message shape against a schema, and does it require a capability token for any action that touches user data or extension permissions. If any of those three is missing, you have the same class of bug.

If you operate a service that depends on user VPN configuration being honest, stop. The browser extension form factor for VPNs has now had multiple disclosed remote control bugs in the last five years across different vendors. The pattern is structural. Assume the extension lies.

The word toad is funny. The bug is not unusual. The next one is already shipped and waiting for somebody to type it.


#ad Contains an affiliate link.

Share

Keep Reading

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.