Greasy Fork is available in English.
Hide HTTP Referrer on all websites to prevent destination sites from tracking your origin page
// ==UserScript==
// @name Hide Referrer
// @namespace https://github.com/planetoid/userscripts
// @version 1.0.0
// @description Hide HTTP Referrer on all websites to prevent destination sites from tracking your origin page
// @author Planetoid
// @match *://*/*
// @grant none
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ============================================================
// Configuration
// ============================================================
const CONFIG = {
// Whether to inject <meta> referrer tag (primary protection)
injectMetaTag: true,
// Whether to add rel="noreferrer" to all <a> tags
patchLinks: true,
// Whether to intercept window.open so it doesn't send referrer
patchWindowOpen: true,
// Whether to observe DOM changes and patch dynamically added links (for SPAs)
observeDom: true,
// Excluded domains (referrer hiding will be skipped on these sites)
// e.g. ['example.com', 'mysite.org']
excludedDomains: [],
// Whether to show debug messages in the console
debug: false,
};
// ============================================================
// Utilities
// ============================================================
function log(...args) {
if (CONFIG.debug) {
console.log('[Hide Referrer]', ...args);
}
}
function isExcluded() {
return CONFIG.excludedDomains.some(
(domain) =>
location.hostname === domain ||
location.hostname.endsWith('.' + domain)
);
}
function isExternalLink(anchor) {
try {
return anchor.href && new URL(anchor.href).hostname !== location.hostname;
} catch {
return false;
}
}
// ============================================================
// 1. Inject <meta name="referrer" content="no-referrer">
// Injected as early as possible during document-start
// ============================================================
function injectMetaTag() {
if (!CONFIG.injectMetaTag) return;
const meta = document.createElement('meta');
meta.name = 'referrer';
meta.content = 'no-referrer';
// <head> may not exist yet at document-start, try multiple mount points
const parent =
document.head ||
document.documentElement ||
document.querySelector('head');
if (parent) {
parent.insertBefore(meta, parent.firstChild);
log('Meta tag injected into', parent.tagName);
} else {
// Very early stage — wait for the DOM to appear before injecting
const observer = new MutationObserver(() => {
const target = document.head || document.documentElement;
if (target) {
target.insertBefore(meta, target.firstChild);
log('Meta tag injected (deferred)');
observer.disconnect();
}
});
observer.observe(document, { childList: true, subtree: true });
}
}
// ============================================================
// 2. Add rel="noreferrer noopener" to all <a> tags
// ============================================================
function patchAnchor(anchor) {
if (!anchor.href || anchor.dataset.referrerPatched) return;
// Only patch external links — remove this check to patch all links
if (!isExternalLink(anchor)) return;
const relValues = new Set((anchor.rel || '').split(/\s+/).filter(Boolean));
relValues.add('noreferrer');
relValues.add('noopener');
anchor.rel = [...relValues].join(' ');
anchor.dataset.referrerPatched = '1';
log('Patched link:', anchor.href);
}
function patchAllLinks() {
if (!CONFIG.patchLinks) return;
document.querySelectorAll('a[href]').forEach(patchAnchor);
}
// ============================================================
// 3. Intercept window.open — use an intermediate page to strip referrer
// ============================================================
function patchWindowOpen() {
if (!CONFIG.patchWindowOpen) return;
const originalOpen = window.open;
window.open = function (url, target, features) {
if (url) {
try {
const parsed = new URL(url, location.href);
if (parsed.hostname !== location.hostname) {
// Use a Blob URL as an intermediate page to strip referrer
const html = `
<!DOCTYPE html>
<html>
<head>
<meta name="referrer" content="no-referrer">
<meta http-equiv="refresh" content="0;url=${parsed.href}">
</head>
<body></body>
</html>`;
const blob = new Blob([html], { type: 'text/html' });
const blobUrl = URL.createObjectURL(blob);
const win = originalOpen.call(window, blobUrl, target, features);
// Revoke Blob URL after a short delay
setTimeout(() => URL.revokeObjectURL(blobUrl), 3000);
log('window.open intercepted:', url);
return win;
}
} catch (e) {
log('window.open parse error:', e);
}
}
return originalOpen.call(window, url, target, features);
};
log('window.open overridden');
}
// ============================================================
// 4. MutationObserver — watch for dynamically added links
// ============================================================
function observeDom() {
if (!CONFIG.observeDom || !CONFIG.patchLinks) return;
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
// If the added node itself is an <a>
if (node.tagName === 'A') {
patchAnchor(node);
}
// Check the subtree of the added node for <a> elements
if (node.querySelectorAll) {
node.querySelectorAll('a[href]').forEach(patchAnchor);
}
}
}
});
// Wait for <body> to exist before observing
function startObserving() {
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
log('DOM Observer started');
} else {
requestAnimationFrame(startObserving);
}
}
startObserving();
}
// ============================================================
// Main
// ============================================================
if (isExcluded()) {
log('Current domain is excluded, skipping');
return;
}
// Phase 1: document-start (as early as possible)
injectMetaTag();
patchWindowOpen();
// Phase 2: Patch links after DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
patchAllLinks();
observeDom();
});
} else {
patchAllLinks();
observeDom();
}
log('Hide Referrer userscript loaded');
})();