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:
@run-at document-start: Injects the script before any DOM elements or site scripts are loaded. This is essential for network interception to ensure the native APIs are wrapped before the site caches or calls them.
Sandbox Isolation
(unsafeWindow)
Modern userscript managers run scripts in an isolated execution context (a sandbox) to prevent security risks.
- Direct modifications to
window.fetchinside a sandboxed userscript only affect the userscript’s environment, not the page’s environment. - To intercept requests made by the webpage’s own scripts, userscripts
must access the page’s global context using
unsafeWindow(e.g.,unsafeWindow.fetch = ...) or inject a<script>element directly into the document DOM.
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.