Self-test pipeline
The self-test pipeline launches a profile against a known- instrumented page that reads back every fingerprint surface the platform exposes. The page checks the readings against the profile's declared fingerprint. Drift = bug.
When to run
- After any change to the stealth injector (init-script, network flags, extension loadout). Per CLAUDE.md this is mandatory.
- After bumping Chrome for Testing to a new milestone — same init-script can behave differently across Chrome 130 → 132.
- When a profile starts producing odd readings on real targets (sudden bot-score spikes, unexpected captcha frequency).
- Once per profile, ahead of first launch on a high-stakes target, as a sanity check.
What gets checked (R1-R12)
The validator runs twelve coherence rules. A pass means every rule returned green.
- R1 navigator.userAgent matches the declared OS + Chrome milestone exactly.
- R2 navigator.platform follows the UA (
Win32on Windows-Chrome,MacIntelon Mac). - R3 hardwareConcurrency / deviceMemory report canonical Windows-Chrome values (7 and 6 respectively). Randomising these is an instant tell.
- R4 timezone matches the proxy's egress country.
- R5 languages begin with the locale that matches the proxy country (e.g.
nl-NL,nl,en). - R6 WebGL renderer reports a plausible GPU string for the OS — no
SwiftShader, no headless tells. - R7 Canvas + AudioContext hashes are stable across reloads (no per-pixel jitter, no per-sample jitter).
- R8 fonts respond to font-presence probes consistently with the OS.
- R9 worker context exposes the same UA, platform, hardwareConcurrency as the main thread. Most stealth tools forget workers; we don't.
- R10 iframe context follows R9 for nested iframes too.
- R11 JS heap size limit matches the canonical Chrome constant (3.5 GB).
- R12 navigator.webdriver is
falseand no automation library is detected.
Running it
Profile detail → Run self-test. The pipeline spawns Chrome for that profile (with proxy, init-script, and warmup state), navigates to the harness, collects 12 readings, and shows a per-rule pass/fail grid. Total runtime: ~30 seconds.
Reading the results
Each rule either passes (green) or fails with a reason. The most common failures:
- R7 hash mismatch. Canvas / audio fingerprints drift if the init-script's noise-injection is stochastic instead of seeded. The seed must derive from the profile id, not from
Math.random(). - R9 worker drift. The init-script wasn't installed in the Worker scope. Confirm the stealth injector covers
SharedWorkerand dedicated workers. - R12 webdriver leak. Some Chrome flags re-expose
webdriver=true. Compare against the pinned flag-list insrc/main/launcher/flags.ts.
Re-running after a fix
The pipeline persists the full result row per run. The validation view shows the last 5 runs side-by-side so you can see the rule that flipped between attempts. Don't ship a stealth change until R1-R12 are all green for at least one profile.