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.

Content Script DOM-based XSS Pattern Content script reads a DOM element, processes it, and writes back to the DOM using innerHTML or equivalent — without sanitization. A malicious page can craft its DOM to inject a script that runs in the content script's isolated world.
// 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:

  1. Attacker controls a page matching the extension's content_scripts.matches
  2. Attacker's page triggers content script XSS via a crafted DOM element
  3. Injected code sends a message to the background worker requesting a privileged action
  4. 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:

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:

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:

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

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
← Back to Blog