# Browser Extension Security

> Browser extension security is controlling what Chrome, Firefox, and Edge extensions can access and verifying what each auto-updated version actually does.

Also known as: Chrome extension security, Chrome extension malware, malicious browser extensions, Firefox add-on security, Edge extension security

Source: https://socket.dev/glossary/browser-extension-security

Last updated: 2026-11-03

Browser extension security (also called Chrome extension security) is the practice of controlling what extensions can access and verifying what their code does. An extension runs inside the browser with permissions a user granted once and updates itself in the background, so a trusted extension can turn into Chrome extension malware in a later version the user never reviewed.

The most important fact in browser extension security is that the update channel, not the install, is where trust breaks. Chrome checks installed extensions for updates [every few hours](https://developer.chrome.com/docs/apps/autoupdate/) and installs new versions without asking. Google's [permission warning guidelines](https://developer.chrome.com/docs/extensions/permission_warnings) describe the one exception: when an update adds a permission that carries a warning, "the extension will be disabled until the user accepts the new permission." Firefox behaves the same way, prompting again only when an update [adds permissions](https://socket.dev/blog/firefox-extension-security). An update that stays inside permissions the user already granted reaches every install silently.

Our position: a permission list is a ceiling on what an extension can do, not a description of what it does. Judge an extension by the code in its current version, and judge it again on every update. The store listing, install count, rating, and badge describe a past version, published by whoever owned the listing at the time.

## How does a browser extension get access to your data?

A browser extension gets access to your data through two fields in its `manifest.json`: `permissions`, which unlock browser APIs, and `host_permissions`, which unlock websites. Chrome, Edge, and Firefox share this format. Firefox moved to the common WebExtensions model [in Firefox 57 in 2017](https://socket.dev/blog/firefox-extension-security).

An extension has three kinds of code. The background service worker runs in the extension's own origin and calls privileged APIs such as `chrome.proxy` or `chrome.cookies`. Content scripts are injected into every page that matches a pattern, and they can read and rewrite that page's DOM. Popups and options pages are the UI. Content scripts run in an isolated world: they share the page's DOM but not its JavaScript heap.

That power is the product. Ad blockers, password managers, translators, and accessibility tools all need to read and rewrite pages they did not write. The browser cannot tell a password manager filling a login form from an [infostealer](https://socket.dev/glossary/infostealer) reading it, because both make the same API calls. Chrome's answer is consent: show a warning at install time and trust the user's decision.

Here is the manifest change that turned the "VPN Go: Free VPN" Chrome extension malicious in version 1.1, as published in our analysis of the [VPN Go clipboard stealer](https://socket.dev/blog/chrome-and-firefox-extensions-free-vpns-add-clipboard-stealers):

```json
{
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "run_at": "document_start",
      "js": ["scripts/version.js"]
    }
  ],
  "permissions": [
    "proxy",
    "storage",
    "webRequest",
    "clipboardRead",
    "webRequestAuthProvider",
    "alarms"
  ],
  "host_permissions": ["<all_urls>"]
}
```

Every line is a legitimate capability for some extension. `<all_urls>` with `document_start` runs `scripts/version.js` on every site before the page's own scripts load. `clipboardRead` lets that script call `navigator.clipboard.readText()`. Chrome's [permissions reference](https://developer.chrome.com/docs/extensions/reference/permissions-list) maps `clipboardRead` to the warning "Read data you copy and paste" and `proxy` to "Read and change all your data on all websites." Nothing in the manifest says the script polls the clipboard every 500 milliseconds and ships each new value to a hardcoded IP address over HTTP. Only the code says that.

## Are browser extensions safe?

Most browser extensions do what they claim, but an extension is only as safe as its current version and its current owner. The user makes one decision, at install time, about a version that may be replaced within hours. Figure 1 shows where each check sits.

> Figure 1. The user is asked once. After that, only a new permission warning stops an update.
>
> The checkpoints an extension update passes in Chrome, and what each one does not check

Figure 1 in text: Chrome checks an extension at four points. At install, it shows a warning for each risky permission, and the user accepts once; this does not check what the code does with the access. Every few hours, it checks for an update that must be signed with the same key; this does not check who holds that key. When an update arrives, Chrome compares its permissions with those already granted: a new warning disables the extension until the user accepts, and no new warning means a silent install; this does not check new behavior inside existing permissions. At runtime, remote configuration, server-side redirects, and C2-delivered modules change behavior with no update at all, and nothing checks them.

VPN Go shows the update path in practice. Chrome version 1.0, published December 22, 2025, behaved like a proxy extension. Version 1.1, published May 31, 2026, added the clipboard stealer. Version 1.3, updated June 14, 2026, kept the stealer and moved to new infrastructure. The Firefox build added the same stealer in version 1.3.3 and polled the clipboard every 1.5 seconds. Under Chrome's rules, adding `clipboardRead` disables the extension until the user accepts a new warning. That prompt is the whole defense, and it asks the user to approve a capability, not to review code.

Ownership changes are invisible. In our [Microsoft Edge extension research](https://socket.dev/blog/edge-extension-security), we noted that users get no notification when a developer sells an extension. The buyer inherits the users, the granted permissions, and the update channel. Of the [19 Chrome and Edge extensions delivering a wallet drainer](https://socket.dev/blog/chrome-edge-extension-wallet-drainer), the threat actor created 14 and bought 5 from legitimate authors. One purchased extension, ["Enable Right Click & Copy — Smart Unlock + OCR,"](https://socket.dev/blog/chrome-edge-extension-wallet-drainer) had around 70,000 Chrome users when the malicious functionality appeared.

Clean first, malicious later is a pattern, not an exception. In the [77 Firefox extensions linked to wallet and credential theft](https://socket.dev/blog/firefox-crypto-wallet-theft), nine confirmed malicious identities had first shipped as harmless sports-score shells. This is the same [supply chain attack](https://socket.dev/glossary/supply-chain-attack) we see on npm and PyPI: compromise the distribution channel and let automatic updates deliver the payload. A vulnerability scanner finds nothing here, because malicious extensions do not get CVEs.

## Which extension permissions matter most?

The extension permissions that matter most grant access to every site, run code in every page, read secrets, or reroute traffic. The table maps each to the warning Chrome shows and to where it appeared in Socket research. Warning text comes from Chrome's [permissions reference](https://developer.chrome.com/docs/extensions/reference/permissions-list).

| Manifest entry | What it grants | Chrome warning | Where we saw it |
| --- | --- | --- | --- |
| host\_permissions: \["\<all\_urls\>"\] or "\*://\*/\*" | Read and modify every page; API access to every site | "Read and change all your data on all websites" | VPN Go 1.1; Socket's Wildcard Host Permission alert is rated High |
| content\_scripts with matches \["\<all\_urls\>"\] and run\_at document\_start | Code in every page, before the page's own scripts | Covered by the all-sites host warning | VPN Go's clipboard poller, scripts/version.js |
| clipboardRead | navigator.clipboard.readText() | "Read data you copy and paste" | VPN Go polled every 500 ms on Chrome, every 1.5 s on Firefox |
| proxy | Route all browser traffic through a chosen server | "Read and change all your data on all websites" | VPN Go's working proxy, which gave users a reason to keep it |
| declarativeNetRequest | Rules that block or rewrite requests and headers | "Block content on any page" | 19 Chrome and Edge extensions stripped CSP headers on every page |
| tabs | URLs and titles of open tabs | "Read your browsing history" (hidden if \<all\_urls\> is also requested) | A Firefox remote-loader cluster requested only storage and tabs |
| scripting | chrome.scripting.executeScript at runtime | None listed | Newer builds of the wallet-drainer framework inject dynamically |
| storage | Persistent key-value storage | None listed | The same framework stores encrypted C2 modules |
| chrome\_settings\_overrides.search\_provider | Replace the default search engine | Not a permission; one prompt at install | 319 Searchjacked extensions, about 13.4 million users |

Read the bottom rows first. `storage` and `scripting` show no warning, yet together they form the loader in our wallet-drainer research. The `tabs` warning does not even appear when an extension also requests `<all_urls>`, according to Google's [permission warning guidelines](https://developer.chrome.com/docs/extensions/permission_warnings). One remote-loader cluster in the 77-extension Firefox campaign requested only `storage` and `tabs`.

The last row needs no code at all. In our [Searchjacked investigation](https://socket.dev/blog/searchjacked), we tracked 319 extensions, installed by roughly 13.4 million users, that capture search queries by replacing the default search engine. Apart from three icon files, this manifest is the entire extension:

```json
{
  "update_url": "https://clients2.google.com/service/update2/crx",
  "manifest_version": 3,
  "version": "1.1",
  "name": "Safe Game Search",
  "short_name": "Game Safe Search",
  "description": "Real-time safety alerts for game searches and risky game websites",
  "default_locale": "en",
  "icons": {
    "16": "icon16.png",
    "48": "icon48.png",
    "128": "icon128.png"
  },
  "chrome_settings_overrides": {
    "search_provider": {
      "name": "Safe Game",
      "keyword": "Safe Game Search",
      "is_default": true,
      "encoding": "UTF-8",
      "favicon_url": "https://safesearchgame.com/favicon.ico",
      "search_url": "https://search.safesearchgame.com/query?q={searchTerms}",
      "suggest_url": "https://suggest.safesearchgame.com/querying/autocomplete?query={searchTerms}"
    }
  }
}
```

There are no permissions, no service worker, no content scripts, and no UI. Every query goes to `search.safesearchgame.com` before it reaches a real search engine. The operator's proxy appends a Yahoo or Bing partner ID to earn referral revenue and chooses the destination per request. For one extension, [Predator Search](https://socket.dev/blog/searchjacked), six identical searches ended in four different places. A permission review scores this extension as harmless, which is exactly the mistake we argue against in that report.

## How malicious extensions get past store review

Store review evaluates a submission at a point in time, and malicious extensions are designed around that. Our research shows four routes:

- Publish a clean version, build an install base, then ship the payload in an update.
- Buy an established extension and push an update to users who already trust it.
- Ship no code at all, as the search hijackers do, and move the behavior to a server.
- Ship a loader, and deliver the payload after review over a [command and control (C2)](https://socket.dev/glossary/command-and-control-c2) channel.

The fourth route matters because Manifest V3 was supposed to close it. Google's [Manifest V3 security changes](https://developer.chrome.com/docs/extensions/develop/migrate/improve-security) say extensions "can no longer execute external logic" with `executeScript()`, `eval()`, or `new Function()`. The same page says "You can still load data remotely at runtime." The ban covers code the extension runs as itself. It does not cover JavaScript that an extension stores as data and then hands to a web page to run.

> Figure 2. The loader framework in 19 Chrome and Edge extensions, reconstructed from Socket's analysis.
>
> How a Manifest V3 extension ran C2-delivered JavaScript without shipping it in any reviewed version

Figure 2 in text: the loader framework in the 19 Chrome and Edge extensions works in eight steps. The C2 server holds JavaScript modules, including a wallet drainer, seed-phrase phishing, and a form grabber. It pushes them over a WebSocket with a 5-minute heartbeat, and it can rotate a victim to a new C2 endpoint. The extension's service worker encrypts each module with AES-GCM and stores it under the `nodes` key in `chrome.storage`. It registers a `declarativeNetRequest` rule that strips the `Content-Security-Policy` header from every page and frame. On a visited page, a content script adds a hidden `<img>`, `<input>`, or `<form>` element with the module as its event handler. It fires the event, so the module runs in the page's main world, and then removes the element. The store received a WebSocket client, a storage write, a header rule, and DOM code. The payload was in none of the shipped versions.

Stripping the [Content Security Policy (CSP)](https://socket.dev/glossary/content-security-policy-csp) is what makes the injection work, because CSP is the page's own defense against injected script. Our analysis found 16 modules in the C2 traffic of one extension. They included a multi-chain wallet drainer, fake Ledger and Trezor recovery wizards, exchange session harvesters, a universal form grabber, browser-history exfiltration, and a fake update that told victims to run an attacker-supplied command.

## How do you check if a browser extension is safe?

You check if a browser extension is safe by comparing what its code does with what its stated purpose needs, and repeating that check on every update. For an individual, the practical version is short. Remove extensions you do not use. For the rest, ask whether the purpose explains each warning: a VPN needs `proxy`, but it has no reason to read your clipboard. Chrome's runtime host permissions [let users withhold site access](https://chromium.googlesource.com/chromium/src/+show/main/extensions/docs/permissions.md) and grant it per site or on click, which applies the [least privilege principle](https://socket.dev/glossary/least-privilege-principle) to extensions that only need one site.

For an organization, the floor is policy. Chrome's [ExtensionSettings policy](https://support.google.com/chrome/a/answer/9867568) can block everything by default, allow named extension IDs, refuse extensions that request specific APIs, and keep extensions away from sensitive sites:

```json
{
  "*": {
    "installation_mode": "blocked",
    "blocked_install_message": "Request extensions through IT."
  },
  "abcdefghijklmnopabcdefghijklmnop": {
    "installation_mode": "allowed",
    "blocked_permissions": ["clipboardRead", "debugger", "proxy"],
    "runtime_blocked_hosts": ["*://*.okta.com", "*://console.aws.amazon.com"]
  }
}
```

The 32-character key stands in for the ID of each approved extension. Settings under `"*"` apply only to extensions without their own entry, so every approved ID carries its own blocks. Listing `blocked_permissions` on an approved ID works as a tripwire: an installed extension that requests a blocked permission does not load, so a future update that adds `clipboardRead` disables the extension instead of shipping silently.

Each control has a cost. `runtime_blocked_hosts` accepts at most 100 entries and discards the rest. Blocking a permission also blocks every legitimate tool that needs it. Most importantly, policy decides which extension IDs may run, not which versions. An approved ID receives every future update that stays inside its allowed permissions, including a malicious one. `minimum_version_required` sets a floor, not a pin. Edge adds a gap of its own: Edge users can install from both the Chrome Web Store and Microsoft Edge Add-ons, where similar tools ship under different extension IDs.

| Control | What it catches | What it misses |
| --- | --- | --- |
| Permission prompt at install and update | New capabilities | New behavior inside granted permissions, remote modules, manifest-only hijackers |
| Store review | Known-bad code at submission | Later updates, purchased listings, payloads fetched after review |
| Allowlist (ExtensionSettings installation\_mode) | Extensions IT never approved | Malicious updates to approved extension IDs |
| blocked\_permissions and runtime\_blocked\_hosts | Named high-risk APIs; access to up to 100 sensitive hosts | Everything not on the lists |
| Per-version code and behavior analysis | Changes in code, permissions, and endpoints between versions | Payloads that never ship in any version, unless the loader is detected |

Verdict: use an allowlist as the floor and per-version code analysis as the gate on updates. Treat the permission prompt and store review as inputs, not security boundaries.

That gate is what Socket's browser extension coverage does. Socket analyzes extensions from the Chrome Web Store, Firefox Add-ons, and Microsoft Edge Add-ons, then compares each release with the previous one under the same extension ID for changes in code, permissions, and network endpoints. When we added Firefox in August 2026, Mozilla's public API listed 97,100 Firefox-compatible extensions; Edge Add-ons had more than 32,000 active extensions when we added Edge. Manifest-level findings surface as alerts such as [Chrome: Wildcard Host Permission](https://socket.dev/alerts/chromeWildcardHostPermission), rated High. As of October 2026, Firefox and Edge coverage is available in Experimental to enterprise customers. Version analysis has a limit too: a payload that only ever exists on a C2 server is caught by detecting the loader, not the payload.

## Takedowns do not cross store boundaries

A takedown in one extension store does not remove the same code from another. When the Chrome Web Store removed "Enable Right Click & Copy," its Edge counterpart, with around 10,000 users, stayed live. The attackers then pushed an Edge update with a new C2 domain on August 14, 2026. Our [Microsoft Edge extension research](https://socket.dev/blog/edge-extension-security) treats each store as a separate distribution channel for that reason.

Cross-store extensions also expose credentials that are not passwords. The [JeetBot Twitch extension](https://socket.dev/blog/malicious-twitch-browser-extension) had about 30,000 Chrome users and 552 Firefox users when we reported it. It forwarded each viewer's Twitch [OAuth](https://socket.dev/glossary/oauth) token as an `&auth=` query parameter on a redirect to the operator's proxy. A bearer token needs no password and no second factor. The developer shipped fixes after publication, Firefox 85.8.7 by September 16, 2026 and Chrome 85.8.9 by September 25, but any install still running older code kept forwarding until it updated.

Browser extensions at least have a permission model. VS Code and Open VSX extensions run with the user's full privileges, which is why we cover [IDE extension security](https://socket.dev/glossary/ide-extension-security) separately.

The open problem is not review quality. It is that the update channel is invisible. Package managers now let teams hold new releases for a set time with a [dependency cooldown](https://socket.dev/glossary/dependency-cooldown). Browsers offer managed fleets no equivalent, no notice when an extension changes owner, and no diff between the version an administrator approved and the version running today. Until they do, the version on the endpoint is the only thing worth judging.
