Flags d’empreinte
L’identité se contrôle au moyen de switches de ligne de commande Chromium. Le SDK les positionne à partir d’options nommées ; avec le build ouvert, vous pouvez aussi les passer en args à votre propre lanceur.
La plupart des switches existent dans les deux builds. Certains sont réservés au build sous licence (Gratuit avec GitHub ou Pro) — la liste ci-dessous les signale, avec la révision du moteur qui les a introduits. Sans clé de licence, le SDK exécute le build ouvert, où ces options restent sans effet.
Le modèle de seed
Tout découle de --fingerprint=<seed>. La seed (un entier ou n’importe quelle chaîne) dérive la persona de façon déterministe — et perturbe les deux surfaces de rendu listées plus bas, dans la section ce qui est bruité :
- Même seed ⇒ même identité d’un lancement à l’autre — un visiteur qui revient.
- Nouvelle seed ⇒ une identité neuve et plausible.
- Le bruit canvas et WebGL est dérivé par site (inspiré du farbling de Brave), si bien que ces hashes diffèrent d’un domaine à l’autre. La persona elle-même — GPU, écran, matériel, polices — est la même partout, comme le serait celle d’une vraie machine.
Vous préférez l’identité d’une vraie machine à l’identité synthétique dérivée de la seed ? Capturez un Chrome donneur avec le collecteur et importez-le via --fingerprint-profile=<gzip+base64 JSON> (ou via l’option fingerprint_profile / fingerprintProfile du SDK, qui se charge du gzip+base64 pour vous). Les champs présents dans le profil remplacent ceux de la persona ; les champs absents retombent sur la persona de la seed --fingerprint, ou sur les valeurs par défaut du navigateur en l’absence de seed. Les deux builds lisent les profils importés ; sur le build sous licence, ils s’appliquent intégralement à partir de la 151 r19. Consultez le guide Playwright pour importer un profil capturé depuis le SDK.
Ce qui est bruité, et ce qui ne l’est volontairement pas
Perturber une surface empêche de relier les identités entre elles, mais se paie en cohérence, et ce compromis ne vaut pas la peine partout. Clearcote bruite les deux surfaces où le bruit se rapproche le plus de la variation matérielle ordinaire, et laisse les autres non perturbées — parce que sur celles-là, une valeur perturbée est un signal plus voyant qu’une valeur partagée. Non perturbé ne veut pas dire intact pour autant : quelques valeurs sont tout de même substituées en bloc à partir de la persona, comme le détaillent le tableau et la note qui le suit. Certains outils de vérification peuvent encore repérer le bruit lui-même ; si cela compte pour vous, fingerprintNoise: false le désactive tout en conservant l’identité (voir Comment fonctionne la détection).
| Surface | Bruit par seed | Pourquoi |
|---|---|---|
Canvas 2D — toDataURL, getImageData et les autres moyens dont dispose une page pour exporter un canvas (sur le build sous licence, tous les chemins d’export concordent à partir de la 152 r21) | Oui | La relecture des pixels varie déjà d’une vraie machine à l’autre selon le GPU, le pilote et le chemin de rastérisation ; une petite perturbation reste donc dans la dispersion naturelle. |
WebGL — readPixels, toDataURL | Oui | Même raisonnement. Avec disableGpuFingerprint, readPixels renvoie les vrais pixels, mais les exports d’un canvas WebGL (toDataURL() et consorts) restent bruités — associez-le à fingerprintNoise: false pour que toutes les lectures du canvas concordent. |
Audio — valeurs des échantillons AudioContext | Non — volontairement | La sortie Web Audio est déterministe pour un pipeline donné. La perturber suppose soit une fréquence d’échantillonnage hors spécification (44099,99 au lieu de 44100), soit des échantillons qu’aucun moteur de rendu standard ne produit — deux types de valeurs qu’un vrai navigateur ne peut pas émettre, ce qui trahit bien davantage que deux profils partageant un même hash audio. Les scalaires rapportés sont une autre affaire, et ceux-là suivent bien la persona — voir plus bas. |
| Client rects & métriques de texte | Non — volontairement | Chrome quantifie la mise en page sur une grille native de 1/512 px : tout rect non modifié est donc un multiple exact de 0,001953125. Un facteur d’échelle appliqué après coup place chaque rect hors de cette grille, et une géométrie hors grille se repère dès une seule mesure, aussi petit que soit le facteur. |
| WebGPU — adaptateur et limites | Non | Suit le GPU de la persona plutôt que la seed, et reste ainsi en accord avec ce que rapporte WebGL. Deux API GPU qui nomment des fabricants différents sur une même page, cela se lit en un seul appel. |
La conséquence pratique pour le travail multi-comptes : canvas et WebGL séparent vos identités ; l’audio rendu et les client rects, non. Ce sont des propriétés de la machine sous-jacente — le même hôte produit le même pipeline audio et la même grille de mise en page quel que soit le profil chargé, exactement comme pour deux personnes différentes sur un matériel identique. Si une cible corrèle précisément sur ces signaux, la réponse, ce sont des machines distinctes, pas un autre switch.
Une exception à connaître : les trois scalaires AudioContext suivent bien la persona. sampleRate, baseLatency et outputLatency sont servis par la persona dès qu’une persona est active — une seed --fingerprint ou un profil importé — et varient donc d’une identité à l’autre au lieu d’exposer le backend audio de l’hôte. Sans l’un ni l’autre, les trois proviennent tels quels de la machine réelle. Une fréquence que la page demande explicitement reste respectée, et outputLatency passe toujours par le quantificateur propre à Chrome, qui dépend des permissions. À partir de la 153 r26, baseLatency suit aussi la taille de rendu demandée par la page, et un contexte qui n’a pas encore commencé le rendu rapporte la même latence qu’un Chrome ordinaire.
Surfaces secondaires cohérentes
Au-delà des signaux bruités, le moteur maintient les surfaces secondaires de la persona en accord avec un vrai Chrome sur la plateforme de la persona. Par défaut, le SDK prend l’OS de votre hôte comme plateforme ; les valeurs propres à Windows ci-dessous s’appliquent à une persona Windows.
- Les limites WebGL (WebGL1 + WebGL2) suivent le GPU de la persona partout où cette machine peut les honorer. Une limite n’est jamais annoncée au-dessus de ce que le pilote réel impose ; sur un moteur de rendu logiciel, certaines valeurs sont donc inférieures à celles du GPU nommé.
- UNMASKED_RENDERER / UNMASKED_VENDOR restent constants pendant toute la session — un seul GPU sur tous les sites, qui suit la persona, au lieu d’un indice révélateur propre à chaque origine.
- navigator.getBattery() décrit un ordinateur de bureau branché sur secteur (en charge, niveau 1,0, aucune décharge), et navigator.connection un profil résidentiel (effectiveType 4g, rtt/downlink arrondis, saveData désactivé).
- navigator.keyboard.getLayoutMap() renvoie une disposition US-QWERTY et, sur une persona Windows, AudioContext rapporte la fréquence d’échantillonnage et la latence de la pile audio de Windows.
- window.getScreenDetails() rapporte un seul écran cohérent ;
@media (pointer: fine)/(hover: hover)correspondent à un ordinateur de bureau équipé d’une souris. - URL — sous une persona Windows,
new URL("C:/").protocolrenvoiefile:, et à partir de la 153 r26, toutes les façons dont une page peut construire une URL donnent la même réponse. navigator.share / canShare sont exposés pour correspondre à l’UA Windows. - WebGPU — les infos d’adaptateur et les limites/features de
navigator.gpusuivent le même GPU que WebGL (sur le build sous licence, dans la limite de ce que l’hôte peut fournir). - Locale —
Accept-Language,navigator.language, la liste complètenavigator.languagesetIntl(thread principal + workers) proviennent tous d’une seule et même liste (acceptLanguage, ougeoip). Le SDK définit--accept-langet--langpour vous ; avec des switches bruts, passez les deux. - Synthèse vocale —
speechSynthesissert le jeu de voix de la persona, y compris les voix réseau Google que liste un Chrome de cette version (153 r27).fingerprintVoices: falseconserve à la place les voix propres à la machine (152 r22). - Matériel — la mesure de vitesse du processeur (
navigator.cpuPerformance) suit la persona plutôt que le processeur réel (152 r20), et le plafond mémoire des scripts (performance.memory.jsHeapSizeLimit) suit la mémoire annoncée par la persona (153 r26). - Codecs & périphériques —
MediaCapabilities.decodingInfo()rapporte une matrice de codecs propre à la persona, etenumerateDevices()un ensemble de périphériques média propre à la persona (identifiants stables pour une même seed, libellés vides avant autorisation). - UA-CH à haute entropie —
bitness=64 /wow64=false /model; sur une persona Linux,platformVersionest vide, comme chez un vrai Chrome sous Linux (153 r26).navigator.storage.estimate()rapporte un quota disque réaliste lorsque vous définissezstorageQuota.
Expérimental : canvas bridge vers un vrai GPU
En option, vous pouvez transférer les opérations canvas / WebGL vers un hôte distant équipé d’un vrai GPU, afin que les relectures (getImageData / toDataURL / readPixels / measureText) soient cohérentes avec le GPU que vous présentez — même sur un matériel incapable de le rendre localement. Comme ce sont les opérations qui sont transférées (et non des images préenregistrées), la plupart des canvas sont pris en charge, pas seulement les sondes connues. Activez-le avec --canvas-bridge-url=ws://host:port (plus --no-sandbox) ; sans ce switch, tout reste local, exactement comme avant. Le serveur de rendu pilote un navigateur sur l’hôte au vrai GPU (un Chrome classique via CDP, ou un Clearcote headless). Consultez le guide du canvas bridge pour la configuration complète.
Surcharges natives de métadonnées & light_stealth
En plus de la persona dérivée de la seed, un ensemble de surcharges natives à valeur unique vous permet de falsifier directement des valeurs navigator/screen individuelles — hardwareConcurrency, deviceMemory, colorDepth, devicePixelRatio, maxTouchPoints et (sur activation explicite) les dimensions screen/avail*. Chacune est lue directement par son getter, avec l’ordre de priorité flag > persona --fingerprint > hôte réel : une valeur codée en dur l’emporte donc sur n’importe quelle seed, et les surcharges fonctionnent avec ou sans seed --fingerprint — elles ne sollicitent jamais la mécanique de la persona.
Le SDK regroupe un sous-ensemble léger derrière un seul flag, light_stealth : il applique l’un d’un petit nombre de lots de métadonnées cohérents (hardwareConcurrency, deviceMemory, colorDepth, devicePixelRatio, maxTouchPoints), uniquement via ces switches natifs. La seed ne sert qu’à choisir le lot et n’est pas transmise au moteur : il n’y a donc aucun bruit canvas ni WebGL, et toutes les sessions light_stealth d’une même machine partagent le hash canvas de l’hôte — utilisez une seed fingerprint par compte, sans lightStealth, lorsque ces hashes doivent différer. Le ClientHello TLS et la vraie version du navigateur restent inchangés. Les dimensions screen ne sont volontairement pas falsifiées par défaut (activation explicite via screenWidth, etc.), car un écran simulé impossible à concilier avec la surface de rendu réelle se repère facilement ; sur un écran sans mise à l’échelle, vérifiez que le devicePixelRatio choisi lui convient, ou définissez-le vous-même. Une option explicite l’emporte toujours sur le préréglage.
import { launch } from "clearcote";
// one coherent metadata bundle; "my-seed" only picks which one — no seed reaches the engine, so no canvas noise:
const browser = await launch({ lightStealth: true, fingerprint: "my-seed" });
// or set individual values by hand (no --fingerprint needed):
const b2 = await launch({
hardwareConcurrency: 8,
deviceMemory: 8,
devicePixelRatio: 1.25,
maxTouchPoints: 0,
});Tous les switches
Filtrez par nom ou par effet, ou limitez l’affichage à un sous-système. Cliquez sur un switch pour lire ce qu’il fait et voir sa forme valide la plus courte.
Seed maître (entier ou chaîne). Dérive toute la persona (GPU, écran, matériel, polices) et pilote le bruit canvas et WebGL par site. La langue et le fuseau horaire ne sont pas dérivés du seed : ils viennent de --accept-lang / --timezone (SDK : acceptLanguage, timezone ou geoip). L'audio rendu et les client rects ne sont pas perturbés ; les scalaires AudioContext sampleRate/baseLatency/outputLatency sont fournis par la persona quand elle est active — voir la documentation sur l'empreinte. Même seed ⇒ même identité.
Exemples
Voici des jeux de flags bruts, en ligne de commande. Pour des workflows Python et Node complets, consultez Exemples.
Une identité Windows complète et cohérente :
--fingerprint=acme-tenant-7 \
--fingerprint-platform=windows \
--fingerprint-brand=Chrome \
--accept-lang=en-US,en \
--lang=en-US \
--timezone=America/New_York \
--fingerprint-hardware-concurrency=8Une identité Windows avec des chaînes GPU personnalisées, sous la forme qu’utilise ANGLE sous Windows :
--fingerprint=42 \
--fingerprint-platform=windows \
--fingerprint-gpu-vendor="Google Inc. (NVIDIA)" \
--fingerprint-gpu-renderer="ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002504) Direct3D11 vs_5_0 ps_5_0, D3D11)"Les chaînes changent ce qui est rapporté, pas ce qui est dessiné : les pixels proviennent toujours du GPU de cette machine. Indiquez le GPU sur lequel le rendu se fait réellement, ou utilisez le canvas bridge. La plateforme macos modifie les chaînes d’identité (UA, client hints, navigator.platform), mais aucun modèle de machine macOS ne se trouve derrière elles, et android est une persona mobile en best-effort.
Fixez une position avec son fuseau horaire pour que la géolocalisation et l’horloge concordent :
--fingerprint=nyc-1 \
--timezone=America/New_York \
--fingerprint-location=40.7128,-74.0060Importer les valeurs d’une vraie machine
Au lieu d’une seed synthétique, vous pouvez reprendre les valeurs exactes qu’a rapportées un vrai Chrome — chaînes GPU + table getParameter, écran, polices, voix, audio. Les valeurs sont substituées ; le rendu reste effectué par votre propre machine, si bien qu’un profil ne donne pas des canvas différents à deux comptes — c’est une seed --fingerprint par compte qui le fait. Prenez-en un dans la bibliothèque clearcote-profiles, triée sur le volet (des milliers de profils de vraies machines, étiquetés par fabricant de GPU), ou capturez le vôtre avec le collecteur, puis chargez-le et vérifiez qu’il est bien chargé :
import { launch } from "clearcote";
// path to a captured .json profile, a profile object, or a JSON string
const browser = await launch({ fingerprintProfile: "./real-machine.json" });
// fields present in the profile override the persona;
// absent fields fall back to the --fingerprint seed's persona, or the browser's defaults with no seed.Procédure de capture : ouvrez la page du collecteur dans le vrai Chrome que vous voulez cloner, exportez le JSON, puis passez-le via fingerprintProfile (SDK) / --fingerprint-profile (moteur). Vérifiez avec tools/fingerprint-collect/verify_profile.py.
Ce que fait chaque patch
Chaque switch du build ouvert correspond à un patch moteur lisible : les diffs se trouvent dans patches/, avec un résumé d’une ligne pour chacun dans patches/README.md. Les switches marqués « build sous licence » proviennent du jeu de patchs propre au build sous licence, qui n’est pas public.
La cohérence compte plus que n’importe quelle valeur isolée : gardez la plateforme, le fuseau horaire, la locale et le GPU plausibles ensemble. Consultez Architecture pour voir comment le moteur les maintient cohérents, et Comment fonctionne la détection pour comprendre pourquoi cette approche l’emporte sur la falsification en JavaScript.
À lire aussi