Brave's WebGL farbling system now uses 10 realistic Apple Silicon GPU profiles instead of random 8-character strings. This provides better fingerprinting resistance while maintaining plausible deniability.
2026-02-07: Migrated from random string generation to profile-based farbling system.
All profiles return the same vendor string:
Google Inc. (Apple)
This matches the format that Chrome/ANGLE returns on macOS with Apple Silicon.
The system randomly selects one of these 10 profiles using the master seed:
ANGLE (Apple, ANGLE Metal Renderer: Apple M1 Pro, Unspecified Version)ANGLE (Apple, ANGLE Metal Renderer: Apple M1, Unspecified Version)ANGLE (Apple, ANGLE Metal Renderer: Apple M2 Pro, Unspecified Version)ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version)ANGLE (Apple, ANGLE Metal Renderer: Apple M3 Max, Unspecified Version)ANGLE (Apple, ANGLE Metal Renderer: Apple M3 Pro, Unspecified Version)ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)ANGLE (Apple, ANGLE Metal Renderer: Apple M4 Pro, Unspecified Version)ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)ANGLE (Apple, ANGLE Metal Renderer: Apple M5, Unspecified Version)
FarblingPRNG prng = MakePseudoRandomGenerator(FarbleKey::kWebGLRenderer);
size_t profile_index = prng() % 10; // Select one of 10 profilesThe PRNG is seeded with:
- Master seed (if set via
setFingerprintingSeed()) - eTLD+1 domain (for cross-site isolation)
- FarbleKey::kWebGLRenderer (specific to this farbling context)
Token = HMAC-SHA256(master_seed, eTLD+1)
Profile = profiles[PRNG(Token, FarbleKey::kWebGLRenderer) % 10]
Same master seed + same domain → same profile every time
- Same seed, different domains → different profiles (eTLD+1 salting)
- Different contexts can have independent fingerprints
All profiles are real Apple Silicon GPUs that exist in the wild:
- M1, M1 Pro (2020-2021)
- M2, M2 Pro (2022-2023)
- M3, M3 Pro, M3 Max (2023-2024)
- M4, M4 Pro (2024-2025)
- M5 (future-proofing)
Chromium/ANGLE doesn't validate GPU strings against any list—they trust OS/driver values
// Set master seed for this context
window.setFingerprintingSeed(12345);
// Query WebGL
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const ext = gl.getExtension('WEBGL_debug_renderer_info');
console.log(gl.getParameter(ext.UNMASKED_VENDOR_WEBGL));
// → "Google Inc. (Apple)"
console.log(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL));
// → "ANGLE (Apple, ANGLE Metal Renderer: Apple M2 Pro, Unspecified Version)"// Test 1: Set seed 12345
window.setFingerprintingSeed(12345);
// Reload page → Profile X
// Test 2: Set same seed again
window.setFingerprintingSeed(12345);
// Reload page → Profile X (same as before)
// Test 3: Different seed
window.setFingerprintingSeed(67890);
// Reload page → Profile Y (different from X)File: src/brave/third_party/blink/renderer/core/farbling/brave_session_cache.h
enum FarbleKey : uint64_t {
// ... existing keys
kWebGLVendor, // NEW
kWebGLRenderer, // NEW
kKeyCount
};File: src/brave/third_party/blink/renderer/core/farbling/brave_session_cache.h
blink::String GetFarbledWebGLVendor();
blink::String GetFarbledWebGLRenderer();File: src/brave/third_party/blink/renderer/core/farbling/brave_session_cache.cc
blink::String BraveSessionCache::GetFarbledWebGLVendor() {
return "Google Inc. (Apple)";
}
blink::String BraveSessionCache::GetFarbledWebGLRenderer() {
static constexpr const char* kWebGLRendererProfiles[] = {
// ... 10 profiles
};
FarblingPRNG prng = MakePseudoRandomGenerator(FarbleKey::kWebGLRenderer);
return blink::String(kWebGLRendererProfiles[prng() % 10]);
}File: src/brave/chromium_src/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
#define BRAVE_WEBGL_GET_PARAMETER_UNMASKED_RENDERER \
if (ExtensionEnabled(kWebGLDebugRendererInfoName) && \
!AllowFingerprintingForHost(Host())) \
return WebGLAny(script_state, \
brave::BraveSessionCache::From( \
*(Host()->GetTopExecutionContext())) \
.GetFarbledWebGLRenderer());With a uniform random distribution across seeds:
- Each profile: ~10% probability
- Total combinations: 10 profiles
This is significantly fewer than the previous system (62^8 = 218 trillion combinations), but:
- ✅ More realistic (all are real GPUs)
- ✅ Harder to detect as fake
- ✅ Still provides meaningful anonymity set
| Aspect | Old System (Random Strings) | New System (Profiles) |
|---|---|---|
| Vendor | Random 8-char string | "Google Inc. (Apple)" |
| Renderer | Random 8-char string | 10 realistic profiles |
| Combinations | 62^8 = 218 trillion | 10 |
| Detectability | Easy (obviously fake) | Hard (real GPUs) |
| Plausibility | Low | High |
Research shows Chromium/ANGLE never validates GPU strings against hardcoded lists:
- Vendor/renderer come from Metal API at runtime (
MTLDevice.name) - Browser trusts whatever the OS/driver returns
- No allowlist/blocklist of valid GPU names
Using real Apple Silicon GPUs means:
- Fingerprints blend into real user population
- Harder for sites to detect farbling
- More plausible deniability
Includes M4 and M5 (unreleased as of 2024) to ensure longevity
See WEBGL_FARBLING_TEST.html for interactive tests.
# Open Brave with shields enabled
brave --enable-features=BraveShields
# Open test page
open WEBGL_FARBLING_TEST.html
# Check console
# Should show one of 10 Apple Silicon profiles// In console
window.setFingerprintingSeed(12345);
location.reload();
// Check GL_RENDERER - should be deterministic
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const ext = gl.getExtension('WEBGL_debug_renderer_info');
console.log(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL));The system uses eTLD+1 normalization to ensure subdomains get the same profile:
example.com → Token_A → Profile 3
www.example.com → Token_A → Profile 3 (same)
api.example.com → Token_A → Profile 3 (same)
different.org → Token_B → Profile 7 (different)
This prevents cross-site tracking while maintaining consistent fingerprints within the same site.
- ANGLE Metal Renderer Implementation
- Chromium GPU Info Collector
- ANGLE SystemInfo
- Previous Analysis - Original random string approach
- 2026-02-07: Implemented profile-based WebGL farbling with 10 Apple Silicon variants
- Previous: Used random 8-character alphanumeric strings