Aggressive privacy protection: blocks trackers, strips tracking params, spoofs fingerprinting vectors — without breaking normal site functionality.
// ==UserScript==
// @name Privacy Shield - 2.7
// @namespace https://github.com/j0tsarup
// @version 2.7
// @description Aggressive privacy protection: blocks trackers, strips tracking params, spoofs fingerprinting vectors — without breaking normal site functionality.
// @author </j0tsarup>
// @match *://*/*
// @run-at document-start
// @grant none
// @license MIT
// ==/UserScript==
/**
* ============================================================
* PRIVACY SHIELD - v2.7
* Author : </j0tsarup>
* ============================================================
*
* WHAT THIS SCRIPT DOES
* ---------------------
* Layer 1 Tracking Parameter Removal
* Strips utm_*, fbclid, gclid, and 30+ other
* tracking tokens from the current page URL.
*
* Layer 2 Fingerprint Spoofing (Canvas / WebGL / Audio)
* Canvas pixel noise, WebGL vendor/renderer mask,
* AudioContext sample noise, Navigator hardware
* property overrides (configurable, safe).
*
* Layer 3 Network Request Blocking
* Hostname-anchored XHR + fetch interception for
* 25+ known tracker domains. No DOM removal.
*
* Layer 4 Search Engine Link Cleaning
* Unwraps Google, Bing, Yahoo, DuckDuckGo redirect
* URLs so clicks go direct to the destination.
*
* Layer 5 ClientRects Spoofing
* Smart probe detection — noises only tiny or
* off-screen elements, not normal layout calls.
*
* Layer 6 Font Fingerprinting Protection
* Patches measureText() with stable session noise.
*
* Layer 7 WebGPU Fingerprinting Protection
* Masks adapter vendor, architecture, device info.
*
* Layer 8 WebRTC Host Candidate Filtering
* Drops host ICE candidates to prevent IP leaks.
*
* Layer 9 Client Hints Blocking
* Spoofs navigator.userAgentData with generic
* brand/platform values.
*
* Layer 10 Timezone & Intl Spoofing (FIXED v2.7)
* Spoofs ONLY the timeZone field. Caller locale is
* always preserved — no longer forces en-US, which
* was breaking non-English sites and currency/number
* formatting globally.
*
* Layer 11 Network Information API Spoofing
* Spoofs navigator.connection with neutral values.
*
* Layer 12 SpeechSynthesis Spoofing (FIXED v2.7)
* Returns empty voice list. No longer blocks the
* voiceschanged event, which was causing sites using
* legitimate async TTS to hang indefinitely.
*
* SHORTCUTS
* ---------
* None - runs silently in the background.
* Type __PS_STATS__ in the browser console for live counts.
*
* LIMITATIONS
* -----------
* - Timezone fix may affect calendar/scheduling apps.
* - First-party proxied trackers cannot be blocked safely.
* - User-Agent spoofing is intentionally omitted.
*
* CHANGELOG
* ---------
* v2.7 Minor release — Layer 10 + 12 breakage fixes
* * Layer 10: Intl.DateTimeFormat wrapper no longer
* forces en-US locale. Caller locale is passed through
* unchanged. Only timeZone is spoofed. Fixes broken
* date/currency/number formatting on non-English sites.
* * Layer 10: toLocaleString family no longer overrides
* locale. Only injects timeZone into options.
* * Layer 12: Removed voiceschanged event blocking.
* Sites using async TTS were hanging waiting for an
* event that would never fire.
* v2.6 Added layers 10 (Timezone), 11 (Network API),
* 12 (SpeechSynthesis)
* v2.5 Layer 3 false-positive fix (Chewy white screen)
* v2.4 Layer 2 Navigator stability fix
* v2.3 Diagnostic build with per-layer toggles
* v2.2 Removed iceTransportPolicy + SCRIPT DOM removal
* v2.1 ClientRects smart probe detection
* v2.0 Added layers 5-9
* v1.0 Initial release
*
* ============================================================
*/
(function () {
'use strict';
/* ----------------------------------------------------------
* STATS (console: __PS_STATS__)
* ---------------------------------------------------------- */
const stats = {
blockedRequests: 0,
strippedParams: 0,
cleanedLinks: 0,
spoofedRects: 0,
blockedRTCLeaks: 0,
};
window.__PS_STATS__ = stats;
function safeDefine(obj, prop, value) {
try {
const desc = Object.getOwnPropertyDescriptor(obj, prop);
if (!desc || desc.configurable) {
Object.defineProperty(obj, prop, {
get: () => value,
configurable: true,
enumerable: true,
});
}
} catch (_) {}
}
function noise(seed, magnitude) {
const x = Math.sin(seed + 1) * 10000;
return (x - Math.floor(x) - 0.5) * magnitude;
}
/* ----------------------------------------------------------
* LAYER 1 TRACKING PARAMETER REMOVAL
* ---------------------------------------------------------- */
const TRACKING_PARAMS = new Set([
'utm_source','utm_medium','utm_campaign','utm_term','utm_content',
'utm_id','utm_source_platform','utm_creative_format','utm_marketing_tactic',
'gclid','gclsrc','dclid','gbraid','wbraid',
'fbclid','fb_action_ids','fb_action_types','fb_source',
'mc_cid','mc_eid','msclkid',
'_hsenc','_hsmi','__hssc','__hstc','__hsfp','hsCtaTracking',
'mkt_tok','s_cid','icid',
'ref','referrer','source','affiliate','zanpid',
'origin','igshid','twclid','li_fat_id','yclid',
'email_source','CMP',
]);
function cleanURL(urlStr) {
let url;
try { url = new URL(urlStr); } catch { return urlStr; }
let stripped = 0;
TRACKING_PARAMS.forEach(p => {
if (url.searchParams.has(p)) { url.searchParams.delete(p); stripped++; }
});
for (const key of [...url.searchParams.keys()])
if (key.startsWith('utm_')) { url.searchParams.delete(key); stripped++; }
stats.strippedParams += stripped;
return stripped > 0 ? url.toString() : urlStr;
}
(function () {
const c = cleanURL(window.location.href);
if (c !== window.location.href) history.replaceState(null, '', c);
})();
/* ----------------------------------------------------------
* LAYER 2 CANVAS / WEBGL / AUDIO / NAVIGATOR SPOOFING
* ---------------------------------------------------------- */
(function () {
try {
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function (...a) {
const ctx = this.getContext('2d');
if (ctx) { const d = ctx.getImageData(0,0,1,1); d.data[3] ^= 1; ctx.putImageData(d,0,0); }
return origToDataURL.apply(this, a);
};
} catch (_) {}
try {
const origGID = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function (...a) {
const d = origGID.apply(this, a); d.data[3] ^= 1; return d;
};
} catch (_) {}
try {
const patchGL = proto => {
const orig = proto.getParameter;
proto.getParameter = function (p) {
if (p === 37445) return 'Intel Inc.';
if (p === 37446) return 'Intel Iris OpenGL';
return orig.call(this, p);
};
};
patchGL(WebGLRenderingContext.prototype);
if (typeof WebGL2RenderingContext !== 'undefined')
patchGL(WebGL2RenderingContext.prototype);
} catch (_) {}
try {
if (typeof AudioBuffer !== 'undefined') {
const orig = AudioBuffer.prototype.getChannelData;
AudioBuffer.prototype.getChannelData = function (...a) {
const d = orig.apply(this, a);
if (d.length > 0) d[0] += 0.0000001 * (Math.random() - 0.5);
return d;
};
}
} catch (_) {}
try { safeDefine(Navigator.prototype, 'hardwareConcurrency', 4); } catch (_) {}
try { safeDefine(Navigator.prototype, 'deviceMemory', 8); } catch (_) {}
try {
Object.defineProperty(Navigator.prototype, 'languages', {
get: () => ['en-GB', 'en'],
configurable: true,
enumerable: true,
});
} catch (_) {}
})();
/* ----------------------------------------------------------
* LAYER 3 NETWORK REQUEST BLOCKING
* ---------------------------------------------------------- */
(function () {
const BLOCKED = [
/^https?:\/\/([^/]*\.)?google-analytics\.com\//,
/^https?:\/\/([^/]*\.)?googletagmanager\.com\//,
/^https?:\/\/([^/]*\.)?googletagservices\.com\//,
/^https?:\/\/([^/]*\.)?doubleclick\.net\//,
/^https?:\/\/([^/]*\.)?googlesyndication\.com\/pagead\//,
/^https?:\/\/([^/]*\.)?facebook\.com\/tr(\/|\?|$)/,
/^https?:\/\/([^/]*\.)?connect\.facebook\.net\//,
/^https?:\/\/([^/]*\.)?static\.ads-twitter\.com\//,
/^https?:\/\/([^/]*\.)?snap\.licdn\.com\//,
/^https?:\/\/([^/]*\.)?px\.ads\.linkedin\.com\//,
/^https?:\/\/([^/]*\.)?hotjar\.com\//,
/^https?:\/\/([^/]*\.)?api\.mixpanel\.com\//,
/^https?:\/\/([^/]*\.)?api\.amplitude\.com\//,
/^https?:\/\/([^/]*\.)?api\.segment\.io\//,
/^https?:\/\/([^/]*\.)?cdn\.segment\.com\//,
/^https?:\/\/([^/]*\.)?heapanalytics\.com\//,
/^https?:\/\/([^/]*\.)?fullstory\.com\//,
/^https?:\/\/([^/]*\.)?static\.getclicky\.com\//,
/^https?:\/\/([^/]*\.)?quantserve\.com\//,
/^https?:\/\/([^/]*\.)?scorecardresearch\.com\//,
/^https?:\/\/([^/]*\.)?trc\.taboola\.com\//,
/^https?:\/\/([^/]*\.)?outbrain\.com\/paid\//,
];
const isBlocked = url => url && BLOCKED.some(p => p.test(url));
const origXHR = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (m, url, ...r) {
if (isBlocked(url)) { stats.blockedRequests++; return origXHR.call(this, m, 'about:blank', ...r); }
return origXHR.call(this, m, url, ...r);
};
const origFetch = window.fetch;
window.fetch = function (input, init) {
const url = typeof input === 'string' ? input
: (input instanceof Request ? input.url : String(input));
if (isBlocked(url)) {
stats.blockedRequests++;
return Promise.resolve(new Response('', { status: 200 }));
}
return origFetch.apply(this, arguments);
};
})();
/* ----------------------------------------------------------
* LAYER 4 SEARCH ENGINE LINK CLEANING
* ---------------------------------------------------------- */
(function () {
function unwrap(a) {
const h = a.href || '';
if (/\bgoogle\.[a-z.]+\/url\b/.test(h)) {
try { const u = new URL(h); const d = u.searchParams.get('q') || u.searchParams.get('url'); if (d) { a.href = d; stats.cleanedLinks++; } } catch (_) {}
} else if (/\bbing\.com\//.test(h)) {
const r = a.getAttribute('data-href'); if (r) { a.href = r; stats.cleanedLinks++; }
} else if (/\bsearch\.yahoo\.com\//.test(h)) {
try { const u = new URL(h); const d = u.searchParams.get('url'); if (d) { a.href = decodeURIComponent(d); stats.cleanedLinks++; } } catch (_) {}
} else if (/\bduckduckgo\.com\//.test(h)) {
const c = cleanURL(h); if (c !== h) { a.href = c; stats.cleanedLinks++; }
}
}
document.addEventListener('mousedown', e => { const a = e.target.closest('a[href]'); if (a) unwrap(a); }, true);
document.addEventListener('DOMContentLoaded', () => document.querySelectorAll('a[href]').forEach(unwrap));
})();
/* ----------------------------------------------------------
* LAYER 5 CLIENTRECTS SPOOFING
* ---------------------------------------------------------- */
(function () {
const SEED = Math.random() * 1000;
const MAG = 0.1;
function noisyRect(rect) {
const n = noise(SEED + rect.left + rect.top, MAG);
return {
top: rect.top+n, bottom: rect.bottom+n, left: rect.left+n,
right: rect.right+n, width: rect.width, height: rect.height,
x: rect.x+n, y: rect.y+n, toJSON: () => ({}),
};
}
function isProbe(el) {
const s = el.style;
if (s) {
const w = parseFloat(s.width), h = parseFloat(s.height);
if (!isNaN(w) && !isNaN(h) && w < 4 && h < 4) return true;
const left = parseFloat(s.left), top = parseFloat(s.top);
if (!isNaN(left) && (left < -100 || left > 9000)) return true;
if (!isNaN(top) && (top < -100 || top > 9000)) return true;
}
if (!document.documentElement.contains(el)) return true;
return false;
}
const origBCR = Element.prototype.getBoundingClientRect;
Element.prototype.getBoundingClientRect = function () {
const r = origBCR.call(this);
if (!isProbe(this)) return r;
stats.spoofedRects++; return noisyRect(r);
};
const origGCR = Element.prototype.getClientRects;
Element.prototype.getClientRects = function () {
const r = origGCR.call(this);
if (!isProbe(this)) return r;
stats.spoofedRects++;
const n = Array.from(r).map(noisyRect); n.item = i => n[i]; return n;
};
const origRBCR = Range.prototype.getBoundingClientRect;
Range.prototype.getBoundingClientRect = function () { return noisyRect(origRBCR.call(this)); };
const origRCR = Range.prototype.getClientRects;
Range.prototype.getClientRects = function () {
const r = Array.from(origRCR.call(this)).map(noisyRect); r.item = i => r[i]; return r;
};
})();
/* ----------------------------------------------------------
* LAYER 6 FONT FINGERPRINTING
* ---------------------------------------------------------- */
(function () {
const SEED = Math.random() * 9999;
const MAG = 0.5;
const orig = CanvasRenderingContext2D.prototype.measureText;
CanvasRenderingContext2D.prototype.measureText = function (text) {
const m = orig.call(this, text);
const n = noise(SEED + text.length, MAG);
return new Proxy(m, {
get(t, p) {
if (p === 'width') return t.width + n;
const v = t[p]; return typeof v === 'function' ? v.bind(t) : v;
},
});
};
})();
/* ----------------------------------------------------------
* LAYER 7 WEBGPU FINGERPRINTING
* ---------------------------------------------------------- */
(function () {
if (!navigator.gpu) return;
const origReq = navigator.gpu.requestAdapter.bind(navigator.gpu);
navigator.gpu.requestAdapter = async function (...args) {
const adapter = await origReq(...args);
if (!adapter) return adapter;
const masked = { vendor: 'generic', architecture: 'unknown', device: '', description: '' };
adapter.requestAdapterInfo = async () => masked;
try { Object.defineProperty(adapter, 'info', { get: () => masked, configurable: true }); } catch (_) {}
return adapter;
};
})();
/* ----------------------------------------------------------
* LAYER 8 WEBRTC HOST CANDIDATE FILTERING
* ---------------------------------------------------------- */
(function () {
if (typeof RTCPeerConnection === 'undefined') return;
const Orig = window.RTCPeerConnection;
function Safe(config, constraints) {
const pc = new Orig(config, constraints);
const origAEL = pc.addEventListener.bind(pc);
pc.addEventListener = function (type, handler, ...rest) {
if (type === 'icecandidate') {
const wrapped = function (e) {
if (e.candidate && /typ host/.test(e.candidate.candidate || '')) {
stats.blockedRTCLeaks++; return;
}
handler.call(this, e);
};
return origAEL(type, wrapped, ...rest);
}
return origAEL(type, handler, ...rest);
};
return pc;
}
Safe.prototype = Orig.prototype;
Object.setPrototypeOf(Safe, Orig);
try { window.RTCPeerConnection = Safe; } catch (_) {}
})();
/* ----------------------------------------------------------
* LAYER 9 CLIENT HINTS / USERAGENTDATA
* ---------------------------------------------------------- */
(function () {
if (!navigator.userAgentData) return;
const BRANDS = [
{ brand: 'Chromium', version: '120' },
{ brand: 'Not=A?Brand', version: '24' },
];
const fake = {
brands: BRANDS, mobile: false, platform: 'Windows',
getHighEntropyValues: async () => ({
brands: BRANDS, mobile: false, platform: 'Windows',
platformVersion: '10.0.0', architecture: 'x86', bitness: '64',
model: '', uaFullVersion: '120.0.0.0',
fullVersionList: BRANDS.map(b => ({ brand: b.brand, version: b.version + '.0.0.0' })),
}),
toJSON() { return { brands: BRANDS, mobile: false, platform: 'Windows' }; },
};
try {
Object.defineProperty(Navigator.prototype, 'userAgentData', {
get: () => fake, configurable: true,
});
} catch (_) {}
})();
/* ----------------------------------------------------------
* LAYER 10 TIMEZONE & INTL SPOOFING (FIXED v2.7)
*
* ROOT CAUSE OF v2.6 BREAKAGE:
* FakeDTF was forcing FAKE_LOCALE = 'en-US' on every
* Intl.DateTimeFormat call globally, ignoring what the
* site passed. This broke:
* - Non-English sites rendering dates/times
* - Currency formatting (e.g. showing $ instead of €)
* - Number formatting with locale-specific separators
* - Any site using Intl for i18n
* Similarly, toLocaleString was overriding locale to
* en-US unconditionally, breaking the same things.
*
* THE FIX:
* - Caller's locale is always passed through unchanged.
* - Only timeZone is injected / overridden.
* - resolvedOptions() still returns the real locale the
* formatter was created with — only timeZone is patched.
* - toLocaleString family only injects timeZone into
* options, never touches the locale argument.
* ---------------------------------------------------------- */
(function () {
const FAKE_TZ = 'America/New_York';
const FAKE_TZ_OFFSET = 300; // UTC-5 (America/New_York standard time)
// ── Date.getTimezoneOffset() ─────────────────────────
try {
Date.prototype.getTimezoneOffset = function () {
return FAKE_TZ_OFFSET;
};
} catch (_) {}
// ── Intl.DateTimeFormat ──────────────────────────────
// Only spoof timeZone. Pass caller's locale through as-is.
try {
const OrigDTF = Intl.DateTimeFormat;
function FakeDTF(locale, options) {
// Preserve whatever locale the caller passed —
// only inject our fake timezone into options.
const safeOptions = Object.assign({}, options || {}, { timeZone: FAKE_TZ });
const instance = new OrigDTF(locale, safeOptions);
// Patch resolvedOptions to report fake timezone only.
// All other fields (locale, calendar, etc.) are real.
const origResolved = instance.resolvedOptions.bind(instance);
instance.resolvedOptions = function () {
const real = origResolved();
return Object.assign({}, real, { timeZone: FAKE_TZ });
};
return instance;
}
FakeDTF.prototype = OrigDTF.prototype;
FakeDTF.supportedLocalesOf = OrigDTF.supportedLocalesOf;
Object.setPrototypeOf(FakeDTF, OrigDTF);
try { Intl.DateTimeFormat = FakeDTF; } catch (_) {}
} catch (_) {}
// ── Date toLocale* methods ───────────────────────────
// Only inject timeZone. Never touch the locale argument.
['toLocaleString', 'toLocaleDateString', 'toLocaleTimeString'].forEach(method => {
try {
const orig = Date.prototype[method];
Date.prototype[method] = function (locale, options) {
// Pass locale through unchanged —
// only add timeZone to the options object.
const safeOptions = Object.assign({}, options || {}, { timeZone: FAKE_TZ });
return orig.call(this, locale, safeOptions);
};
} catch (_) {}
});
})();
/* ----------------------------------------------------------
* LAYER 11 NETWORK INFORMATION API SPOOFING
* ---------------------------------------------------------- */
(function () {
try {
const fakeConnection = {
type: 'wifi',
effectiveType: '4g',
downlink: 10,
downlinkMax: Infinity,
rtt: 50,
saveData: false,
onchange: null,
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => true,
};
Object.defineProperty(Navigator.prototype, 'connection', {
get: () => fakeConnection, configurable: true, enumerable: true,
});
Object.defineProperty(Navigator.prototype, 'mozConnection', {
get: () => fakeConnection, configurable: true,
});
Object.defineProperty(Navigator.prototype, 'webkitConnection', {
get: () => fakeConnection, configurable: true,
});
} catch (_) {}
})();
/* ----------------------------------------------------------
* LAYER 12 SPEECHSYNTHESIS SPOOFING (FIXED v2.7)
*
* ROOT CAUSE OF v2.6 BREAKAGE:
* Blocking the voiceschanged event caused any site that
* loads TTS voices asynchronously to hang forever waiting
* for the event that signals voices are ready to use.
* The event block was overly aggressive — fingerprinters
* can still be defeated by simply returning an empty list
* from getVoices() without touching the event system.
*
* THE FIX:
* - voiceschanged event listener blocking removed entirely.
* - getVoices() still returns [] so voice enumeration
* fingerprinting is defeated.
* - Sites using TTS legitimately can still detect when
* the API is ready; they just get no voices back.
* ---------------------------------------------------------- */
(function () {
try {
if (typeof SpeechSynthesis === 'undefined') return;
// Return empty voice list — prevents voice enumeration
// fingerprinting without blocking the event system.
SpeechSynthesis.prototype.getVoices = function () {
return [];
};
} catch (_) {}
})();
/* ----------------------------------------------------------
* END OF PRIVACY SHIELD v2.7
* ---------------------------------------------------------- */
})();