How Firefox's userScripts API Sandboxes Third-Party Scripts
The Firefox userScripts API enables browser extensions,
such as user script managers, to safely execute untrusted, third-party
JavaScript code on web pages. This article provides a technical
breakdown of how Firefox achieves this security model. It covers the
creation of isolated JavaScript compartments, the use of Xray wrappers
for secure DOM manipulation, and the apiScript mechanism
that allows extension developers to selectively expose custom APIs to
the sandboxed environment without risking privilege escalation.
The Threat Model of Third-Party Scripts
Standard WebExtension content scripts operate in a semi-privileged
environment. While they are isolated from the webpage's JavaScript, they
still have access to a subset of powerful browser.* APIs,
such as browser.storage and
browser.runtime.
User script managers (like Tampermonkey or Violentmonkey) allow users
to install arbitrary scripts written by third-party developers. Running
these untrusted scripts as standard content scripts is a security risk,
as a malicious user script could hijack the parent extension's
privileges. The userScripts API solves this by creating a
stricter, zero-privilege sandbox specifically for third-party code.
Isolated JavaScript Compartments
When an extension registers a script using
browser.userScripts.register(), Firefox’s underlying engine
(Gecko) creates a completely separate JavaScript compartment—the
sandbox—for that specific user script.
This sandbox has three defining restrictions:
- No WebExtension APIs: The sandbox has zero access
to the
browser.*orchrome.*namespaces. - Isolated Global Scope: The user script gets its own
global
windowobject. Variables and functions defined by the host webpage are invisible to the user script, and vice versa. - Cross-Script Isolation: Each registered user script runs in its own isolated sandbox, preventing different third-party scripts from interfering with one another.
Secure DOM Access via Xray Vision
Although the JavaScript environments are separated, a user script still needs to read and modify the webpage's Document Object Model (DOM). Firefox facilitates this safely using a security mechanism called "Xray vision" (Xray wrappers).
When a user script accesses the DOM of the underlying webpage, it views the DOM through an Xray wrapper. This provides two-way protection:
- Protection from the Webpage: If a malicious webpage
modifies standard DOM properties or prototypes (for example, overriding
Element.prototype.appendChildto steal data), the user script's Xray wrapper bypasses the modified JavaScript and calls the original, native C++ implementation of the DOM method. - Protection from Expando Properties: Custom properties attached to DOM nodes by the webpage are hidden from the user script, preventing the page from tricking the user script into executing malicious code.
Bridging the Sandbox with
apiScript
User scripts often require elevated capabilities, such as making
cross-origin network requests or storing data locally (traditionally
known as GM_* APIs). Because the userScripts
sandbox is stripped of all privileges, Firefox provides a controlled
bridge called the apiScript.
The apiScript is a script written by the extension
developer (not the third-party user script author) and declared in the
extension's manifest. It executes before the user script runs and acts
as a gatekeeper.
The execution flow works as follows:
- Privileged Initialization: The
apiScriptruns in a context that has access to thebrowser.userScriptsevent listener and standard content script APIs (likebrowser.runtime.sendMessage). - Defining Custom APIs: When the
browser.userScripts.onBeforeScriptevent fires, theapiScriptintercepts the initialization of the third-party user script. - Selective Exporting: The
apiScriptuses Firefox-specific cloning functions—exportFunction()andcloneInto()—to safely copy specific custom functions into the isolated user script sandbox. - Message Passing: When the third-party user script
calls one of these exported functions, the execution securely crosses
the boundary back into the
apiScript. TheapiScriptthen validates the request and uses standard WebExtension messaging to ask the extension's background script to perform the privileged action, returning the result back into the sandbox as a Promise.
By combining isolated compartments, Xray wrappers, and the
apiScript cloning boundary, Firefox ensures third-party
user scripts can manipulate web pages and utilize custom extension
features without ever gaining direct access to the browser's underlying
privileged APIs.