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:

  1. No WebExtension APIs: The sandbox has zero access to the browser.* or chrome.* namespaces.
  2. Isolated Global Scope: The user script gets its own global window object. Variables and functions defined by the host webpage are invisible to the user script, and vice versa.
  3. 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.appendChild to 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:

  1. Privileged Initialization: The apiScript runs in a context that has access to the browser.userScripts event listener and standard content script APIs (like browser.runtime.sendMessage).
  2. Defining Custom APIs: When the browser.userScripts.onBeforeScript event fires, the apiScript intercepts the initialization of the third-party user script.
  3. Selective Exporting: The apiScript uses Firefox-specific cloning functions—exportFunction() and cloneInto()—to safely copy specific custom functions into the isolated user script sandbox.
  4. Message Passing: When the third-party user script calls one of these exported functions, the execution securely crosses the boundary back into the apiScript. The apiScript then 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.