How Do Userscripts Handle Cross-Origin Requests?

Userscripts bypass the web browser’s standard Same-Origin Policy (SOP) by leveraging special APIs provided by userscript managers like Tampermonkey, Violentmonkey, or Greasemonkey. While normal client-side JavaScript running in a web page is restricted from making XMLHttpRequests or fetch requests to different domains, userscripts execute with elevated permissions. By using dedicated API functions—most notably GM_xmlhttpRequest or GM.xmlHttpRequest—userscripts delegate cross-origin network calls to the browser extension background context, bypassing traditional CORS restrictions while managing security risks through specific header declarations.

The Same-Origin Policy Barrier

In standard web development, the Same-Origin Policy is a critical security mechanism that restricts how a document or script loaded from one origin can interact with a resource from another origin. An origin is defined by the scheme (protocol), host (domain), and port.

When a standard web page script attempts to fetch data from another domain, the browser checks for Cross-Origin Resource Sharing (CORS) headers. If the destination server does not explicitly allow the requesting origin via the Access-Control-Allow-Origin header, the browser blocks the response.

Standard Web Page:
[ Script on Domain A ] ---> (Standard fetch/XHR) ---> [ Domain B ] (Blocked by SOP/CORS)

How Userscript Managers Bypass CORS

Userscript managers operate as browser extensions, giving them access to extension-level capabilities that regular web pages do not possess. Extension background scripts are not bound by the Same-Origin Policy in the same way content scripts or page scripts are.

Userscript Architecture:
[ Userscript (Domain A) ] 
       |
       v  (Calls GM_xmlhttpRequest)
[ Userscript Manager Extension ] 
       |
       v  (Executes Privileged Network Request)
[ Server (Domain B) ]

When a userscript invokes a manager-provided network function, the following process occurs:

The GM_xmlhttpRequest API

The primary mechanism for cross-origin requests in userscripts is GM_xmlhttpRequest (or its modern Promise-based equivalent, GM.xmlHttpRequest). This function shares a similar interface with the standard XMLHttpRequest object but includes extended parameters.

Request Syntax Example

To use cross-origin capabilities, userscripts must explicitly grant themselves permission using the @grant directive in the metadata block:

// ==UserScript==
// @name         Cross-Origin Data Fetcher
// @namespace    http://example.com/
// @version      1.0
// @match        https://example.com/*
// @grant        GM_xmlhttpRequest
// me ==/UserScript==

GM_xmlhttpRequest({
    method: "GET",
    url: "https://api.thirdparty.com/data",
    headers: {
        "Accept": "application/json"
    },
    onload: function(response) {
        console.log("Status:", response.status);
        console.log("Data:", response.responseText);
    },
    onerror: function(error) {
        console.error("Request failed:", error);
    }
});

Modern Async/Await Usage

Modern userscript managers support GM.xmlHttpRequest (using dot notation), which returns a Promise and can be used seamlessly with async and await:

// ==UserScript==
// @name         Async Fetcher
// @match        https://example.com/*
// @grant        GM.xmlHttpRequest
// ==/UserScript==

async function fetchData() {
    try {
        const response = await GM.xmlHttpRequest({
            method: "GET",
            url: "https://api.thirdparty.com/data"
        });
        const data = JSON.parse(response.responseText);
        console.log(data);
    } catch (error) {
        console.error("Error fetching data:", error);
    }
}

Security Considerations and the @connect Directive

Allowing arbitrary cross-origin requests poses significant security risks, such as silent data exfiltration or unauthorized actions on third-party sites where the user is authenticated. To mitigate these risks, modern managers enforce domain restriction policies.

Domain Whitelisting with @connect

Managers like Tampermonkey and Violentmonkey require scripts to declare intended cross-origin targets using the @connect directive in the metadata header.

// ==UserScript==
// @name         Secure Request Script
// @match        https://example.com/*
// @grant        GM_xmlhttpRequest
// @connect      api.thirdparty.com
// @connect      cdn.anotherdomain.org
// ==/UserScript==

Permission Enforcement Rules

Comparison: Native Fetch vs. GM_xmlhttpRequest

Feature Native fetch / XHR GM_xmlhttpRequest
Same-Origin Policy Strictly enforced by browser Bypassed via extension privileges
CORS Header Requirement Required on target server Not required
Cookie Handling Subject to credentials mode and SameSite rules Can send cookies associated with target domain
User Permission None (constrained by browser sandbox) Controlled via @grant and @connect
Custom Headers Restricted (e.g., forbidden headers like User-Agent) Less restricted (can override restricted headers)