Related: #76 — PixelScan reports 5 checks; this is the fix for the "Fingerprint: Masking detected" one specifically. The other four (Browser, Location, Proxy, Bot check) are unrelated to font spacing — see below.
Problem
AudioFingerprintManager::ApplyTransformation() has a clean off switch — when seed == 0, it returns early and produces vanilla audio output. Font spacing perturbation doesn't have this: when no seed is set, gfxHarfBuzzShaper.cpp falls back to a hardcoded constant (0x6D2B79F5u) and always applies perturbation.
This means every Camoufox session produces font metrics and canvas text hashes that no real device has ever generated. Fingerprint checkers that maintain a population database (PixelScan's "Masking detected" verdict) can trivially flag this as synthetic.
| Perturbation |
seed == 0 behaviour |
Hardcoded fallback |
Can disable from Python? |
| Audio |
returns early, no-op |
none |
yes — set audio:seed to 0 |
| Font spacing |
LCG still runs (0 → 12345) |
0x6D2B79F5u when no seed set |
no |
| Canvas |
returns early, no-op |
none |
yes — set canvas:seed to 0 |
The audio and canvas paths let you opt out. Font spacing doesn't — inconsistent and makes A/B testing impossible from the Python API.
Evidence
PixelScan runs 5 checks. With the font spacing fallback removed, the Fingerprint check flips from fail to pass:
| PixelScan check |
Before fix |
After fix |
Notes |
| Browser |
pass |
pass |
Firefox correctly identified |
| Location |
pass |
pass |
Geoip/timezone cross-check |
| Proxy |
fail (when proxied) |
fail (when proxied) |
IP reputation — unrelated |
| Fingerprint |
fail — "Masking detected" |
pass |
Font spacing was the trigger |
| Bot check |
pass |
pass |
Juggler protocol not flagged |
Tested across 6 Camoufox runs + 1 vanilla Firefox baseline. A custom build with the hardcoded fallback removed passes the Fingerprint check cleanly.
Current C++ behaviour
// gfxHarfBuzzShaper.cpp — ShapeText()
uint32_t seed = FontSpacingSeedManager::GetSeed(pbid);
bool seedFromManager = (seed != 0);
if (!seedFromManager) {
seed = 0x6D2B79F5u; // always-on fallback
}
// LCG + spacing applied unconditionally
Compare with audio:
// AudioFingerprintManager::ApplyTransformation()
if (seed == 0 || length == 0 || !data) {
return; // clean no-op
}
Suggested fix
Remove the hardcoded fallback and guard the perturbation block, same as audio:
uint32_t seed = FontSpacingSeedManager::GetSeed(pbid);
if (seed != 0) {
// LCG + apply spacing (existing code, just wrapped in the guard)
}
When no seed is set, GetSeed() returns 0, the guard skips perturbation, and you get vanilla font metrics. To disable from the Python API, set fonts:spacing_seed to 0 in the config/preset — same convention as audio:seed and canvas:seed.
Happy to put up a PR if this looks reasonable.
Related: #76 — PixelScan reports 5 checks; this is the fix for the "Fingerprint: Masking detected" one specifically. The other four (Browser, Location, Proxy, Bot check) are unrelated to font spacing — see below.
Problem
AudioFingerprintManager::ApplyTransformation()has a clean off switch — whenseed == 0, it returns early and produces vanilla audio output. Font spacing perturbation doesn't have this: when no seed is set,gfxHarfBuzzShaper.cppfalls back to a hardcoded constant (0x6D2B79F5u) and always applies perturbation.This means every Camoufox session produces font metrics and canvas text hashes that no real device has ever generated. Fingerprint checkers that maintain a population database (PixelScan's "Masking detected" verdict) can trivially flag this as synthetic.
seed == 0behaviouraudio:seedto 00x6D2B79F5uwhen no seed setcanvas:seedto 0The audio and canvas paths let you opt out. Font spacing doesn't — inconsistent and makes A/B testing impossible from the Python API.
Evidence
PixelScan runs 5 checks. With the font spacing fallback removed, the Fingerprint check flips from fail to pass:
Tested across 6 Camoufox runs + 1 vanilla Firefox baseline. A custom build with the hardcoded fallback removed passes the Fingerprint check cleanly.
Current C++ behaviour
Compare with audio:
Suggested fix
Remove the hardcoded fallback and guard the perturbation block, same as audio:
When no seed is set,
GetSeed()returns 0, the guard skips perturbation, and you get vanilla font metrics. To disable from the Python API, setfonts:spacing_seedto 0 in the config/preset — same convention asaudio:seedandcanvas:seed.Happy to put up a PR if this looks reasonable.