The Architecture of Chrome Extension Permissions: A Deep Dive
Sep 21, 2024
Chrome extensions are incredibly popular tools that enhance your browser's functionality. You probably have a bunch of them installed - these extensions can do things like store passwords, manage tabs, change words on a webpage as you type, block ads, and more and go beyond what the usual website security model allows. Given how much sensitive and personal data your browser stores (browsing history, passwords, and banking credentials, etc) how does a browser ensure the security of extensions? That’s what this post will cover: The Chrome Extension Permission System.
In this post, I’ll focus on Manifest V3 (MV3), the current system for Chrome extension permissions. While Manifest V2 (MV2) was simpler, it is being deprecated and will no longer be supported in the future.
The Basics: The Manifest File
Every Chrome extension has a manifest file (manifest.json) that contains the extension's basic information, such as its name, version, and required permissions. For example, an extension can request permission to read your cookies, access your bookmarks, manage your downloads, play audio, use the printer, and a lot more.
Here is a sample of what a manifest file may look like:
A particularly important type of permissions (in the content_scripts block) is host permissions, which allows a Chrome extension to inject content scripts into specific websites matching a set of URLs. This may be executed on all websites or only certain ones (eg: if you’re developing an extension only for YouTube, then you probably only need to inject content scripts on the YouTube website). When installing an extension, users are presented with a warning dialog explaining what the extension can do with these permissions.
For example, an extension might request permission to “read and change all your data on websites you visit.” While this sounds alarming, it’s necessary because if an extension injects JavaScript into websites, it could theoretically read or write any content on any page you’re visiting. The browser doesn’t really know what the script is doing, so it can only assume the worst-case scenario.
Parts of a Chrome Extension
A Chrome extension is not just one unified entity—it is composed of several parts, and each part has different permissions. The three most important components are the content scripts, popup pages, and background service workers.
There are additional components, such as the DevTools page, the options page, the Omnibox, etc, but this post will focus on the three main components. These parts communicate with each other using message passing through the WebExtensions API. Let’s break down each component.
Content Scripts
Content scripts are the only part of a Chrome extension that can interact with the DOM of a webpage. For example, a content script could change the background color of a page to blue. However, it cannot access higher-privileged Chrome APIs, even if permissions are granted in the manifest file. Instead, content scripts must send messages to other parts of the extension, usually the background service worker, to perform tasks requiring elevated privileges.
Content scripts are essentially a lower-privilege system. They can access the page DOM, display UI directly on the page, and send messages to other components.
However, they cannot access Chrome APIs or fetch data freely (restricted by CORS and CSP policies).
Background Service Workers
Service workers run in the background and are allowed to access all Chrome APIs the extension has permissions for, such as tabs or bookmarks.
They cannot interact with the DOM of any web page or display any UI. They can only respond to events from other parts of the extension and run for a limited amount of time before shutting down.
Their primary role is to relay messages between other components, Chrome APIs, and any backend servers.
Popup Pages
Popup pages provide a user interface for the extension when triggered by clicking the browser toolbar icon. They can only be triggered by the user clicking the toolbar button and cannot be opened any other way (eg: programmatically or in response to a user action on the page).
These pages have permissions to access Chrome APIs but, like service workers, also cannot interact with the DOM of the web page.
Permissions in a Nutshell
Each component of a Chrome extension has its own set of permissions and limitations. Here’s a quick summary:
Chrome extensions must coordinate these parts through message passing, which ensures data is transferred securely and tasks are performed in the correct component.
Example: AI Summarizer Extension
Let’s walk through an example of a Chrome extension that summarizes the current page using AI. Here’s how you might architect this extension:
The user triggers the action through the popup window.
The popup window sends a message to the content script injected into the page.
The content script reads the page DOM but cannot make a backend request, so it sends a message to the service worker.
The service worker fetches data from the backend AI model and returns the summary to the content script.
Finally, the content script updates the page DOM to display the AI-generated summary.
This multi-step process is a typical example of how a Chrome extension developer needs to use message passing to work around permission constraints.
The Content Security Policy (CSP)
One common restriction Chrome extensions face is the Content Security Policy (CSP). CSP is a security feature enforced by websites via response headers to control which resources and operations are allowed. Some common examples of CSP are:
script-src: Restricts where JavaScript can be loaded from.
img-src: Restricts where images can be loaded from.
frame-src: Restricts which URLs can be embedded in <iframe> elements.
connect-src: Restricts where the browser can send requests using fetch().
Since content scripts execute in the same environment as the web page, they are subject to the page’s CSP restrictions. This means that on some websites (e.g., GitHub), fetch requests initiated by a content script will fail due to CSP, even though the same requests might work on other websites (e.g., Wikipedia).
For extensions that need to work across all websites, you may assume that whenever an action may be blocked by CSP, it will be blocked at least on some websites. A common operation for an extension is communication with a backend through a fetch() request, therefore proxying fetch requests through the service worker is necessary to bypass these restrictions. The service worker operates independently of the page's CSP and can make unrestricted fetch requests.
The Eval Ban in Manifest V3
Another significant restriction introduced in Manifest V3 is the complete ban on using eval() or any equivalent constructs, such as:
Creating a new function with Function().
Using certain patterns in regular expressions.
Loading scripts dynamically from CDNs with the script tag.
Using web workers for multithreading.
While eval() isn’t commonly used in modern JavaScript, its equivalents appear more frequently, especially in third-party dependencies. To address this limitation, Chrome introduced a special sandbox page. This sandbox has no permissions to use Chrome APIs and cannot access the DOM of any other page, but it can execute eval.
How the sandbox works is that libraries or code that use eval or similar constructs must be placed inside the special sandbox page. Inputs and outputs must be transferred between the sandbox page and the rest of the extension using (you guessed it) more message passing.
You can probably see why this is problematic. Any library that invokes eval or similar constructs—even indirectly through dependencies—must be isolated into the sandbox page. Refactoring this can be extremely challenging, especially if the eval usage is deeply embedded within a framework.
Due to these challenges, many popular libraries have taken steps to avoid using eval-like constructs to ensure compatibility with Chrome extensions, but not always. When this happens, raising an issue on the package’s GitHub repo might honestly be the best solution.
Why is Chrome Extension Security So Complex?
If you're wondering how Chrome extensions arrived at their current security architecture, the reasoning might surprise you. The rationale is outlined in this paper from Google. The security model for Chrome extensions does not aim to protect users against malicious extensions. If an extension is malicious, there’s little anyone can do about it. Users receive warnings before installation but often proceed regardless.
Instead, Chrome’s security model assumes that extensions are not malicious but may have security vulnerabilities. This assumption is reasonable since most extensions are created by average developers who may not be security experts. These insecure extensions can become prime targets for malicious websites, which aim to exploit vulnerabilities in content scripts to gain access to privileged APIs.
For example, a malicious website might attempt to trick the extension into running malicious scripts. There are many potential attack vectors like replacing functions on the window object, with its own functions, so when the extension calls them, the attacker’s arbitrary code is executed.
To mitigate these risks, Chrome extensions are split into two distinct privilege levels:
Content Scripts (Lower Privilege): These share the same environment as the webpage's DOM and process. They interact with untrusted website code but have limited permissions and access.
Extension Core (Higher Privilege): These include the popup page and background service workers and hold access to powerful Chrome APIs, such as storage, tabs, and bookmarks. They are process-isolated from the content scripts, and communication is restricted to message passing (strings only, no shared objects).
By isolating privileges, an attacker must first exploit a vulnerability in the content script and then trick it into sending a malicious message to the higher-privilege background script. But even if this happens, eval() is disabled entirely within the extension, making arbitrary code execution much harder to achieve.
These mechanisms collectively make Chrome extensions significantly harder to exploit.
Voice Writer Chrome Extension
If you enjoyed this post, check out my Voice Writer Chrome Extension! You can dictate your thoughts freely and it automatically corrects grammar in real time, working on any website. I use it to write a lot faster and never have to worry about the wording of my sentences.
Voice Writer can also be used as a web app. When I developed the Chrome extension, I had some trouble getting the permissions to work, which inspired this blog post to share what I learned in the process.
This blog post was written using Voice Writer. Try it for free today! If this post was helpful please let me know at bai@voicewriter.io!
More Resources
Book: Building Browser Extensions by Matt Frisbie
Official Documentation: Chrome Extensions Documentation
Google’s Paper on Chrome Extension Security: Protecting Browsers from Extension Vulnerabilities