Browser Extension Security Testing: Manifest V3, Content Scripts, and Privilege Escalation
Browser extensions sit at a uniquely privileged position: they run inside the user's browser with access to all page content, cookies, network requests, and browser APIs — across every site the user visits. Enterprise environments commonly deploy extensions for SSO, endpoint security, productivity, and compliance. When a browser extension has a security vulnerability, the blast radius is the user's entire browser session: every site they're authenticated to, every password they've typed, every document they've viewed.
This guide covers how to security test browser extensions — whether you're assessing an internal extension your company ships, auditing a third-party extension before enterprise deployment, or doing a full pentest engagement that includes the browser client.
Extension Architecture: The Three Layers
A browser extension operates in three isolated contexts, each with different permissions:
| Context | Runs In | Access | Security Risk |
|---|---|---|---|
| Background (Service Worker in MV3) | Isolated extension context | All extension APIs; no DOM access | Privilege escalation target; persistent state |
| Content Script | Page context (isolated world) | Page DOM; limited extension APIs | XSS injection target; data exfiltration |
| Extension Page (popup, options) | Extension origin | Full extension APIs; no page DOM | Clickjacking; XSS in extension pages |
The content script is injected into web pages but runs in a separate JavaScript world — it cannot directly access page JavaScript variables, but it shares the DOM. The key attack surface is communication between these layers via message passing.
Step 1: Manifest Audit
Start with manifest.json. It declares every permission the extension holds and every host it can access.
# Extract extension from Chrome
# chrome://extensions > Developer mode > Pack extension / Export crx
# Or find installed extensions at:
# macOS: ~/Library/Application Support/Google/Chrome/Default/Extensions/
# Linux: ~/.config/google-chrome/Default/Extensions/
# Windows: %LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions\
# For a .crx file, rename to .zip and extract
unzip extension.zip -d extension-unpacked/
cat extension-unpacked/manifest.json | python3 -m json.tool
High-risk permissions to flag:
| Permission | What It Allows | Risk Level |
|---|---|---|
<all_urls> or *://*/* |
Content script injection and network access to all sites | Critical |
cookies |
Read and modify all cookies across all origins | Critical |
webRequest (MV2) / declarativeNetRequest (MV3) |
Intercept and modify network requests | High |
history |
Access full browsing history | High |
tabs |
Access URL and title of all open tabs | High |
nativeMessaging |
Communicate with native applications on the OS | High |
storage |
Access extension's persistent storage | Medium |
Content Script XSS
Content scripts run in the page's DOM context. If a content script reads data from the page and renders it without sanitization, an attacker who controls the page content can execute JavaScript in the extension's content script context.
// Vulnerable content script pattern
const title = document.querySelector('.product-title').textContent;
document.getElementById('extension-tooltip').innerHTML = `<b>${title}</b>`; // XSS if title contains HTML
// Exploit: malicious page sets title to:
// <img src=x onerror=chrome.runtime.sendMessage({action:'steal-cookies'})>
The content script itself doesn't have access to sensitive APIs (cookies, tabs), but it can send messages to the background service worker that does. A content script XSS can be escalated by sending messages to the background.
Message Passing Vulnerabilities
Extension components communicate via chrome.runtime.sendMessage (one-way) and chrome.runtime.connect (persistent port). The background service worker must validate the sender of every message it processes.
Insufficient Sender Validation
// Vulnerable background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'fetchAndReturn') {
// No sender validation - any content script on any page can call this
fetch(message.url).then(r => r.text()).then(sendResponse);
return true;
}
});
// Attacker's page with content script access:
chrome.runtime.sendMessage({
action: 'fetchAndReturn',
url: 'https://internal.corp.com/admin/users'
}, response => console.log(response));
The background has access to all network origins (if <all_urls> is declared) and can bypass CORS. A fetch from the background is not subject to the page's CORS policy. This turns a vulnerable extension into a CORS bypass for any origin the extension can reach.
External Message Handling
Extensions can declare externally_connectable to allow specific websites to send messages directly — without needing a content script. If the allowed origins are too broad:
"externally_connectable": {
"matches": ["*://*.example.com/*"] // All subdomains — subdomain takeover can exploit this
}
If a subdomain of the allowed domain is vulnerable to takeover, an attacker can host a page there that sends privileged messages to the extension.
Privilege Escalation via Background Access
The background service worker (MV3) or background page (MV2) has the highest privilege level. Getting code execution here gives access to all declared browser APIs. Common paths:
Content Script → Background Message Escalation
Chain a content script XSS (from a malicious page) with an insecure message handler in the background to execute privileged operations:
- Attacker controls a page matching the extension's
content_scripts.matches - Attacker's page triggers content script XSS via a crafted DOM element
- Injected code sends a message to the background worker requesting a privileged action
- Background executes without proper sender validation
Extension Page XSS
Extension pages (popup, options) run at the extension's origin. XSS in an extension page executes with full extension privileges — including access to all declared APIs. Common sources:
- Extension options page that renders user-controlled configuration values without sanitization
- Popup that displays page content (title, URL) without HTML escaping
- Extension page that loads remote content from the extension's update server
Manifest V3 Security Changes
Chrome's Manifest V3 (MV3) introduced several security improvements over MV2, but also new attack surface:
| Change | Security Impact |
|---|---|
| Service workers replace background pages | No persistent DOM; harder to maintain long-lived malicious state |
eval() prohibited in extension context |
Prevents dynamic code execution in background; content scripts can still use eval |
| Remote code execution restricted | Extensions cannot load and execute remote JavaScript; reduces drive-by supply chain attacks |
declarativeNetRequest instead of webRequest |
Rules are static; no arbitrary request interception/modification in MV3 |
| Host permissions separated from API permissions | Users can restrict host access at install time; doesn't eliminate privilege risks |
MV3 does not eliminate XSS in content scripts, insecure message passing, or overly broad permissions. It primarily raises the bar for supply chain attacks on extensions.
Sensitive Data Storage
Extensions often store authentication tokens, API keys, or user credentials. Test what's stored and how:
// Inspect extension storage from DevTools console (in extension page context)
chrome.storage.local.get(null, items => console.log(JSON.stringify(items)));
chrome.storage.sync.get(null, items => console.log(JSON.stringify(items)));
// Or via automated test
// In content script context, send message to background requesting storage dump
Key findings to look for:
- Authentication tokens stored in
chrome.storage.localwithout encryption (any page content script can request them via messages) - Credentials stored in
localStorageof extension pages (accessible if extension pages are XSS-vulnerable) - Sensitive values logged to the browser console
- API keys embedded in the extension source code
Native Messaging Security
Extensions with the nativeMessaging permission can communicate with native applications installed on the OS. Vulnerabilities in the native messaging host can be exploited via the extension:
- If the native host processes attacker-controlled data without validation, command injection is possible
- The native host runs with the user's OS privileges — exploitation means OS-level code execution
- Verify that the native messaging host validates the extension ID of messages it receives
Testing with DevTools
# Load unpacked extension in developer mode
# chrome://extensions > Developer mode > Load unpacked
# Open DevTools for the background service worker
# chrome://extensions > [extension] > "service worker" link
# Open DevTools for a specific extension page
# Right-click popup > Inspect
# Intercept content script messages
# In page DevTools console:
const origSendMessage = chrome.runtime.sendMessage;
chrome.runtime.sendMessage = function(...args) {
console.log('Message to background:', JSON.stringify(args[0]));
return origSendMessage.apply(this, args);
};
Browser Extension Security Testing Checklist
- Review
manifest.jsonfor overly broad permissions (<all_urls>,cookies,webRequest) - Audit
externally_connectableorigins for subdomain takeover potential - Test all content scripts for DOM-based XSS (innerHTML, document.write, insertAdjacentHTML)
- Review background message handlers for missing sender validation
- Test whether content script XSS can escalate to privileged background actions via messages
- Audit extension pages (popup, options) for XSS via user-controlled data
- Check extension storage for sensitive data stored without encryption
- Review API keys and credentials in extension source code
- Test native messaging host for injection vulnerabilities
- Verify native messaging host validates caller extension ID
- Audit update mechanism for MITM potential (extension updates must be HTTPS)
- Check Content Security Policy in manifest for
unsafe-evalorunsafe-inline - Verify extension does not load remote scripts (prohibited in MV3, but check legacy MV2 extensions)
- Test cross-extension messaging if the extension exposes a message API to other extensions
Ironimo scans the web application layer of your stack — including client-side JavaScript that interacts with browser extensions — to catch vulnerabilities before they reach production.
Start free scan