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é à chaque lancement sur la même machine et la même version du navigateur — un visiteur qui revient. Tout ce qu’une page peut lire revient à l’identique : le GPU, l’écran, le matériel, les polices et les voix, ainsi que les résultats canvas, WebGL et audio. La seule exception est l’estimation de la vitesse de connexion (le temps aller-retour et la bande passante dans
navigator.connection), qui suit votre réseau réel, comme dans Chrome. - 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.
La même seed sur des machines différentes
La carte graphique qu’annonce une persona provient du fabricant de la carte avec laquelle la machine effectue réellement le rendu (NVIDIA, Intel ou AMD), si bien que les pixels et les limites graphiques que mesure une page correspondent au type de carte annoncé à la page. Sur une machine sans carte graphique, la persona annonce une carte Intel. Une seed annonce donc une carte graphique différente sur des machines dont les cartes viennent de fabricants différents — c’est voulu.
Sur le build sous licence, à partir de la 154 r36, le fabricant de GPU de la machine peut aussi modifier le reste du matériel. Un ordinateur d’entrée de gamme n’est pas livré avec une carte graphique dédiée ; sur une machine équipée d’une carte NVIDIA, une seed qui annoncerait du matériel d’entrée de gamme (4 à 6 threads de processeur, 4 à 8 Go de mémoire, un petit écran) annonce donc à la place du milieu de gamme ou mieux, et jamais d’écran tactile. Environ la moitié des seeds annoncent par conséquent un nombre de threads de processeur et une quantité de mémoire différents sur une machine NVIDIA que sur une machine Intel, AMD ou sans carte graphique, et certaines une autre taille d’écran. Sur des machines du même fabricant de GPU, l’identité d’une seed est la même.
Pour qu’une seed garde une seule et même identité d’une machine à l’autre :
- Exécutez-la sur des machines du même fabricant de GPU — par exemple un seul type de serveur en production.
- Fixez le fabricant de GPU en passant
--fingerprint-gpu-host-vendor=nvidia(ouintel,amd) dans lesargsde votre lanceur. La persona dérive alors son identité comme elle le ferait sur une machine équipée d’une carte de ce fabricant, où qu’elle s’exécute. Fixez le fabricant des machines sur lesquelles la persona tourne le plus souvent : sur une machine dont la carte vient d’un autre fabricant, la carte annoncée ne correspond plus à celle qui effectue le rendu. - Conservez les identités créées avant la r36 avec
persona_schema=2/personaSchema: 2(--fingerprint-schema=2). Chaque seed dérive alors exactement ce qu’elle dérivait de la 153 r29 à la 154 r35, champ par champ : la carte graphique suit toujours le fabricant de la machine, le reste du matériel non.
Sans seed, il n’y a pas d’identité propre à chaque persona : tous les lancements sur le même type de machine se ressemblent. Donnez à chaque identité sa propre seed.
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. Sur le build sous licence à partir de la 154 r34, disableGpuFingerprint ne change que ce que rapporte le GPU : readPixels et les exports d’un canvas WebGL conservent le bruit de la seed et concordent entre eux. Sur le build ouvert et les anciens builds sous licence, il supprime aussi le bruit de readPixels alors que les exports restent bruités : associez-le donc, dans ce cas, à fingerprintNoise: false pour que toutes les lectures du canvas concordent. fingerprintNoise: false désactive le bruit WebGL partout. |
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.
- Clair ou sombre — depuis la 154 r32, la persona préfère un thème de couleurs (environ une sur trois en sombre ;
--fingerprint-color-scheme=light|darkpermet de choisir), etprefers-color-scheme,matchMedia, l’en-têteSec-CH-Prefers-Color-Schemeet les couleurs système CSS concordent tous. Le SDK (0.41.0+) laisse désactivée l’émulation du thème de couleurs de Playwright sur un tel build, pour que rien ne le contredise. - Polices — sur une persona Windows, le texte a les hauteurs de ligne et les largeurs de polices courantes d’une machine Windows, et les polices qu’une page demande par leur nom se comportent comme sous Windows (154 r32). Une vraie police installée sur la machine est utilisée telle quelle ; sous Linux, listez les vôtres avec fontDirs.
- 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îtresse (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 de la 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é sur la même machine et la même version du navigateur ; sur des machines d'un autre fabricant de GPU, l'identité diffère (voir la documentation sur l'empreinte et --fingerprint-gpu-host-vendor).
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