Fingerprint Spoofing β
Nothing Browser injects a spoofing script at DocumentCreation phase β before any page JavaScript runs. This makes fingerprinting almost impossible.
Overview β
Browser fingerprinting is how websites identify you without cookies. Nothing Browser spoofs the most common fingerprinting vectors.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FINGERPRINT SPOOFING β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Real Machine: Spoofed to Website: β
β βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β CPU: 16 cores β ββββΊ β CPU: 8 cores β β
β β RAM: 32 GB β ββββΊ β RAM: 8 GB β β
β β GPU: NVIDIA RTX β ββββΊ β GPU: Intel Iris β β
β β OS: Linux β ββββΊ β OS: Windows β β
β β Canvas: Unique β ββββΊ β Canvas: Noised β β
β β Audio: Unique β ββββΊ β Audio: Noised β β
β βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββHow It Works β
On first launch, IdentityGenerator reads real machine values and generates a BrowserIdentity:
// IdentityGenerator reads real values:
id.cpuCores = std::thread::hardware_concurrency(); // real CPU
id.ramGb = readFromProcMeminfo(); // real RAM, rounded to power of 2
id.screenW/H = QGuiApplication::primaryScreen()->size();
id.timezone = QTimeZone::systemTimeZoneId();
id.canvasSeed = rng->generateDouble(); // random per session
id.audioSeed = rng->generateDouble(); // random per sessionKey Principles β
| Principle | Description |
|---|---|
| Real values as base | CPU cores, RAM, screen size come from your machine |
| Noise added on top | Canvas and audio get per-session noise |
| Consistent per session | Same fingerprint throughout the session |
| Different across sessions | New noise seeds = new fingerprint |
Real values are used as the base. Only the canvas/audio/webgl seeds change each session β so APIs are internally consistent (CreepJS cross-validation passes) but differ across sessions (tracking fails).
What Is Spoofed β
Navigator Properties β
| Property | Real Value | Spoofed Value |
|---|---|---|
navigator.webdriver | undefined (real browser) | undefined |
navigator.vendor | Google Inc. | Google Inc. |
navigator.maxTouchPoints | 0 (desktop) | 0 |
navigator.pdfViewerEnabled | true | true |
navigator.plugins | Chrome plugins | Chrome PDF Plugin, Viewer, Native Client |
navigator.mimeTypes | application/pdf, text/pdf | Same |
Screen Properties β
| Property | Real Value | Spoofed Value |
|---|---|---|
screen.width | Real screen width | Real screen width |
screen.height | Real screen height | Real screen height |
screen.availWidth | Real width | Real width |
screen.availHeight | Real height - 40 (taskbar) | Real height - 40 |
screen.colorDepth | 24 | 24 |
devicePixelRatio | Real DPI ratio | Real DPI ratio |
Canvas Noise β
Canvas reads are intercepted. Each pixel gets Β±1 noise using a seeded PRNG:
// Seed changes every session β same within a session
function seededRand(seed, index) {
const x = Math.sin(seed * 9301 + index * 49297 + 233720) * 24601;
return x - Math.floor(x);
}Both getImageData and toDataURL are intercepted. The same seed is used for both β so they are internally consistent.
Audio Noise β
AudioBuffer.getChannelData and copyFromChannel add Β±0.00000005 noise per sample using a separate seed:
| Characteristic | Value |
|---|---|
| Noise amount | Β±0.00000005 |
| Audibility | Inaudible |
| Detection | Different hash every session |
WebGL Spoofing β
// Intercepts getParameter for aliased range params
WebGLRenderingContext.prototype.getParameter // spoofed
WebGL2RenderingContext.prototype.getParameter // spoofed| Parameter | Real Value | Spoofed Value |
|---|---|---|
| UNMASKED_VENDOR_WEBGL | NVIDIA/AMD | Intel Inc. |
| UNMASKED_RENDERER_WEBGL | Actual GPU | Intel Iris OpenGL Engine |
Chrome Object β
A full window.chrome object is injected:
window.chrome = {
runtime: { ... },
loadTimes: function() { ... },
csi: function() { ... },
app: { ... }
};WebRTC Protection β
STUN servers are filtered from RTCPeerConnection config to prevent IP leakage:
// Before: Your real IP could leak
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
// After: STUN servers removed
iceServers: [] // No IP leakagePerformance Timer β
performance.now() is rounded to 0.1ms precision β reduces timing attack surface.
Battery API β
navigator.getBattery() // β { charging: true, level: 0.85-1.0, ... }Timezone β
Intl.DateTimeFormat is proxied to inject the real system timezone where not specified.
Injection Timing β
The fingerprint spoofing script is injected at DocumentCreation β before any page JavaScript runs.
Timeline:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΊ
DocumentCreation Page JavaScript User Interaction
β β β
βΌ βΌ βΌ
Fingerprint Page sees already Everything works
injected spoofed values normallyWhy DocumentCreation Matters β
| Injection Point | Can Page Detect? | Why |
|---|---|---|
| DocumentCreation | β No | Script runs before page code |
| After page load | β οΈ Maybe | Page can see when values changed |
| Via CDP (Puppeteer) | β Yes | Automation detectable |
QWebEngineScript with DocumentCreation is architecturally superior to CDP-based injection.
Session Identity β
Where Identity Is Stored β
The identity is saved at:
~/.config/nothing-browser/identity.jsonIdentity File Example β
{
"cpuCores": 8,
"ramGb": 16,
"screenWidth": 1920,
"screenHeight": 1080,
"timezone": "America/New_York",
"canvasSeed": 0.123456789,
"audioSeed": 0.987654321,
"createdAt": 1700000000000
}Resetting Identity β
To generate a new permanent identity:
# Delete the identity file
rm ~/.config/nothing-browser/identity.json
# Restart browser β new identity generatedIn-app reset button coming in future version.
Testing Fingerprint β
Online Test Sites β
| Site | What It Tests |
|---|---|
| amiunique.org | Complete fingerprint analysis |
| browserleaks.com | Canvas, WebGL, audio |
| fingerprintjs.com/demo | Commercial fingerprinting |
| creepjs.com | Advanced detection tests |
| deviceinfo.me | Device information |
Manual Test β
// Run in browser console
console.log({
webdriver: navigator.webdriver,
plugins: navigator.plugins.length,
languages: navigator.languages,
webglVendor: document.createElement('canvas').getContext('webgl')?.getParameter(37445),
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory
});Comparison with Other Browsers β
| Feature | Nothing Browser | Brave | Firefox | Chrome |
|---|---|---|---|---|
| DocumentCreation injection | β Yes | β οΈ Partial | β No | β No |
| Canvas noise | β xorshift | β Sin-based | β No | β No |
| Audio noise | β Yes | β Yes | β No | β No |
| WebGL spoofing | β Yes | β Yes | β No | β No |
| WebRTC protection | β Yes | β Yes | β οΈ Partial | β No |
| Per-session randomization | β Yes | β Yes | β No | β No |
Known Limitations β
| Limitation | Status | Fix Version |
|---|---|---|
| Canvas uniqueness 99.98% | π¨ In progress | v0.1.4 |
sec-ch-ua brand format | π¨ In progress | v0.1.4 |
navigator.userAgentData missing | π¨ In progress | v0.1.4 |
| WebGL UNMASKED params spoofing | π¨ In progress | v0.1.4 |
Why Canvas Uniqueness Isn't 100% β
The sin() PRNG is being replaced with xorshift in upcoming versions to improve this.
Enabling / Disabling β
Fingerprint spoofing is always enabled in Nothing Browser. There is no toggle.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β There is no "disable fingerprint spoofing" button. β
β β
β Privacy is on by default. β
β Fingerprinting is blocked by default. β
β β
β If you need real fingerprints, use regular Chrome. β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββTechnical Details β
Injection Code (Simplified) β
(function() {
'use strict';
// Spoof navigator properties
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// Spoof plugins
Object.defineProperty(navigator, 'plugins', {
get: () => [
{ name: 'Chrome PDF Plugin' },
{ name: 'Chrome PDF Viewer' },
{ name: 'Native Client' }
]
});
// Spoof WebGL
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 37445) return 'Intel Inc.';
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
return getParameter.call(this, parameter);
};
// Canvas noise
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
const imageData = originalGetImageData.call(this, x, y, w, h);
// Add noise using xorshift
for (let i = 0; i < imageData.data.length; i += 4) {
if (seededRand() < 0.5) {
imageData.data[i] ^= 1;
imageData.data[i+1] ^= 1;
imageData.data[i+2] ^= 1;
}
}
return imageData;
};
})();Next Steps β
- BROWSER Tab β See fingerprint in action
- DEVTOOLS Tab β Capture network traffic
- TLS Fingerprint Report β Network-level fingerprinting
Nothing Ecosystem Β· Ernest Tech House Β· Kenya Β· 2026