Skip to content

Instantly share code, notes, and snippets.

@a904guy
Last active August 14, 2025 20:30
Show Gist options
  • Select an option

  • Save a904guy/068e4f980907a5a3270291c554308712 to your computer and use it in GitHub Desktop.

Select an option

Save a904guy/068e4f980907a5a3270291c554308712 to your computer and use it in GitHub Desktop.
All-in-one stealth JS patch for anti-fingerprinting.
/*!
* Ava Stealth FP Patch - Combined Edition
* Version: 2025-08-13
* Injection: document-start in the main world. Idempotent guards prevent double install.
* Purpose: Reduce fingerprint surface area and stabilize identity for embedded browsers or webviews.
*
* Coverage summary:
* 1) Navigator and UA identity
* - navigator.userAgent, userAgentData (brands, platform, mobile)
* - platform, language, languages, vendor, appName, appVersion, productSub, vendorSub
* - doNotTrack, deviceMemory, hardwareConcurrency, maxTouchPoints, cookieEnabled, pdfViewerEnabled
* - webdriver set to undefined
* - window.chrome stub (runtime, app, webstore), legacy chrome.loadTimes and chrome.csi stubs
*
* 2) Permissions and notification surfaces
* - navigator.permissions.query normalization for notifications
* - Notification.permission forced stable, requestPermission returns "default"
*
* 3) Battery and network info
* - navigator.getBattery() stable values
* - NetworkInformation: effectiveType, downlink, rtt, saveData stabilized
*
* 4) Storage and quota
* - navigator.storage.estimate() returns rounded usage and quota
*
* 5) Plugins and mimeTypes
* - Stable PluginArray and MimeTypeArray shapes with PDF viewer entries
*
* 6) Screen and window geometry
* - screen.width, height, availWidth, availHeight, colorDepth
* - devicePixelRatio
* - outerWidth, outerHeight best effort
* - screenX, screenY, availTop, availLeft
* - screen.orientation angle and type
* - visualViewport rounding for width, height, offsets, scale
*
* 7) Timezone and Intl
* - Intl.DateTimeFormat.resolvedOptions().timeZone
* - Intl.NumberFormat, PluralRules, Collator normalized fields
*
* 8) CSS and media queries
* - window.matchMedia for prefers-color-scheme, prefers-reduced-motion, contrast, color-gamut, dynamic-range
* - CSS.supports filter for high entropy color and HDR features
*
* 9) Geometry probes
* - getBoundingClientRect optional tiny jitter
* - getClientRects rounding or tiny jitter
*
* 10) Canvas and text metrics
* - 2D canvas getImageData noise injection
* - toDataURL and toBlob side effects to ensure noise is sampled
* - measureText width fuzzing
* - OffscreenCanvas 2D path parity
*
* 11) WebGL identity and caps
* - WebGL vendor and renderer spoof
* - Hide WEBGL_debug_renderer_info
* - Normalize supported extensions list
* - Normalize shader precision and key capability limits
*
* 12) Audio fingerprinting
* - AudioContext sampleRate guard
* - AnalyserNode.getByteFrequencyData shaping
* - OfflineAudioContext.startRendering output noise
*
* 13) WebRTC leak reduction
* - Sanitize SDP to prefer relay and strip host candidates
* - Filter onicecandidate events to relay only
*
* 14) Media devices
* - mediaDevices.enumerateDevices fallback shape
* - getUserMedia strips exact deviceId hints before permission
*
* 15) Speech and input devices
* - speechSynthesis.getVoices stable fallback
* - navigator.getGamepads stable array shape
* - navigator.keyboard.getLayoutMap returns stable QWERTY map
*
* 16) WebGPU
* - navigator.gpu.requestAdapter returns null to reduce entropy
*
* 17) Performance surfaces
* - performance.now jitter or rounding
* - performance.timeOrigin rounded
* - performance.memory stabilized
* - getEntries*, getEntriesByType, getEntriesByName rounding and size field removal
* - PerformanceObserver.observe filters resource entries
* - requestIdleCallback wrapper with stable timeRemaining
*
* 18) History and referrer
* - history.length floor to plausible value
* - document.referrer empty
*
* 19) SVG and path metrics
* - SVGPathElement.getTotalLength tiny deterministic offset
*
* 20) Media capabilities and codecs
* - HTMLMediaElement.canPlayType returns common Chrome style answers
* - navigator.mediaCapabilities.decodingInfo optimistic, stable
* - EME requestMediaKeySystemAccess reduces to Widevine or rejects uncommon systems
*
* 21) Clipboard API
* - readText and writeText guarded to avoid leaking environment via errors
*
* 22) Document encoding
* - document.characterSet and document.charset set to UTF-8
*
* Configuration:
* - CFG object controls identity and toggles
* ua, platform, brands, lang, timezone, doNotTrack, maxTouchPoints, hardwareConcurrency, deviceMemory,
* colorDepth, dpr, screen.{width,height,availWidth,availHeight}, webglVendor, webglRenderer,
* enableCanvasNoise, enableAudioNoise, enablePerfJitter, stablePerOrigin
* - ENABLE flags gate optional modules
* cssMedia, geometryJitter, fonts, offscreenCanvas, webglCaps, mediaCodecs,
* perfEntries, keyboardLayout, legacyNavFields, screenExtras
*
* Usage:
* - Inject at document-start before any site script. For Qt use QWebEngineScript.DocumentCreation. For CEF use a preload or extension.
* - Keep CFG stable per persona to avoid linkability across visits.
* - Modules are guarded and safe to include multiple times in the same context.
*
* Host level hardening recommended outside JS:
* - Engine flags: force WebRTC to public interface only or relay only
* - Normalize HTTP headers and UA hints: Accept-Language, DNT, Sec-CH-UA family
* - Lock TLS profile, ALPN order, cipher suites per persona via proxy or engine config
* - Fix OS font list by bundling a known font pack
* - Route DNS and all traffic through a fixed proxy or gateway
*
* Caveats:
* - Overriding too much can break feature detection or playback on some sites. Toggle modules as needed.
* - Stability matters. Frequent identity changes can increase linkability risk.
*/
(() => {
if (globalThis.__ava_stealth_installed) return;
Object.defineProperty(globalThis, "__ava_stealth_installed", { value: true });
// -------- Config --------
const CFG = {
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
platform: "Win32",
brands: [
{ brand: "Chromium", version: "123" },
{ brand: "Not.A/Brand", version: "24" },
{ brand: "Google Chrome", version: "123" }
],
lang: ["en-US", "en"],
timezone: "America/New_York",
doNotTrack: "1",
maxTouchPoints: 0,
hardwareConcurrency: 8,
deviceMemory: 8,
colorDepth: 24,
dpr: 1,
screen: { width: 1920, height: 1080, availWidth: 1920, availHeight: 1040 },
webglVendor: "Intel Inc.",
webglRenderer: "Intel Iris OpenGL Engine",
enableCanvasNoise: true,
enableAudioNoise: true,
enablePerfJitter: true,
stablePerOrigin: true,
// module toggles
cssMedia: true,
geometryJitter: false, // set true if you accept tiny layout noise
fonts: true,
offscreenCanvas: true,
webglCaps: true,
mediaCodecs: true,
perfEntries: true,
keyboardLayout: true,
legacyNavFields: true,
screenExtras: true
};
// Map toggles to ENABLE for code that expects it
const ENABLE = {
cssMedia: CFG.cssMedia,
geometryJitter: CFG.geometryJitter,
fonts: CFG.fonts,
offscreenCanvas: CFG.offscreenCanvas,
webglCaps: CFG.webglCaps,
mediaCodecs: CFG.mediaCodecs,
perfEntries: CFG.perfEntries,
keyboardLayout: CFG.keyboardLayout,
legacyNavFields: CFG.legacyNavFields,
screenExtras: CFG.screenExtras
};
// -------- Utilities --------
const Rand = (() => {
const seedStr = CFG.stablePerOrigin
? (location.origin || location.host || "default") + "|" + CFG.ua
: (Math.random() + "|" + Date.now());
let h = 2166136261 >>> 0;
for (let i = 0; i < seedStr.length; i++) { h ^= seedStr.charCodeAt(i); h = Math.imul(h, 16777619); }
const next = () => {
h ^= h << 13; h >>>= 0;
h ^= h >>> 17; h >>>= 0;
h ^= h << 5; h >>>= 0;
return (h >>> 0) / 4294967296;
};
return {
float: next,
int: (min, max) => Math.floor(next() * (max - min + 1)) + min,
smallNoise: (scale = 1e-5) => (next() - 0.5) * 2 * scale
};
})();
const defineGetter = (obj, prop, getter) => {
try {
const desc = Object.getOwnPropertyDescriptor(obj, prop);
if (desc && !desc.configurable) return;
Object.defineProperty(obj, prop, { configurable: true, enumerable: desc ? desc.enumerable : true, get: getter });
} catch {}
};
const defineValue = (obj, prop, value) => {
try {
const desc = Object.getOwnPropertyDescriptor(obj, prop);
if (desc && !desc.configurable) return;
Object.defineProperty(obj, prop, { configurable: true, enumerable: desc ? desc.enumerable : true, value });
} catch {}
};
const override = (obj, prop, fnFactory) => {
try {
const orig = obj[prop];
const wrapped = fnFactory(orig);
defineValue(obj, prop, wrapped);
return { orig, wrapped };
} catch { return null; }
};
// -------- Navigator base props --------
try {
const navProto = Navigator.prototype;
defineGetter(navProto, "userAgent", () => CFG.ua);
defineGetter(navProto, "platform", () => CFG.platform);
defineGetter(navProto, "language", () => CFG.lang[0]);
defineGetter(navProto, "languages", () => CFG.lang.slice());
defineGetter(navProto, "hardwareConcurrency", () => CFG.hardwareConcurrency);
defineGetter(navProto, "deviceMemory", () => CFG.deviceMemory);
defineGetter(navProto, "maxTouchPoints", () => CFG.maxTouchPoints);
defineGetter(navProto, "doNotTrack", () => CFG.doNotTrack);
defineGetter(navProto, "webdriver", () => undefined);
const uaData = {
brands: CFG.brands.map(b => ({ brand: b.brand, version: String(b.version) })),
mobile: false,
platform: CFG.platform.includes("Win") ? "Windows" : (CFG.platform.includes("Mac") ? "macOS" : "Linux"),
getHighEntropyValues: async (hints) => {
const out = {
architecture: "x86",
bitness: "64",
model: "",
platform: CFG.platform.includes("Win") ? "Windows" : (CFG.platform.includes("Mac") ? "macOS" : "Linux"),
platformVersion: "15.0.0",
uaFullVersion: "123.0.0.0",
fullVersionList: CFG.brands.map(b => ({ brand: b.brand, version: "123.0.0.0" }))
};
if (!Array.isArray(hints)) return out;
return Object.fromEntries(hints.map(k => [k, out[k]]));
},
toJSON() { return { brands: this.brands, mobile: this.mobile, platform: this.platform }; }
};
defineGetter(navProto, "userAgentData", () => uaData);
if (!("chrome" in globalThis)) {
defineValue(globalThis, "chrome", { runtime: {}, app: {}, webstore: {} });
}
} catch {}
// -------- Permissions API --------
try {
const permissions = navigator.permissions;
if (permissions && permissions.query) {
override(permissions, "query", (orig) => function(p) {
try {
if (p && p.name === "notifications") {
return Promise.resolve({ state: Notification.permission });
}
} catch {}
return orig.apply(this, arguments);
});
}
} catch {}
// -------- Battery API --------
try {
if (typeof navigator.getBattery === "function") {
override(navigator, "getBattery", (_orig) => async function() {
return {
charging: true,
chargingTime: 0,
dischargingTime: Infinity,
level: 1,
onchargingchange: null,
onlevelchange: null,
addEventListener() {},
removeEventListener() {},
dispatchEvent() { return false; }
};
});
}
} catch {}
// -------- Network Information API --------
try {
const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (conn) {
const proto = Object.getPrototypeOf(conn) || conn;
defineGetter(proto, "effectiveType", () => "4g");
defineGetter(proto, "downlink", () => 10);
defineGetter(proto, "rtt", () => 50);
defineGetter(proto, "saveData", () => false);
}
} catch {}
// -------- Storage API estimate --------
try {
if (navigator.storage && navigator.storage.estimate) {
override(navigator.storage, "estimate", (orig) => async function() {
const e = await orig.call(this);
const quota = e && e.quota ? e.quota : 1024 * 1024 * 1024;
return { usage: Math.floor(quota * 0.05), quota };
});
}
} catch {}
// -------- Plugins and MimeTypes --------
try {
const makeArrayLike = (arr, name) => {
Object.defineProperty(arr, "item", { value: (i) => arr[i] || null });
Object.defineProperty(arr, "namedItem", { value: (n) => arr.find(x => x.name === n) || null });
Object.defineProperty(arr, "refresh", { value: () => {} });
Object.defineProperty(arr, "toString", { value: () => `[object ${name}]` });
return arr;
};
const mimeTypes = makeArrayLike([], "MimeTypeArray");
const plugins = makeArrayLike([], "PluginArray");
const addPlugin = (name, filename, description, mimes) => {
const p = { name, filename, description, length: mimes.length, 0: null };
mimes.forEach((mt, idx) => {
const mtObj = { type: mt.type, suffixes: mt.suffixes || "", description: mt.description || "" };
mimeTypes.push(mtObj);
p[idx] = mtObj;
});
plugins.push(p);
};
addPlugin("Chrome PDF Viewer", "internal-pdf-viewer", "Portable Document Format", [{ type: "application/pdf", suffixes: "pdf", description: "" }]);
addPlugin("Chromium PDF Viewer", "internal-pdf-viewer", "Portable Document Format", [{ type: "application/pdf", suffixes: "pdf", description: "" }]);
addPlugin("PDF Viewer", "internal-pdf-viewer", "Portable Document Format", [{ type: "application/pdf", suffixes: "pdf", description: "" }]);
defineGetter(Navigator.prototype, "plugins", () => plugins);
defineGetter(Navigator.prototype, "mimeTypes", () => mimeTypes);
} catch {}
// -------- Screen, DPR, window sizing --------
try {
if (typeof Screen !== "undefined" && Screen.prototype) {
const scr = Screen.prototype;
defineGetter(scr, "width", () => CFG.screen.width);
defineGetter(scr, "height", () => CFG.screen.height);
defineGetter(scr, "availWidth", () => CFG.screen.availWidth);
defineGetter(scr, "availHeight", () => CFG.screen.availHeight);
defineGetter(scr, "colorDepth", () => CFG.colorDepth);
}
} catch {}
try {
defineGetter(globalThis, "devicePixelRatio", () => CFG.dpr);
} catch {}
try {
defineGetter(globalThis, "outerWidth", () => innerWidth + 16);
defineGetter(globalThis, "outerHeight", () => innerHeight + 88);
} catch {}
// -------- Timezone hints --------
try {
const intlProto = Intl.DateTimeFormat.prototype;
override(intlProto, "resolvedOptions", (orig) => function() {
const r = orig.call(this);
if (!r || typeof r !== "object") return r;
return Object.assign({}, r, { timeZone: CFG.timezone });
});
} catch {}
// -------- Canvas fingerprint noise --------
try {
if (CFG.enableCanvasNoise) {
const addNoise2D = (ctx) => {
const _getImageData = ctx.getImageData;
ctx.getImageData = function() {
const data = _getImageData.apply(this, arguments);
for (let i = 0; i < data.data.length; i += 4) {
data.data[i] = Math.min(255, Math.max(0, data.data[i] + Rand.smallNoise(0.6) * 255));
}
return data;
};
const _toDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function() {
try { ctx.getImageData(0, 0, 1, 1); } catch {}
return _toDataURL.apply(this, arguments);
};
const _toBlob = HTMLCanvasElement.prototype.toBlob;
if (_toBlob) {
HTMLCanvasElement.prototype.toBlob = function() {
try { ctx.getImageData(0, 0, 1, 1); } catch {}
return _toBlob.apply(this, arguments);
};
}
const _measureText = ctx.measureText;
ctx.measureText = function() {
const m = _measureText.apply(this, arguments);
try {
const w = m.width + Rand.smallNoise(0.01);
return new Proxy(m, { get: (t, p) => p === "width" ? w : t[p] });
} catch { return m; }
};
};
const _getContext = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function(type, opts) {
const ctx = _getContext.call(this, type, opts);
if (type === "2d" && ctx && !ctx.__ava_patched) {
addNoise2D(ctx);
defineValue(ctx, "__ava_patched", true);
}
return ctx;
};
}
} catch {}
// -------- WebGL spoof --------
try {
const patchGL = (proto) => {
if (!proto) return;
const PARAM_VENDOR = 0x1F00;
const PARAM_RENDERER = 0x1F01;
const UNMASKED_VENDOR_WEBGL = 0x9245;
const UNMASKED_RENDERER_WEBGL = 0x9246;
override(proto, "getParameter", (orig) => function(p) {
if (p === PARAM_VENDOR || p === UNMASKED_VENDOR_WEBGL) return CFG.webglVendor;
if (p === PARAM_RENDERER || p === UNMASKED_RENDERER_WEBGL) return CFG.webglRenderer;
return orig.apply(this, arguments);
});
override(proto, "getExtension", (orig) => function(name) {
if (name === "WEBGL_debug_renderer_info") return null;
return orig.apply(this, arguments);
});
};
if (globalThis.WebGLRenderingContext) patchGL(WebGLRenderingContext.prototype);
if (globalThis.WebGL2RenderingContext) patchGL(WebGL2RenderingContext.prototype);
} catch {}
// -------- Audio fingerprint noise --------
try {
if (CFG.enableAudioNoise) {
const addNoiseToBuffer = (buf) => {
try {
const len = buf.length;
for (let ch = 0; ch < buf.numberOfChannels; ch++) {
const data = buf.getChannelData(ch);
for (let i = 0; i < len; i++) data[i] += Rand.smallNoise(1e-6);
}
} catch {}
return buf;
};
const wrapCtx = (proto) => {
if (!proto) return;
override(proto, "createAnalyser", (orig) => function() {
const node = orig.apply(this, arguments);
const _getByteFreqData = node.getByteFrequencyData;
node.getByteFrequencyData = function(arr) {
_getByteFreqData.call(this, arr);
for (let i = 0; i < arr.length; i++) arr[i] = Math.min(255, Math.max(0, arr[i] + Rand.int(-1, 1)));
};
return node;
});
};
if (globalThis.AudioContext) wrapCtx(AudioContext.prototype);
if (globalThis.webkitAudioContext) wrapCtx(webkitAudioContext.prototype);
const OAC = globalThis.OfflineAudioContext || globalThis.webkitOfflineAudioContext;
if (OAC && OAC.prototype && OAC.prototype.startRendering) {
override(OAC.prototype, "startRendering", (orig) => async function() {
const res = await orig.apply(this, arguments);
try { addNoiseToBuffer(res); } catch {}
return res;
});
}
}
} catch {}
// -------- WebRTC leak reduction --------
try {
if ("RTCPeerConnection" in globalThis) {
const sanitizeSDP = (sdp) => {
if (typeof sdp !== "string") return sdp;
return sdp
.split("\n")
.filter(line => !/^a=candidate:/.test(line) || / typ relay /.test(line))
.map(line => line.replace(/(c=IN IP4 )\d+\.\d+\.\d+\.\d+/, "$10.0.0.0"))
.join("\n");
};
const PC = RTCPeerConnection;
const pcProto = PC.prototype;
override(pcProto, "createOffer", (orig) => function() {
return orig.apply(this, arguments).then(desc => {
desc.sdp = sanitizeSDP(desc.sdp);
return desc;
});
});
override(pcProto, "setLocalDescription", (orig) => function(desc) {
if (desc && typeof desc.sdp === "string") desc.sdp = sanitizeSDP(desc.sdp);
return orig.apply(this, arguments);
});
const _addEventListener = pcProto.addEventListener;
pcProto.addEventListener = function(type, listener, opts) {
if (type === "icecandidate" && typeof listener === "function") {
const wrapped = (e) => {
if (e && e.candidate && e.candidate.candidate && !/ typ relay /.test(e.candidate.candidate)) return;
listener(e);
};
return _addEventListener.call(this, type, wrapped, opts);
}
return _addEventListener.call(this, type, listener, opts);
};
const _onicecandidate = Object.getOwnPropertyDescriptor(pcProto, "onicecandidate");
if (_onicecandidate && _onicecandidate.configurable) {
Object.defineProperty(pcProto, "onicecandidate", {
configurable: true,
get() { return this.__ava_onice || null; },
set(fn) {
this.__ava_onice = typeof fn === "function" ? (e) => {
if (e && e.candidate && e.candidate.candidate && !/ typ relay /.test(e.candidate.candidate)) return;
fn(e);
} : fn;
}
});
}
}
} catch {}
// -------- MediaDevices and device enumeration --------
try {
if (navigator.mediaDevices) {
override(navigator.mediaDevices, "enumerateDevices", (orig) => async function() {
const base = await orig.call(this);
const fake = [
{ deviceId: "default", kind: "audioinput", label: "", groupId: "default" },
{ deviceId: "default", kind: "audiooutput", label: "", groupId: "default" },
{ deviceId: "default", kind: "videoinput", label: "", groupId: "default" }
];
return Array.isArray(base) && base.length ? base : fake;
});
if (navigator.mediaDevices.getUserMedia) {
override(navigator.mediaDevices, "getUserMedia", (orig) => function(constraints) {
const c = JSON.parse(JSON.stringify(constraints || {}));
["audio", "video"].forEach(k => {
if (c[k] && typeof c[k] === "object") {
if ("deviceId" in c[k]) delete c[k].deviceId;
}
});
return orig.call(this, c);
});
}
}
} catch {}
// -------- SpeechSynthesis voices --------
try {
if ("speechSynthesis" in globalThis && speechSynthesis.getVoices) {
override(speechSynthesis, "getVoices", (orig) => function() {
const list = orig.call(this) || [];
if (list.length) return list;
return [
{ name: "Microsoft David - English", lang: "en-US", localService: true, voiceURI: "Microsoft David - English", default: true }
];
});
}
} catch {}
// -------- Gamepads --------
try {
if ("getGamepads" in navigator) {
override(navigator, "getGamepads", (orig) => function() {
const pads = orig.call(this);
return pads && pads.length ? pads : [null, null, null, null];
});
}
} catch {}
// -------- WebGPU --------
try {
if ("gpu" in navigator) {
defineGetter(Navigator.prototype, "gpu", () => ({
requestAdapter: async () => null
}));
}
} catch {}
// -------- Performance jitter --------
try {
if (CFG.enablePerfJitter && performance && performance.now) {
override(performance, "now", (orig) => function() {
return orig.call(this) + Rand.smallNoise(0.02);
});
if (globalThis.Performance && Performance.prototype && Performance.prototype.now) {
override(Performance.prototype, "now", (orig) => function() {
return orig.call(this) + Rand.smallNoise(0.02);
});
}
}
} catch {}
// -------- Navigator connection toString shape --------
try {
const toStringPatched = (proto, name) => {
if (!proto) return;
override(proto, "toString", () => function() { return `[object ${name}]`; });
};
if (navigator.connection) toStringPatched(Object.getPrototypeOf(navigator.connection), "NetworkInformation");
} catch {}
// -------- Document focus and visibility edge cases --------
try {
if (typeof document.hidden === "undefined") defineGetter(Document.prototype, "hidden", () => false);
if (typeof document.visibilityState === "undefined") defineGetter(Document.prototype, "visibilityState", () => "visible");
} catch {}
// -------- Misc small surfaces --------
try {
if (!("mediaCapabilities" in navigator)) {
defineValue(navigator, "mediaCapabilities", {
decodingInfo: async () => ({ supported: true, powerEfficient: true, smooth: true }),
encodingInfo: async () => ({ supported: true, powerEfficient: true, smooth: true })
});
}
defineGetter(Navigator.prototype, "vendor", () => "Google Inc.");
defineGetter(Navigator.prototype, "product", () => "Gecko");
} catch {}
try {
if (CFG.maxTouchPoints > 0 && !("ontouchstart" in globalThis)) {
defineValue(globalThis, "ontouchstart", null);
}
} catch {}
// --- CSS media queries normalization ---
if (ENABLE.cssMedia && "matchMedia" in window) {
const fixed = {
"(prefers-color-scheme: dark)": false,
"(prefers-contrast: more)": false,
"(prefers-reduced-motion: reduce)": false,
"(prefers-reduced-transparency: reduce)": false,
"(color-gamut: srgb)": true,
"(color-gamut: p3)": false,
"(color-gamut: rec2020)": false,
"(dynamic-range: standard)": true,
"(dynamic-range: high)": false,
"(forced-colors: active)": false,
"(monochrome)": false
};
const mm = window.matchMedia.bind(window);
window.matchMedia = function(q) {
const m = mm(q);
if (q in fixed) {
return new Proxy(m, {
get(t, p) {
if (p === "matches") return fixed[q];
if (p === "media") return q;
return t[p];
}
});
}
return m;
};
}
// --- Geometry jitter ---
if (ENABLE.geometryJitter) {
const jit = (v) => v + (Math.random() - 0.5) * 0.02;
const E = Element.prototype;
const origRect = E.getBoundingClientRect;
E.getBoundingClientRect = function() {
const r = origRect.apply(this, arguments);
return new DOMRectReadOnly(jit(r.x), jit(r.y), jit(r.width), jit(r.height));
};
}
// --- Font probing normalization ---
if (ENABLE.fonts) {
if (document.fonts && document.fonts.check) {
const baseCheck = document.fonts.check.bind(document.fonts);
document.fonts.check = function(spec, text) {
const ok = /(?:^|\s)(serif|sans-serif|monospace)(?:,|$)/i.test(spec);
return ok ? true : baseCheck("12px sans-serif", text);
};
}
if (document.fonts) {
const ffsProto = Object.getPrototypeOf(document.fonts);
if (ffsProto && !ffsProto.status) {
Object.defineProperty(ffsProto, "status", { get: () => "loaded" });
}
}
}
// --- OffscreenCanvas parity ---
if (ENABLE.offscreenCanvas && "OffscreenCanvas" in window) {
try {
const ocp = OffscreenCanvas.prototype;
const _getContext = ocp.getContext;
ocp.getContext = function(type, opts) {
const ctx = _getContext.call(this, type, opts);
if (type === "2d" && ctx && !ctx.__ava_patched) {
const _getImageData = ctx.getImageData;
ctx.getImageData = function() {
const data = _getImageData.apply(this, arguments);
for (let i = 0; i < data.data.length; i += 4) {
data.data[i] = Math.min(255, Math.max(0, data.data[i] + (Math.random() - 0.5) * 0.6));
}
return data;
};
Object.defineProperty(ctx, "__ava_patched", { value: true });
}
return ctx;
};
} catch {}
}
// --- WebGL caps normalization ---
if (ENABLE.webglCaps) {
const normCaps = (gl) => {
try {
const ORIG = {
getParameter: gl.getParameter.bind(gl),
getSupportedExtensions: gl.getSupportedExtensions && gl.getSupportedExtensions.bind(gl),
getShaderPrecisionFormat: gl.getShaderPrecisionFormat && gl.getShaderPrecisionFormat.bind(gl)
};
gl.getSupportedExtensions = () => ["OES_standard_derivatives", "OES_vertex_array_object", "OES_element_index_uint"];
gl.getShaderPrecisionFormat = (_type, _prec) => ({ rangeMin: 127, rangeMax: 127, precision: 23 });
gl.getParameter = (p) => {
const caps = {
0x0D33: 16384, // MAX_TEXTURE_SIZE
0x846D: 8, // MAX_VERTEX_ATTRIBS
0x8872: 16, // MAX_TEXTURE_IMAGE_UNITS
0x8C8A: 4096 // MAX_RENDERBUFFER_SIZE
};
if (p in caps) return caps[p];
return ORIG.getParameter(p);
};
} catch {}
};
if (window.WebGLRenderingContext) {
const _getCtx = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function(type, opts) {
const ctx = _getCtx.call(this, type, opts);
if ((type === "webgl" || type === "experimental-webgl") && ctx) normCaps(ctx);
return ctx;
};
}
}
// --- Media codecs normalization ---
if (ENABLE.mediaCodecs) {
const H = HTMLMediaElement.prototype;
const _cpt = H.canPlayType;
H.canPlayType = function(t) {
if (typeof t !== "string") return "";
if (/video\/mp4/i.test(t)) return "probably";
if (/audio\/mp4|audio\/aac/i.test(t)) return "probably";
if (/audio\/mpeg/i.test(t)) return "probably";
if (/video\/webm|audio\/webm/i.test(t)) return "maybe";
return "";
};
if (navigator.mediaCapabilities && navigator.mediaCapabilities.decodingInfo) {
const _di = navigator.mediaCapabilities.decodingInfo.bind(navigator.mediaCapabilities);
navigator.mediaCapabilities.decodingInfo = async (cfg) => {
const base = await _di(cfg).catch(() => null);
return base ? { ...base, powerEfficient: true, smooth: true, supported: true }
: { supported: true, powerEfficient: true, smooth: true };
};
}
}
// --- Performance entries rounding ---
if (ENABLE.perfEntries && performance) {
const round = (x) => Math.round(x * 10) / 10;
["getEntries", "getEntriesByType", "getEntriesByName"].forEach(fn => {
if (performance[fn]) {
const orig = performance[fn].bind(performance);
performance[fn] = function() {
const entries = orig.apply(this, arguments) || [];
return entries.map(e => {
const clone = {};
for (const k of Object.keys(e)) {
const v = e[k];
clone[k] = typeof v === "number" ? round(v) : v;
}
delete clone.serverTiming;
delete clone.transferSize;
delete clone.encodedBodySize;
delete clone.decodedBodySize;
return clone;
});
};
}
});
if ("timeOrigin" in performance) {
Object.defineProperty(performance, "timeOrigin", { get: () => Math.floor(Date.now() / 1000) * 1000 });
}
if (performance.memory) {
try {
const mem = performance.memory;
Object.defineProperty(performance, "memory", {
get: () => ({ jsHeapSizeLimit: mem.jsHeapSizeLimit, totalJSHeapSize: 200e6, usedJSHeapSize: 120e6 })
});
} catch {}
}
}
// --- Keyboard layout normalization ---
if (ENABLE.keyboardLayout && navigator.keyboard && navigator.keyboard.getLayoutMap) {
const _glm = navigator.keyboard.getLayoutMap.bind(navigator.keyboard);
navigator.keyboard.getLayoutMap = async () => {
const m = await _glm().catch(() => null);
if (!m) return new Map([["KeyQ", "q"], ["KeyW", "w"]]);
const map = new Map();
["KeyQ","KeyW","KeyE","KeyR","KeyT","KeyY","KeyU","KeyI","KeyO","KeyP",
"KeyA","KeyS","KeyD","KeyF","KeyG","KeyH","KeyJ","KeyK","KeyL",
"KeyZ","KeyX","KeyC","KeyV","KeyB","KeyN","KeyM","Space"].forEach(c => map.set(c, (c === "Space") ? " " : c.slice(-1).toLowerCase()));
return map;
};
}
// --- Legacy navigator fields ---
if (ENABLE.legacyNavFields) {
const np = Navigator.prototype;
Object.defineProperty(np, "appVersion", { get: () => "5.0 (" + (navigator.platform || "Win32") + ") AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" });
Object.defineProperty(np, "appName", { get: () => "Netscape" });
Object.defineProperty(np, "productSub", { get: () => "20030107" });
Object.defineProperty(np, "vendorSub", { get: () => "" });
Object.defineProperty(np, "pdfViewerEnabled", { get: () => true });
}
// --- Screen extras and window positions ---
if (ENABLE.screenExtras) {
if (typeof Screen !== "undefined" && Screen.prototype) {
const sp = Screen.prototype;
if (!("availLeft" in screen)) Object.defineProperty(sp, "availLeft", { get: () => 0 });
if (!("availTop" in screen)) Object.defineProperty(sp, "availTop", { get: () => 0 });
if (screen.orientation) {
const so = Object.getPrototypeOf(screen.orientation) || screen.orientation;
try { Object.defineProperty(so, "angle", { get: () => 0 }); } catch {}
try { Object.defineProperty(so, "type", { get: () => "landscape-primary" }); } catch {}
}
}
if (!("screenX" in window)) Object.defineProperty(window, "screenX", { get: () => 0 });
if (!("screenY" in window)) Object.defineProperty(window, "screenY", { get: () => 0 });
}
// ---- Utilities: native camouflage and rounding ----
const NativeSig = Symbol.for("__ava_native_sig__");
const makeNative = (fn, name = fn.name || "", length = fn.length) => {
try { Object.defineProperty(fn, "name", { value: name, configurable: true }); } catch {}
try { Object.defineProperty(fn, "length", { value: length, configurable: true }); } catch {}
try { Object.defineProperty(fn, NativeSig, { value: true }); } catch {}
return fn;
};
try {
const origToString = Function.prototype.toString;
Function.prototype.toString = makeNative(function toString() {
const fn = this;
try {
if (fn && fn[NativeSig]) {
const name = fn.name || "";
return `function ${name}() { [native code] }`;
}
} catch {}
return origToString.call(fn);
}, "toString", 0);
} catch {}
try {
const patchTag = /__ava_stealth/i;
const _prepare = Error.prepareStackTrace;
Error.prepareStackTrace = makeNative(function(err, frames) {
try {
frames = frames.filter(f => {
const file = (f && typeof f.getFileName === "function") ? f.getFileName() : null;
return !(file && patchTag.test(String(file)));
});
} catch {}
return _prepare ? _prepare(err, frames) : `${err}\n${frames.map(String).join("\n")}`;
}, "prepareStackTrace", 2);
} catch {}
const round01 = (x) => Math.round(x * 10) / 10;
// ---- CSS.supports and extended matchMedia hardening ----
try {
if ("CSS" in globalThis && CSS.supports) {
const orig = CSS.supports.bind(CSS);
CSS.supports = makeNative(function(prop, value) {
if (typeof prop === "string" && /(^|:)\s*(environment-blending|color-gamut|dynamic-range|display-p3)/i.test(prop)) {
return false;
}
return orig(prop, value);
}, "supports", 2);
}
if ("matchMedia" in window) {
const mm = window.matchMedia.bind(window);
const table = {
"(prefers-color-scheme: dark)": false,
"(prefers-color-scheme: light)": true,
"(prefers-reduced-motion: reduce)": false,
"(prefers-contrast: more)": false,
"(color-gamut: srgb)": true,
"(color-gamut: p3)": false,
"(dynamic-range: high)": false,
"(inverted-colors: inverted)": false,
"(forced-colors: active)": false
};
window.matchMedia = makeNative(function(q) {
const m = mm(q);
if (q in table) {
return new Proxy(m, {
get(t, p) {
if (p === "matches") return table[q];
if (p === "media") return q;
return t[p];
}
});
}
return m;
}, "matchMedia", 1);
}
} catch {}
// ---- Geometry extras ----
try {
const EP = Element.prototype;
if (EP.getClientRects) {
const _gcr = EP.getClientRects;
EP.getClientRects = makeNative(function() {
const list = _gcr.apply(this, arguments);
try {
const arr = [];
for (let i = 0; i < list.length; i++) {
const r = list[i];
arr.push(new DOMRectReadOnly(
Math.round(r.x * 10) / 10, Math.round(r.y * 10) / 10,
Math.round(r.width * 10) / 10, Math.round(r.height * 10) / 10
));
}
arr.item = (i) => arr[i] || null;
return arr;
} catch { return list; }
}, "getClientRects", 0);
}
if (globalThis.visualViewport) {
const vpProto = Object.getPrototypeOf(visualViewport);
["width","height","offsetLeft","offsetTop","pageLeft","pageTop","scale"].forEach(k => {
try {
const desc = Object.getOwnPropertyDescriptor(vpProto, k) || { configurable: true };
Object.defineProperty(vpProto, k, {
configurable: true,
get: makeNative(function() {
const v = (desc.get ? desc.get.call(this) : visualViewport[k]);
return typeof v === "number" ? round01(v) : v;
}, `get ${k}`, 0)
});
} catch {}
});
}
} catch {}
// ---- Intl normalization ----
try {
const fixResolved = (proto, fields) => {
if (!proto || !proto.resolvedOptions) return;
const orig = proto.resolvedOptions;
proto.resolvedOptions = makeNative(function() {
const r = orig.call(this);
for (const [k, v] of Object.entries(fields)) {
try { r[k] = v; } catch {}
}
return r;
}, "resolvedOptions", 0);
};
fixResolved(Intl.DateTimeFormat.prototype, { timeZone: CFG.timezone });
fixResolved(Intl.NumberFormat.prototype, { numberingSystem: "latn" });
fixResolved(Intl.PluralRules.prototype, { type: "cardinal" });
fixResolved(Intl.Collator.prototype, { usage: "sort", sensitivity: "variant" });
} catch {}
// ---- Clipboard API stubs ----
try {
if (navigator.clipboard) {
const cp = navigator.clipboard;
if (cp.readText) {
const _rt = cp.readText.bind(cp);
cp.readText = makeNative(function() {
return _rt().catch(() => Promise.resolve(""));
}, "readText", 0);
}
if (cp.writeText) {
const _wt = cp.writeText.bind(cp);
cp.writeText = makeNative(function(t) {
return _wt(t).catch(() => Promise.resolve());
}, "writeText", 1);
}
} else {
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: { readText: makeNative(async function(){ return ""; }, "readText", 0),
writeText: makeNative(async function(){}, "writeText", 1) }
});
}
} catch {}
// ---- Notification API consistency ----
try {
if ("Notification" in globalThis) {
const _perm = Object.getOwnPropertyDescriptor(Notification, "permission");
if (_perm && _perm.configurable) {
Object.defineProperty(Notification, "permission", { get: makeNative(() => "default", "get permission", 0) });
}
if (Notification.requestPermission) {
const _rp = Notification.requestPermission.bind(Notification);
Notification.requestPermission = makeNative(function(cb) {
const p = Promise.resolve("default");
if (typeof cb === "function") p.then(cb);
return p;
}, "requestPermission", 1);
}
}
} catch {}
// ---- History and referrer ----
try {
if ("length" in history) {
const d = Object.getOwnPropertyDescriptor(History.prototype, "length");
if (d && d.configurable) {
Object.defineProperty(History.prototype, "length", { get: makeNative(() => Math.max(2, window.history.length || 2), "get length", 0) });
}
}
if ("referrer" in Document.prototype) {
const dr = Object.getOwnPropertyDescriptor(Document.prototype, "referrer");
if (dr && dr.configurable) {
Object.defineProperty(Document.prototype, "referrer", { get: makeNative(() => "", "get referrer", 0) });
}
}
} catch {}
// ---- Legacy navigator fields and booleans ----
try {
const NP = Navigator.prototype;
if (!("cookieEnabled" in NP)) {
Object.defineProperty(NP, "cookieEnabled", { get: makeNative(() => true, "get cookieEnabled", 0) });
}
if (!("pdfViewerEnabled" in NP)) {
Object.defineProperty(NP, "pdfViewerEnabled", { get: makeNative(() => true, "get pdfViewerEnabled", 0) });
}
if (!("vendor" in NP)) {
Object.defineProperty(NP, "vendor", { get: makeNative(() => "Google Inc.", "get vendor", 0) });
}
if (!("appName" in NP)) {
Object.defineProperty(NP, "appName", { get: makeNative(() => "Netscape", "get appName", 0) });
}
} catch {}
// ---- Audio fingerprint extras ----
try {
const AC = globalThis.AudioContext || globalThis.webkitAudioContext;
if (AC && AC.prototype) {
const ACP = AC.prototype;
try {
Object.defineProperty(ACP, "sampleRate", { get: makeNative(function() { return 48000; }, "get sampleRate", 0) });
} catch {}
if (ACP.createAnalyser) {
const _ca = ACP.createAnalyser;
ACP.createAnalyser = makeNative(function() {
const node = _ca.apply(this, arguments);
try {
node.fftSize = 2048;
const _gbfd = node.getByteFrequencyData.bind(node);
node.getByteFrequencyData = makeNative(function(arr) {
_gbfd(arr);
for (let i = 0; i < arr.length; i++) arr[i] = Math.min(255, Math.max(0, arr[i]));
}, "getByteFrequencyData", 1);
} catch {}
return node;
}, "createAnalyser", 0);
}
}
} catch {}
// ---- SVG metrics ----
try {
if (globalThis.SVGPathElement && SVGPathElement.prototype && SVGPathElement.prototype.getTotalLength) {
const _gtl = SVGPathElement.prototype.getTotalLength;
SVGPathElement.prototype.getTotalLength = makeNative(function() {
const v = _gtl.apply(this, arguments);
return v + 0.0001;
}, "getTotalLength", 0);
}
} catch {}
// ---- Media EME flattening ----
try {
if (navigator.requestMediaKeySystemAccess) {
const _rmksa = navigator.requestMediaKeySystemAccess.bind(navigator);
navigator.requestMediaKeySystemAccess = makeNative(async function(ks, cfg) {
if (!/com\.widevine\.alpha/i.test(String(ks))) throw new DOMException("NotSupportedError", "NotSupportedError");
return _rmksa(ks, cfg);
}, "requestMediaKeySystemAccess", 2);
}
} catch {}
// ---- PerformanceObserver and entries rounding ----
try {
if (globalThis.PerformanceObserver) {
const POP = PerformanceObserver.prototype;
const _observe = POP.observe;
POP.observe = makeNative(function(opts) {
if (opts && Array.isArray(opts.entryTypes)) {
opts = { ...opts, entryTypes: opts.entryTypes.filter(t => t !== "resource") };
}
return _observe.call(this, opts);
}, "observe", 1);
}
if (performance) {
["getEntries", "getEntriesByType", "getEntriesByName"].forEach(fn => {
if (performance[fn]) {
const orig = performance[fn].bind(performance);
performance[fn] = makeNative(function() {
const arr = orig.apply(this, arguments) || [];
return arr.map(e => {
const o = {};
for (const k in e) {
const v = e[k];
o[k] = typeof v === "number" ? round01(v) : v;
}
delete o.transferSize;
delete o.encodedBodySize;
delete o.decodedBodySize;
return o;
});
}, fn, performance[fn].length || 0);
}
});
if ("now" in performance) {
const _now = performance.now.bind(performance);
performance.now = makeNative(function() { return round01(_now()); }, "now", 0);
}
}
if ("requestIdleCallback" in window) {
const ric = window.requestIdleCallback.bind(window);
window.requestIdleCallback = makeNative(function(cb, opts) {
return ric(function(deadline) {
const proxy = new Proxy(deadline, {
get(t, p) {
if (p === "timeRemaining") return makeNative(function(){ return 10; }, "timeRemaining", 0);
return t[p];
}
});
return cb(proxy);
}, opts);
}, "requestIdleCallback", 2);
}
} catch {}
// ---- Window.chrome legacy stubs ----
try {
if (!("chrome" in window)) {
Object.defineProperty(window, "chrome", { configurable: true, value: { runtime: {}, app: {}, webstore: {} } });
}
if (window.chrome) {
window.chrome.loadTimes = makeNative(function(){ return {}; }, "loadTimes", 0);
window.chrome.csi = makeNative(function(){ return {}; }, "csi", 0);
}
} catch {}
// ---- Document charset ----
try {
const DP = Document.prototype;
const cs = Object.getOwnPropertyDescriptor(DP, "characterSet");
if (cs && cs.configurable) {
Object.defineProperty(DP, "characterSet", { get: makeNative(() => "UTF-8", "get characterSet", 0) });
}
const ce = Object.getOwnPropertyDescriptor(DP, "charset");
if (ce && ce.configurable) {
Object.defineProperty(DP, "charset", { get: makeNative(() => "UTF-8", "get charset", 0) });
}
} catch {}
// ---- Window dimensions rounding ----
try {
["innerWidth","innerHeight","outerWidth","outerHeight","screenX","screenY"].forEach(k => {
const desc = Object.getOwnPropertyDescriptor(window, k) || { configurable: true };
try {
Object.defineProperty(window, k, { configurable: true, get: makeNative(function() {
const v = desc.get ? desc.get.call(window) : window[k];
return typeof v === "number" ? Math.round(v) : v;
}, `get ${k}`, 0) });
} catch {}
});
} catch {}
// console.debug("Ava stealth patch installed");
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment