How Do Userscripts Intercept Webpage Network Requests?

Userscripts intercept network requests made by a webpage by modifying or wrapping the browser’s built-in networking APIs—primarily fetch() and XMLHttpRequest (XHR)—before the webpage executes its own scripts. By replacing these global functions with custom wrapper functions or using userscript manager APIs like GM_xmlhttpRequest, scripts can inspect, modify, or block both outgoing requests and incoming responses.


Modifying Native Networking APIs (Monkey Patching)

The most common technique for intercepting requests within the webpage’s context is monkey patching. Because JavaScript objects and global functions are mutable, a userscript running at document-start can replace the standard browser APIs with custom implementations.

Intercepting the Fetch API

To intercept calls made via window.fetch, a userscript saves a reference to the original fetch function and replaces window.fetch with a custom wrapper.

// Store the original fetch function
const originalFetch = window.fetch;

// Override window.fetch
window.fetch = async function(...args) {
    let [resource, config] = args;

    // Inspect or modify request parameters
    console.log('Intercepted Fetch Request:', resource, config);

    // Call the original fetch function
    const response = await originalFetch.apply(this, args);

    // Clone and inspect the response
    const clonedResponse = response.clone();
    clonedResponse.json().then(data => {
        console.log('Intercepted Fetch Response:', data);
    });

    return response;
};

Intercepting XMLHttpRequest (XHR)

Intercepting older XHR requests involves overriding XMLHttpRequest.prototype.open and XMLHttpRequest.prototype.send.

const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.open = function(method, url, ...rest) {
    this._url = url; // Store URL on the instance for later reference
    return originalOpen.apply(this, [method, url, ...rest]);
};

XMLHttpRequest.prototype.send = function(body) {
    this.addEventListener('load', function() {
        console.log(`XHR completed for ${this._url}:`, this.responseText);
    });
    return originalSend.apply(this, [body]);
};

Managing Execution Timing and Contexts

For monkey patching to work reliably, the userscript must execute before the target webpage runs its own scripts.

Execution Timing

Userscript managers (like Tampermonkey or Violentmonkey) allow developers to set execution timing using the @run-at directive:

Sandbox Isolation (unsafeWindow)

Modern userscript managers run scripts in an isolated execution context (a sandbox) to prevent security risks.


Using Userscript Manager APIs (GM_xmlhttpRequest)

Instead of intercepting the page’s existing requests, userscripts often need to make their own cross-origin requests or bypass CORS (Cross-Origin Resource Sharing) restrictions. Userscript managers provide privileged APIs for this purpose, such as GM_xmlhttpRequest (or GM.xmlHttpRequest).

Feature Standard Page fetch / XHR GM_xmlhttpRequest
CORS Restrictions Enforced strictly by the browser Bypassed via extension privileges
Cookie Handling Restricted by SameSite / origin rules Can send/receive cross-domain cookies
Context Runs inside the page context Runs through the background extension context

By combining API overrides with privileged extension functions, userscripts gain full control over inspecting, redirecting, or altering the data flowing between a web application and its backend servers.