Tables CreepJS : 51 noms et 14 retours possibles
CreepJS interroge 51 noms de polices pour déduire une version Windows ou macOS. Exécuter ses tables montre que chaque réponse exige un ensemble de base complet.
BotBrowser Team
Vous voulez la documentation structurée pour Empreinte ?
Cet article fait partie de la bibliothèque éditoriale. Pour les étapes de configuration, la référence et les mises à jour continues, passez directement à la section docs.
BotBrowser peut rejouer un profil déclaré contre une vérification de polices propre à la page et conserver un enregistrement borné des familles trouvées, mais il ne peut pas faire qu’un ensemble de polices installées décrive un autre système. CreepJS lit une liste fixe de noms de polices et transforme ceux qu’une page peut détecter en version de Windows ou de macOS. Exécuter ses tables publiées montre que la recherche est plus stricte que la forme des tables ne le suggère : chaque version qu’elle peut nommer exige un ensemble de base complet, donc une famille isolée ne décide rien.
Les trois noms de polices Android de cette source ne permettent pas d’identifier le contexte d’intégration qui les a exposés. Les limites des signaux CreepJS dans Android WebView distingue les valeurs visibles dans la page des signaux de requête et du pont applicatif facultatif.
Regarder la vidéo
Cette vidéo en anglais présente la recherche publiée dans les tables de polices. Le code répertorie 13 versions nommées de Windows et macOS ; le quatorzième retour possible est undefined lorsqu’aucun ensemble de base complet ne correspond. L’audio est en anglais. La trace reproductible utilise des ensembles synthétiques de noms de polices et ne prétend pas mesurer des polices dans un navigateur actif.
Résumé
- Le module publié déclare 51 noms de polices dans cinq tables : 14 Windows, 18 macOS, 9 applications de bureau, 7 Linux et 3 Android.
- Ces 51 noms sont déjà uniques : aucun nom n’apparaît dans deux tables, donc rien n’est à replier avant la vérification.
- La table Windows répartit ses 14 noms en cinq groupes de version, et le plus petit ne contient qu’un nom.
- La table macOS répartit ses 18 noms en huit groupes, dont deux contiennent aussi un seul nom.
- Un groupe d’un seul nom est nécessaire mais jamais suffisant : la table Windows ne contient que des clés qui incluent la paire Windows 7 complète, donc
Segoe Fluent Iconsseul ne rend rien. - CreepJS emploie deux chemins de détection,
document.fonts.checket un chargement local de polices, puis réunit leurs réponses en une liste triée. - Exécuter la recherche donne 5 réponses Windows, 8 réponses macOS et une réponse absente, soit 14 valeurs possibles au total.
Sommaire
- Regarder la vidéo
- Ce que déclarent les tables de polices
- Pourquoi un nom ne suffit jamais
- Ce qu’ajoutent les deux chemins de détection
- Où l’inférence s’arrête
- Enregistrer une lecture de polices
Ce que déclarent les tables de polices
Le module de polices est un fichier de données avec un peu de logique par-dessus. La sonde évalue chaque table comme des données au lieu de scanner son texte, car les étiquettes de version sont des clés entre guillemets qu’un scan de texte compterait comme des noms :
source -> https://raw.githubusercontent.com/abrahamjuliot/creepjs/master/src/fonts/index.ts
source_bytes -> 12847 sha256=ce2bcefd13e41cef79bfdf63634ad17ca7f641ee2e88c0ef720c2d38938dbf11
table:WindowsFonts -> names=14 shape=buckets
table:MacOSFonts -> names=18 shape=buckets
table:DesktopAppFonts -> names=9 shape=buckets
table:LINUX_FONTS -> names=7 shape=list
table:ANDROID_FONTS -> names=3 shape=list
fontlist -> length=51 distinct=51 duplicated=0
overlap:windows_macos -> 0
overlap:linux_android -> 0
Cinquante et un noms, sans doublon. C’est toute la surface de détection de la moitié « polices » de la bibliothèque. Une page qui exécute le module interroge 51 familles, et chaque conclusion de plateforme que le module peut tirer vient de celles qu’il a trouvées parmi ces 51.
La même source publique alimente aussi les groupes de signaux de CreepJS et leur combinaison, où la liste de polices est un groupe parmi d’autres, et le cycle de graine canvas de CreepJS, qui lit le générateur du module canvas.
Les cinq tables ne sont pas des variantes orthographiques d’une même liste. Les sept noms Linux sont des familles qu’une image Ubuntu ou Chrome OS embarque, et les trois noms Android sont un ensemble mobile qui recoupe Chrome OS et non un poste de bureau. Chaque table apporte des noms que les autres n’ont pas, et le module les réunit une seule fois en une liste alphabétique que la page vérifie.
Windows et macOS sont presque à égalité : 14 noms contre 18. Cette proximité trompe pour une autre raison, car les deux recherches lisent leurs tables avec des règles différentes, et la règle la plus stricte est celle qui décide si une réponse apparaît.
Pourquoi un nom ne suffit jamais
Les tables ne sont pas des listes plates. Chaque version est un groupe, et la recherche demande si l’un des noms du groupe est présent :
bucketsizes:WindowsFonts -> 7=2 8=4 10=4 11=1 8.1=3
bucketsizes:MacOSFonts -> 12=3 13=4 10.9=2 10.10=2 10.11=1 10.12=3 10.13-10.14=1 10.15-11=2
hashkeys:windows -> 11 macos=8
answers:windows -> 5 11,10,8.1,8,7
answers:macos -> 8 Ventura,Monterey,10.15-11,10.13-10.14,Sierra,El Capitan,Yosemite,Mavericks
answers:possible_returns -> 14
Le groupe Windows 11 contient un seul nom, Segoe Fluent Icons. Le groupe macOS 10.11 en contient un, tout comme celui de 10.13-10.14. Lire les groupes seuls laisse croire que trouver l’une de ces polices fixe la version. Exécuter la recherche dit autre chose.
Chaque clé de version est calculée, jointe en une chaîne triée, puis cherchée dans une table fixe. Toutes les entrées de cette table contiennent l’étiquette Windows 7, car le test Windows 7 est le seul à exiger un ensemble complet plutôt qu’un nom isolé. La jointure triée place 7 là où tombe son ordre alphabétique : la position varie, la présence non. Un ensemble qui a Segoe Fluent Icons mais pas les deux noms Windows 7 ne correspond à aucune clé :
replay:windows_11_bucket_only -> undefined (input=1)
replay:windows_7_both_names -> Windows 7 (input=2)
replay:windows_7_one_name -> undefined (input=1)
sweep:windows:undefined -> 5 subsets 11 10 11+10 11+8.1 11+8
sweep:windows:Windows 11 -> 1 subsets 11+10+8.1+8
Le balayage ci-dessus est l’ensemble des sous-ensembles des quatre groupes incrémentaux ajoutés à la paire Windows 7. Cinq des seize combinaisons ne rendent rien. La seule combinaison qui rend Windows 11 est l’ensemble complet des cinq groupes. Retirer un groupe entier et retirer une famille de ce groupe sont deux événements différents, et la sonde les sépare :
drop:Segoe Fluent Icons -> Windows 10
drop:Segoe MDL2 Assets -> Windows 11
drop:Segoe UI Emoji -> Windows 11
drop:Cambria Math -> macOS Ventura
Un groupe d’un seul nom n’a aucune marge : perdre Segoe Fluent Icons coûte la réponse Windows 11. Un groupe de plusieurs noms en a une, car il reste vrai tant qu’un de ses noms est présent : Segoe MDL2 Assets est dans le groupe de quatre noms de Windows 10 et Segoe UI Emoji dans le groupe de trois de 8.1, donc perdre l’un ou l’autre rend encore Windows 11. Perdre un nom Windows 7 est le cas le plus tranchant : la recherche Windows ne rend rien et le repli macOS prend le relais. Sur une machine qui a aussi les familles macOS, la réponse bascule sur macOS Ventura ; sur une machine qui n’a que des polices Windows, elle devient undefined. Dans les deux cas la réponse Windows disparaît à cause d’une seule famille manquante.
Le côté macOS est encore plus strict. Les huit entrées contiennent l’étiquette 10.9, et la table n’a aucune entrée pour un ensemble qui a des groupes plus récents sans elle :
sweep:macos:macOS Mavericks -> 1 subsets base:10.9
sweep:macos:macOS Ventura -> 1 subsets 13+12+10.15-11+10.13-10.14+10.12+10.11+10.10
sweep:macos:undefined -> 120 subsets
Cent vingt des 128 sous-ensembles ne rendent rien. Chacune des huit réponses nommées correspond à exactement un sous-ensemble, et la réponse est le groupe le plus récent présent. Une machine qui ne signale que des polices de l’époque Ventura et à qui manquent Helvetica Neue et Geneva n’obtient aucune réponse macOS, alors que quatre noms du groupe Ventura ont été trouvés.
Les groupes d’un seul nom sont donc réels mais étroits. Segoe Fluent Icons ne distingue Windows 11 de Windows 10 qu’après la correspondance des deux noms Windows 7 et de trois autres groupes. L’asymétrie à consigner est celle entre la forme de la table et la forme de la recherche : la table ressemble à un ensemble de tests OR indépendants, et la table de correspondance se comporte comme un escalier qui ne démarre qu’une fois l’ensemble de base complet.
Cet escalier a une seconde conséquence qu’un décompte de groupes cache. Comme chaque clé est la jointure triée de toutes les entrées vraies, un ensemble qui porte un groupe supplémentaire non prévu par l’auteur produit une chaîne absente de la table. La recherche ne retombe pas sur la correspondance la plus proche ; elle ne rend rien. Les tables publiées sont closes sous leurs propres noms, donc tout ensemble Windows 11 réel portant les 14 noms, y compris les quatre familles Windows 8, rend encore Windows 11 ; le risque tient à un changement de table qui ajouterait un groupe sans ajouter sa clé.
La table des applications de bureau est vérifiée par une règle encore différente, et la différence mérite d’être dite nettement car c’est le seul endroit où une correspondance complète est exigée à dessein. getDesktopApps compare l’ensemble de polices de chaque application à la liste trouvée et ne rend la clé que si tous les noms sont présents. Quatre applications produisent donc au plus quatre résultats, et une correspondance partielle n’en produit aucun.
Ce qu’ajoutent les deux chemins de détection
Le module ne s’appuie pas sur une seule façon de demander si une police est présente :
detection_paths -> 2 document.fonts.check and local font load
union -> fontsChecked then fontsLoaded, deduplicated
Le chemin document.fonts.check interroge directement l’ensemble de polices. Il indique si une famille est disponible pour le document, et le module parcourt les 51 noms avec lui. Le chemin local font load construit un objet par nom et attend les résultats de chargement, en retenant ceux qui aboutissent.
Deux chemins existent parce qu’ils peuvent diverger. Un navigateur peut annoncer une police disponible par une API et échouer à la charger par l’autre, et une page qui n’en vérifierait qu’une manquerait la divergence. L’union des deux réponses est la liste finale du module, triée et dédoublonnée, donc le résultat reste stable quel que soit l’ordre de retour des deux chemins.
Un rapport qui conserve les deux listes séparément peut aussi dire si une API est systématiquement plus faible sur une machine. Si le chemin de requête ne trouve rien alors que celui de chargement trouve des noms, la garde s’est probablement déclenchée, et c’est une observation différente d’une machine qui n’a réellement aucune police.
Avant de parcourir la liste, le module vérifie un nom de famille délibérément aléatoire. Quand cette vérification annonce que le nom aléatoire est disponible, le module saute la requête par nom et s’appuie sur le chemin de chargement. Cette garde évite de faire confiance à un ensemble qui prétend contenir tous les noms.
Cette garde se manque facilement à la lecture du rapport, car une requête sautée et une requête échouée laissent toutes deux la liste vérifiée vide. Les deux états ne se distinguent que si le rapport indique si la sonde du nom aléatoire a rendu vrai. Un lecteur qui voit une liste vérifiée vide et une liste chargée pleine doit supposer que la garde s’est déclenchée, et non que toutes les familles ont échoué au test de disponibilité.
Un nom fusionné par dédoublonnage ressemble à un nom trouvé une seule fois, donc le rapport doit conserver les deux listes séparément. La recherche est indépendante de l’ordre pour une autre raison : les tests de groupe utilisent l’appartenance et une comparaison de longueur, et la clé est construite en triant les étiquettes de groupe, pas la liste de polices. La sonde le confirme en donnant les mêmes 32 noms dans l’ordre inverse :
reorder:reversed_names -> Windows 11
Les deux chemins échouent aussi différemment. Une requête qui lève une exception envoie tout le module dans sa branche de capture et rend une liste vide, alors qu’un chargement raté ne retire que ce nom de la liste chargée. Garder les listes séparées est ce qui permet de distinguer une panne du module entier d’une famille qui n’a pas chargé.
Où l’inférence s’arrête
L’ordre des deux recherches de plateforme décide aussi de ce qu’annonce une machine qui a des polices des deux tables. getPlatformVersion essaie d’abord la recherche Windows et ne se rabat sur macOS que si Windows ne rend rien, donc un Mac qui satisfait par hasard tous les groupes Windows est annoncé comme Windows. La sonde rend le cas concret : un ensemble qui porte les 14 noms Windows et les 18 noms macOS rend Windows 11, pas macOS Ventura.
Les tables WindowsFonts et MacOSFonts sont aussi un instantané figé des plateformes qui intéressaient l’auteur. Une nouvelle version du système qui embarque des polices que la table ne nomme pas reste invisible au module jusqu’à la mise à jour de la table, donc l’absence de correspondance ne prouve pas que la plateforme est nouvelle.
La table macOS est la seule à porter des étiquettes de version à deux chiffres, et ces étiquettes sont des plages et non des valeurs exactes : 10.13-10.14 est un groupe et 10.15-11 en est un autre. Une correspondance identifie donc une plage, pas une version ponctuelle, et le rapport doit conserver la plage au lieu de l’arrondir à une seule version.
La recherche s’exécute après la construction de la liste, donc un échec de détection produit une liste vide et, avec elle, aucune réponse de plateforme. Cela compte parce que la réponse absente est une valeur, pas deux : une page qui lit une version vide devrait vérifier si la liste était vide avant de conclure que la machine n’est pas reconnue, car une liste vide et une liste non vide sans clé correspondante produisent le même silence.
Le module publié expose aussi quelques ensembles de polices d’applications, et chacun est comparé à la liste trouvée en tant que groupe :
bucketsizes:DesktopAppFonts -> Microsoft Outlook=1 Adobe Acrobat=1 LibreOffice=4 OpenOffice=3
La table est petite, neuf noms, et l’ensemble de chaque application est plus petit encore. Avoir les polices d’une application n’est pas la même chose que l’exécuter, car les polices peuvent être copiées ou embarquées séparément. Le module traite un ensemble complet de polices d’application comme une preuve de cette application, et un rapport devrait le présenter ainsi plutôt que comme la preuve que le programme est installé.
Une page qui appelle le module deux fois dans une même session peut obtenir deux listes, car une police peut se charger entre les appels. Le module ne met rien en cache entre deux exécutions et reconstruit la liste à chaque fois, donc une seconde lecture est une observation neuve et non une vue rafraîchie de la première.
La liste combinée est ce que la page vérifie réellement, donc un nom qui figure dans la table d’une plateforme est interrogé sur toutes. Une machine Windows est aussi interrogée sur les noms macOS, et la réponse est presque toujours non. C’est ainsi que les tables restent indépendantes : la page interroge l’union et les recherches de plateforme lisent la partie de l’union qui les concerne.
La liste de polices peut aussi changer sans que le navigateur bouge : installer une police pour un utilisateur change ce que son navigateur annonce et laisse intacte la liste d’un autre, et une mise à jour de paquets peut ajouter des noms qui manquaient. La réponse de plateforme suit la liste, donc elle bouge avec une installation et non avec la version du système.
Les 14 retours possibles fixent aussi le rapport minimal utile. Un seul champ de version peut nommer la réponse mais pas la raison, et qui veut vérifier l’inférence a besoin de l’ensemble de groupes qui a correspondu, pas seulement de la version nommée. Le rapport doit conserver le digest de source et la liste de polices dans un même enregistrement, car une liste sans la révision ne se vérifie pas et une révision sans la liste ne montre pas ce que le navigateur a trouvé.
Les 14 retours fixent aussi ce qu’une comparaison entre deux machines peut affirmer. Deux rapports qui disent Windows 11 ne concordent que si leurs ensembles de groupes consignés concordent, car la même réponse peut venir d’une liste trouvée légèrement différente. Comparer les listes consignées sépare une vraie différence dans les polices installées des mêmes polices lues deux fois.
Enregistrer une lecture de polices
Une lecture de polices soutient une affirmation étroite : cette révision de source, cet ensemble de tables, ont produit cette liste. Quatre champs la gardent vérifiable :
- Le digest de la source, car un changement de table change les noms interrogés et la signification d’une réponse de version.
- Les noms trouvés, pas seulement la version, car la réponse est une fonction de toute la liste trouvée et ne se redéduit pas du seul nom de version.
- Le chemin de détection qui a trouvé chaque nom, car les deux sont des observations séparées et l’union triée cache la distinction.
- Les tailles des groupes, car une réponse de version appuyée par un nom et une autre appuyée par quatre ne sont pas la même preuve.
Sources publiques
- CreepJS
- Module de polices CreepJS
- MDN : document.fonts
- MDN : local font load
- BotBrowser : référence de cohérence d’automatisation
Conclusion pratique
Un enregistrement qui conserve le groupe Windows 11 correspondant et les deux listes de chemins se rejoue avec un court script ; un enregistrement qui ne garde que la version ne se vérifie pas sans le navigateur. Consignez les noms trouvés avec la version, car la réponse dépend d’un ensemble de base complet et ne se redéduit pas du seul nom de version. Lisez les deux chemins de détection comme des observations distinctes, car l’union triée cache quelle API a trouvé un nom. Traitez les 14 retours possibles comme la taille de la recherche et non comme sa précision, car un groupe d’un seul nom ne discrimine qu’après la correspondance de tous les autres groupes de la table. Pour la façon dont la bibliothèque regroupe son signal de polices avec les groupes canvas et audio, lisez les groupes de signaux de CreepJS et leur combinaison ; pour le générateur canvas du même module, lisez le cycle de graine canvas CreepJS.
Cet article fait partie du guide : Qu'est-ce qu'une empreinte de navigateur ?
Articles Connexes
Détection Android WebView avec CreepJS : limites
EmpreinteLire les signaux d’empreinte du navigateur
EmpreinteFingerprinting par mesure de texte : suivi sous-pixel
EmpreinteQu'est-ce que l'empreinte des polices ? Cohérence du texte
PlateformeProfils navigateur multi-plateforme : une identité sur chaque hôte
EmpreintePrédiction CreepJS : agrégation et états inconnus
Faites passer BotBrowser de la recherche à la production
Utilisez ces guides pour comprendre le modèle, puis passez à la validation multi-plateforme, aux contextes isolés et au déploiement navigateur prêt pour l'échelle.