Search Enhanced Protection (Tampermonkey)

Enhance privacy when using search engines: strip tracking params, remove redirect wrappers, set no-referrer, force HTTPS on links, and add noreferrer/noopener. Works on Google, Bing, DuckDuckGo, Yahoo and common search pages. Toggle features from the Tampermonkey menu.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Search Enhanced Protection (Tampermonkey) 
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Enhance privacy when using search engines: strip tracking params, remove redirect wrappers, set no-referrer, force HTTPS on links, and add noreferrer/noopener. Works on Google, Bing, DuckDuckGo, Yahoo and common search pages. Toggle features from the Tampermonkey menu.
// @author       Skibidi555
// @match        *://*.google.*/*
// @match        *://*.bing.com/*
// @match        *://*.duckduckgo.com/*
// @match        *://search.yahoo.com/*
// @match        *://*.brave.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-idle
// @license      You can only change the name 
// ==/UserScript==

(function () {
    'use strict';
    const DEBUG = false;

    // ---- Configuration (persistent via GM_setValue with defaults) ----
    const defaults = {
        stripTrackingParams: true,
        fixRedirectLinks: true,
        setNoReferrerMeta: true,
        forceHTTPS: true,
        addNoreferrerRel: true
    };

    // Load/save helpers (use GM_getValue default arg to avoid undefined)
    function getOpt(key) {
        try {
            return GM_getValue(key, defaults[key]);
        } catch (e) {
            // fallback - Tampermonkey should support GM_getValue with default but be defensive
            return (typeof window['__sep_defaults__'] !== 'undefined' && key in window["__sep_defaults__"])
                ? window['__sep_defaults__'][key] : defaults[key];
        }
    }
    function setOpt(key, val) {
        try {
            GM_setValue(key, !!val);
            if (DEBUG) console.info('SEP: setOpt', key, !!val);
        } catch (e) {
            if (DEBUG) console.warn('SEP: GM_setValue failed', e);
        }
    }

    // Register menu commands to toggle features (re-run without reload)
    function registerMenu() {
        try {
            GM_registerMenuCommand('Toggle stripTrackingParams (' + (getOpt('stripTrackingParams') ? 'ON' : 'OFF') + ')', () => {
                setOpt('stripTrackingParams', !getOpt('stripTrackingParams'));
                runProtection(true);
            });
            GM_registerMenuCommand('Toggle fixRedirectLinks (' + (getOpt('fixRedirectLinks') ? 'ON' : 'OFF') + ')', () => {
                setOpt('fixRedirectLinks', !getOpt('fixRedirectLinks'));
                runProtection(true);
            });
            GM_registerMenuCommand('Toggle setNoReferrerMeta (' + (getOpt('setNoReferrerMeta') ? 'ON' : 'OFF') + ')', () => {
                setOpt('setNoReferrerMeta', !getOpt('setNoReferrerMeta'));
                runProtection(true);
            });
            GM_registerMenuCommand('Toggle forceHTTPS (' + (getOpt('forceHTTPS') ? 'ON' : 'OFF') + ')', () => {
                setOpt('forceHTTPS', !getOpt('forceHTTPS'));
                runProtection(true);
            });
            GM_registerMenuCommand('Toggle addNoreferrerRel (' + (getOpt('addNoreferrerRel') ? 'ON' : 'OFF') + ')', () => {
                setOpt('addNoreferrerRel', !getOpt('addNoreferrerRel'));
                runProtection(true);
            });
        } catch (e) {
            if (DEBUG) console.warn('SEP: registerMenu failed', e);
        }
    }

    registerMenu();

    // ---- Utilities ----
    const TRACKING_PARAMS = [
        /^utm_/i,
        /^gclid$/i,
        /^fbclid$/i,
        /^mc_cid$/i,
        /^mc_eid$/i,
        /^igshid$/i,
        /^ref$/i,
        /^ref_src$/i,
        /^msclkid$/i,
        /^trk_?/i,
        /^icid$/i,
        /^_hsenc$/i,
        /^_hsmi$/i
    ];

    function isTrackingParam(key) {
        return TRACKING_PARAMS.some(rx => rx.test(key));
    }

    function stripTrackingParamsFromSearchParams(sp) {
        const toDelete = [];
        for (const key of sp.keys()) {
            if (isTrackingParam(key)) toDelete.push(key);
        }
        for (const k of toDelete) sp.delete(k);
    }

    function cleanUrlString(urlStr) {
        try {
            // resolve relative URLs
            const u = new URL(urlStr, document.baseURI);

            if (getOpt('stripTrackingParams')) {
                stripTrackingParamsFromSearchParams(u.searchParams);
                if (u.hash && u.hash.includes('=')) {
                    try {
                        const h = new URLSearchParams(u.hash.replace(/^#/, ''));
                        let changed = false;
                        for (const key of Array.from(h.keys())) {
                            if (isTrackingParam(key)) { h.delete(key); changed = true; }
                        }
                        if (changed) u.hash = h.toString() ? '#' + h.toString() : '';
                    } catch (e) { /* ignore malformed hash */ }
                }
            }

            if (getOpt('forceHTTPS') && u.protocol === 'http:') {
                if (/\.(com|org|net|io|gov|edu|co|uk|de|fr|es|nl|ca|au|info|me)$/i.test(u.hostname)) {
                    u.protocol = 'https:';
                }
            }
            return u.toString();
        } catch (e) {
            if (DEBUG) console.warn('SEP: cleanUrlString parse fail for', urlStr, e);
            return urlStr;
        }
    }

    function unwrapGoogleRedirect(href) {
        try {
            const u = new URL(href, document.baseURI);
            // common redirect endpoints
            if ((/\/url$/i).test(u.pathname) && u.searchParams.has('q')) {
                return u.searchParams.get('q');
            }
            for (const p of ['url', 'u', 'q']) {
                if (u.searchParams.has(p)) return u.searchParams.get(p);
            }
        } catch (e) {
            if (DEBUG) console.debug('SEP: unwrap failed', href, e);
        }
        return href;
    }

    function addRel(existing, addition) {
        const set = new Set((existing || '').split(/\s+/).filter(Boolean));
        addition.split(/\s+/).forEach(t => set.add(t));
        return Array.from(set).join(' ');
    }

    function cleanAnchor(a) {
        try {
            if (!(a instanceof HTMLAnchorElement)) return;
            const orig = a.getAttribute('href');
            if (!orig) return;

            if (/^\s*(javascript:|#|mailto:|tel:)/i.test(orig)) {
                if (getOpt('addNoreferrerRel')) a.rel = addRel(a.rel, 'noreferrer noopener');
                return;
            }

            let cleaned = orig;

            if (getOpt('fixRedirectLinks')) {
                cleaned = unwrapGoogleRedirect(cleaned);
            }

            cleaned = cleanUrlString(cleaned);

            if (cleaned !== orig) {
                a.setAttribute('data-sep-cleaned-href', '1');
                a.setAttribute('href', cleaned);
            }

            if (getOpt('addNoreferrerRel')) {
                a.rel = addRel(a.rel, 'noreferrer noopener');
            }
        } catch (e) {
            if (DEBUG) console.warn('SEP: cleanAnchor error', e);
        }
    }

    function processAnchors(root = document) {
        try {
            const anchors = (root && root.querySelectorAll) ? root.querySelectorAll('a[href]') : [];
            for (const a of anchors) {
                if (a.getAttribute('data-sep-cleaned-href') === '1') continue;
                cleanAnchor(a);
            }
        } catch (e) {
            if (DEBUG) console.warn('SEP: processAnchors error', e);
        }
    }

    // prevent handling repeated clicks on same link (e.g., search engine handlers)
    let recentlyIntercepted = new WeakSet();

    function installClickInterceptor() {
        document.addEventListener('click', function (ev) {
            try {
                // ignore if user used modifier keys to open context or special clicks
                if (ev.defaultPrevented) return;
                const target = ev.target;
                if (!target || typeof target.closest !== 'function') return;
                const a = target.closest('a[href]');
                if (!a) return;
                if (recentlyIntercepted.has(a)) return;

                const href = a.getAttribute('href');
                if (!href || /^\s*(javascript:|#)/i.test(href)) return;

                if (getOpt('fixRedirectLinks') || getOpt('stripTrackingParams')) {
                    const cleaned = cleanUrlString(unwrapGoogleRedirect(href));
                    if (cleaned && cleaned !== href) {
                        // middle click / ctrl/meta should open in new tab
                        const openInNew = (ev.button === 1) || ev.ctrlKey || ev.metaKey || a.target === '_blank';
                        ev.preventDefault();
                        ev.stopPropagation();
                        recentlyIntercepted.add(a);
                        if (openInNew) {
                            // open with no opener/referrer where possible
                            try { window.open(cleaned, '_blank', 'noopener,noreferrer'); }
                            catch (e) { window.open(cleaned, '_blank'); }
                        } else {
                            location.href = cleaned;
                        }
                        // remove mark after short time to allow normal subsequent clicks
                        setTimeout(() => { recentlyIntercepted.delete(a); }, 1000);
                    }
                }
            } catch (e) {
                if (DEBUG) console.warn('SEP: click interceptor error', e);
            }
        }, true);
    }

    function setNoReferrerMeta() {
        if (!getOpt('setNoReferrerMeta')) return;
        try {
            let meta = document.querySelector('meta[name="referrer"]');
            if (!meta) {
                meta = document.createElement('meta');
                meta.name = 'referrer';
                if (document.head) {
                    document.head.prepend(meta);
                } else if (document.documentElement) {
                    // fallback - append to documentElement if head absent (rare)
                    document.documentElement.insertBefore(meta, document.documentElement.firstChild);
                }
            }
            meta.setAttribute('content', 'no-referrer');
        } catch (e) {
            if (DEBUG) console.warn('SEP: setNoReferrerMeta failed', e);
        }
    }

    let observer = null;
    function observeAndClean() {
        try {
            if (observer) observer.disconnect();
            observer = new MutationObserver((mutations) => {
                for (const m of mutations) {
                    if (m.addedNodes && m.addedNodes.length) {
                        for (const n of m.addedNodes) {
                            if (n.nodeType === 1) {
                                if (n.querySelector && n.querySelector('a[href]')) {
                                    processAnchors(n);
                                } else if (n.matches && n.matches('a[href]')) {
                                    cleanAnchor(n);
                                }
                            }
                        }
                    }
                }
            });
            observer.observe(document.documentElement || document.body, { childList: true, subtree: true });
            processAnchors(document);
        } catch (e) {
            if (DEBUG) console.warn('SEP: observeAndClean failed', e);
        }
    }

    function runProtection(forceReprocess = false) {
        try {
            if (getOpt('setNoReferrerMeta')) setNoReferrerMeta();
            if (getOpt('fixRedirectLinks') || getOpt('stripTrackingParams') || getOpt('addNoreferrerRel') || getOpt('forceHTTPS')) {
                if (forceReprocess) processAnchors(document);
                installClickInterceptor();
                observeAndClean();
            }
            if (DEBUG) console.info('Search Enhanced Protection: active (features: ' +
                (getOpt('stripTrackingParams') ? 'stripTrackingParams ' : '') +
                (getOpt('fixRedirectLinks') ? 'fixRedirectLinks ' : '') +
                (getOpt('setNoReferrerMeta') ? 'setNoReferrerMeta ' : '') +
                (getOpt('forceHTTPS') ? 'forceHTTPS ' : '') +
                (getOpt('addNoreferrerRel') ? 'addNoreferrerRel ' : '') + ')');
        } catch (e) {
            if (DEBUG) console.error('SEP: runProtection failed', e);
        }
    }

    runProtection();

    // SPA navigation hooks
    (function hijackHistory() {
        try {
            const push = history.pushState;
            const replace = history.replaceState;
            history.pushState = function () {
                push.apply(this, arguments);
                setTimeout(() => runProtection(true), 200);
            };
            history.replaceState = function () {
                replace.apply(this, arguments);
                setTimeout(() => runProtection(true), 200);
            };
            window.addEventListener('popstate', () => setTimeout(() => runProtection(true), 200));
        } catch (e) {
            if (DEBUG) console.warn('SEP: hijackHistory failed', e);
        }
    })();

})();