Retour au Centre de connaissances
Empreinte

BotD : quels détecteurs s’exécutent vraiment

Lisez le registre figé de FingerprintJS BotD et exécutez ses détecteurs sur des états de composants explicites pour voir quels contrôles réagissent et lesquels restent muets.

BotBrowser Team

Documentation

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 fixture autorisé de même origine sous un profil déclaré et conserver des observations bornées sur les surfaces d’automatisation qu’une page peut lire, mais il ne peut pas exécuter un service de détection tiers, décider du verdict d’un fournisseur ni promettre que nettoyer une surface nettoie les autres. Cette revue suit le code public de FingerprintJS BotD au commit 193c471ac70530afcebe5b09664c09f611d19218, lit ses deux fichiers de registre, puis exécute ses propres fonctions de détection sur des états de composants explicites, afin que les affirmations ci-dessous reposent sur une sortie observée et non sur la seule lecture du code.

TL;DR

  • BotD collecte 23 sources nommées et expose 18 détecteurs ; un fichier de détecteur de l’arbre figé est exporté mais jamais enregistré, donc sa branche ne peut jamais produire de verdict.
  • Un verdict est un repliement sur les détecteurs, pas un vote : le dernier détecteur qui rapporte un nom de bot remplace le précédent, donc le type rapporté dépend de l’ordre du registre.
  • Un composant dont la source a levé une erreur garde un état non réussi, et chaque détecteur vérifie cet état, donc une valeur absente se résout le plus souvent en aucun verdict et non en une supposition.
  • Exécuter les fonctions figées sur des états synthétiques montre une surface qui se nettoie pendant qu’une autre rapporte encore de l’automatisation, deux branches exactes de faux négatif, et un résultat true sans nom qui perd le nom du détecteur.
  • Le comportement observé appartient à ce commit, pas à tous les détecteurs, et jamais à une décision de production hébergée.

Contents

SRC_23 alimente DET_18, puis WEBDRIVER_FLAG, DISTINCTIVE_PROPS, WINDOW_0X0 avec DOC_FOCUS_GUARD et RTT_UNREGISTERED

Ce que contient réellement un registre figé

src/sources/index.ts enregistre 23 fonctions source : android, browserKind, browserEngineKind, documentFocus, userAgent, appVersion, rtt, windowSize, pluginsLength, pluginsArray, errorTrace, productSub, windowExternal, mimeTypesConsistent, evalLength, webGL, webDriver, languages, notificationPermissions, documentElementKeys, functionBind, process et distinctiveProps. Chaque source est une fonction simple qui renvoie une valeur ou lève une erreur. Une source qui ne peut pas lire sa surface lève un BotdError porteur d’un State, et collect() dans src/api.ts transforme cette erreur en composant { state, error } au lieu d’une valeur.

src/detectors/index.ts enregistre 18 fonctions de détection dans un ordre de déclaration fixe : detectAppVersion, detectDocumentAttributes, detectErrorTrace, detectEvalLengthInconsistency, detectFunctionBind, detectLanguagesLengthInconsistency, detectNotificationPermissions, detectPluginsArray, detectPluginsLengthInconsistency, detectProcess, detectUserAgent, detectWebDriver, detectWebGL, detectWindowExternal, detectWindowSize, detectMimeTypesConsistent, detectProductSub et detectDistinctiveProperties.

Le dossier des détecteurs contient 19 fichiers. src/detectors/rtt.ts exporte detectRTT, et detectRTT n’apparaît pas dans cet objet de registre. Comparer les clés du registre aux noms exportés du même checkout donne 19 fichiers et 18 noms enregistrés, rtt.ts étant le seul fichier dont l’exportation n’est jamais atteinte. C’est un fait de code d’un commit ; cela ne dit rien d’une version ultérieure ni d’un service hébergé qui importerait la fonction directement.

Le repliement dans src/api.ts décide comment un verdict unique est produit. detect() parcourt detectors dans l’ordre de déclaration, appelle chacun avec le dictionnaire de composants, et affecte finalDetection chaque fois qu’un détecteur rapporte un bot. Un détecteur qui renvoie une chaîne l’enregistre comme botKind ; un détecteur qui renvoie true seul enregistre BotKind.Unknown. L’affectation l’emportant sur l’accumulation, le type rapporté appartient au dernier détecteur du registre qui s’est déclenché, pas au plus précis ni au plus confiant. collect() exécute les sources en parallèle via Promise.all, donc le dictionnaire est complet avant qu’un détecteur le lise, et seul l’ordre des détecteurs décide du type.

Exécuter les détecteurs sur des états de composants explicites

Le dictionnaire de composants est la seule entrée lue par chaque détecteur, donc les fonctions figées peuvent s’exécuter sur des états écrits à la main sans navigateur, profil ni requête réseau. La commande ci-dessous empaquette l’arbre figé avec esbuild puis appelle les détecteurs exportés, en imprimant une ligne par cas :

npx esbuild src/detectors/index.ts src/api.ts \
  --bundle --format=cjs --platform=node --outdir=/tmp/botd-build
node /tmp/botd-probe.mjs

La sonde fournit un dictionnaire de base avec android: false, browserKind: 'chrome', browserEngineKind: 'chromium' et documentFocus: true, puis remplace un ou deux composants par cas. Un composant réussi porte { value, state: 0 } et un composant illisible porte { state: -1, error }, conformément à l’énumération State de src/types.ts (Success = 0, Undefined = -1, UnexpectedBehaviour = -3). La sortie observée des fonctions figées :

case 1 [detectWebDriver] bot: headless_chrome - driven browser: webdriver flag true
case 2 [detectDistinctiveProperties] bot: webdriver - sanitized flag but webdriver-shaped window property present
case 3 [detectDistinctiveProperties] bot: false - flag layer removed only, bridge gone too
case 4 [detectWindowSize] bot: headless_chrome - headless default window 0x0 while document has focus
case 5 [detectWindowSize] bot: false - headless report 0x0 but tab never focused
case 6 [detectPluginsArray] bot: headless_chrome - empty plugin list
case 7 [detectRTT] not-runnable: not registered in detectors/index.ts - connection rtt reported as zero
case 8 [detectWebDriver] bot: false - webdriver source unreadable instead of false

Les huit cas ci-dessus sont tranchés par quatre petites fonctions, qu’il vaut mieux lire directement que déduire de la sortie. src/detectors/webdriver.ts tient sur une ligne : elle renvoie BotKind.HeadlessChrome seulement si webDriver.state === State.Success && webDriver.value. src/detectors/window_size.ts vérifie d’abord que windowSize et documentFocus sont en State.Success, revient tôt si documentFocus.value est faux, et ne compare qu’ensuite outerWidth et outerHeight à zéro. src/detectors/plugins_array.ts renvoie BotKind.HeadlessChrome si pluginsArray.state === State.Success && !pluginsArray.value. src/detectors/distinctive_properties.ts parcourt l’objet renvoyé et renvoie le premier nom de bot dont l’indicateur est vrai.

La même discipline de lecture vaut quand un détecteur se nettoie et un autre non. Pour le côté collecte du même fournisseur, collecte et provenance dans FingerprintJS OSS montre comment les composants deviennent un condensat, tandis que détection et prévention de l'automatisation recense l'ensemble plus large des surfaces qu'une page peut inspecter, donc le verdict d'une seule fonction n'est jamais le tableau complet.

Où nettoyer une surface laisse un autre signal

Les cas 1 à 3 sont le résultat le plus net de l’exécution. Avec webDriver à true en succès, detectWebDriver rapporte headless_chrome. Avec webDriver à false en succès mais distinctiveProps portant { webdriver: true }, detectWebDriver ne rapporte rien et detectDistinctiveProperties rapporte webdriver à la place. Avec les deux surfaces propres, detectDistinctiveProperties ne rapporte rien et le cas se résout en bot: false.

Deux conséquences en découlent, et les deux comptent pour qui cite un verdict de cette bibliothèque. Les deux contrôles sont indépendants : nettoyer l’indicateur ne nettoie pas la liste de propriétés, car la liste est lue par Object.getOwnPropertyNames(window) et Object.getOwnPropertyNames(document) dans src/sources/distinctive_properties.ts, pas depuis l’indicateur. Et le type rapporté change avec la surface qui survit : headless_chrome et webdriver sont deux étiquettes différentes produites par la même bibliothèque depuis le même dictionnaire, donc un rapport qui cite l’une alors que l’autre existait cite l’ordre du registre, pas l’ensemble des signaux présents.

La liste de propriétés est assez précise pour être vérifiée à la main. Elle couvre 14 types de bots et liste 26 entrées de window (dont une expression régulière plutôt qu'un nom littéral) et 17 entrées de document, de awesomium et RunPerfTest côté CEF jusqu’à callPhantom et _phantom pour PhantomJS. Le groupe WebDriver porte à lui seul webdriver, __webdriverFunc, __lastWatirAlert, _WEBDRIVER_ELEM_CACHE et ChromeDriverw côté window, et __webdriver_script_fn, __driver_evaluate, __fxdriver_unwrapped, $cdc_asdjflasutopfhvcZLmcfl_ et $chrome_asyncScriptInfo côté document. Une revue qui n’inspecte que window manque le groupe document, et une revue qui n’inspecte que l’indicateur manque les deux.

Quels contrôles échouent en ouvert sur une valeur absente

Le cas 8 montre la première branche de faux négatif. Quand le composant webDriver est en State.Undefined, detectWebDriver renvoie false, car la garde exige State.Success avant de lire la valeur. Un navigator.webdriver illisible, comme une page qui masque la propriété, un realm où la lecture lève une erreur ou un habillage qui ne renvoie rien, est donc enregistré comme absence de preuve d’automatisation plutôt que comme signal positif. C’est un choix défendable pour une bibliothèque qui préfère le silence à la supposition, mais cela signifie qu’une valeur absente et une valeur propre sont indistinguables dans le verdict.

Le cas 5 montre la seconde branche de faux négatif, et elle est plus facile à manquer car les composants sont présents et valides. src/detectors/window_size.ts revient tôt quand documentFocus.value est faux, avant que la comparaison de taille nulle ne s’exécute. Une fenêtre headless rapportant outerWidth et outerHeight à zéro n’est signalée que pendant que le document a le focus. Un onglet ouvert en arrière-plan, ou une capture qui s’exécute avant l’arrivée du focus, produit des mesures identiques et aucun verdict. Le commentaire du code énonce l’intention, qu’un nouvel onglet en arrière-plan rapporte aussi une taille extérieure nulle, donc le retour anticipé est délibéré, mais la conséquence pour un lecteur est que les mêmes nombres mènent à des verdicts différents selon le moment du focus.

La troisième lacune de cette exécution est d’un autre ordre : non pas un faux négatif dans un détecteur, mais une branche sans chemin vers le verdict. detectRTT contient une condition réelle qui renvoie BotKind.HeadlessChrome quand rtt.value === 0, et le registre figé ne l’appelle jamais. La source rtt est bien collectée, donc la valeur est mesurée et placée dans le dictionnaire, mais aucun détecteur enregistré ne la lit. Lire rtt.ts seul surestime la bibliothèque ; lire detectors/index.ts seul cache la branche. Seuls les deux fichiers ensemble montrent un signal collecté qui ne peut pas atteindre le verdict.

Quelles branches réduisent le verdict à un moteur ou une valeur

Les cas restants rendent visible la spécificité de chaque branche, et la spécificité est ce dont un lecteur a besoin pour savoir quel verdict est seulement applicable. Chaque ligne ci-dessous est un cas que la sonde a exécuté :

DétecteurCondition fournie par l’exécutionRésultat
detectUserAgentchaîne d’user agent contenant HeadlessChromeheadless_chrome
detectUserAgentchaîne ordinaire de Chrome de bureau sous Windowsfalse
detectWebGLvendor Brian Paul, renderer Mesa OffScreenheadless_chrome
detectWebGLrenderer ANGLE sur un adaptateur NVIDIAfalse
detectProcessobjet de processus de type renderer avec une version electronelectron
detectNotificationPermissionspermission à vrai, type de navigateur chromeheadless_chrome
detectEvalLengthInconsistencyevalLength 33 sur le moteur chromiumfalse
detectEvalLengthInconsistencyevalLength 37 sur le moteur chromiumtrue
detectLanguagesLengthInconsistencytableau de langues videheadless_chrome
detectPluginsLengthInconsistencyzéro plugin, chromium chromeheadless_chrome
detectWindowExternalchaîne externe contenant Sequentumsequentum
detectMimeTypesConsistentvaleur de cohérence fausseunknown
detectDocumentAttributesliste de clés du document contenant seleniumselenium
detectProductSubvaleur productSub 20100101 dans chromeunknown

Deux lignes méritent une attention particulière car elles sont contre-intuitives si l’on ne lit que les noms des détecteurs. La paire detectEvalLengthInconsistency montre que la même fonction ne renvoie rien pour une longueur et un bot pour une autre sur le même moteur : 33 est accepté car Chromium figure dans la liste autorisée pour cette longueur, tandis que 37 est rejeté car Chromium n’y figure pas pour cette autre. La fonction ne demande pas si la valeur paraît étrange ; elle demande si la valeur est étrange pour le moteur détecté.

La ligne detectNotificationPermissions montre une porte de navigateur facile à manquer. src/detectors/notification_permissions.ts ne renvoie rien sauf si browserKind.value === BrowserKind.Chrome, donc la même valeur de permission ne peut produire un verdict que dans une seule famille de navigateur. Qui teste cette surface dans un profil rapportant une autre marque ne verra aucun verdict et pourrait conclure à tort que la valeur a été acceptée.

Les lignes true sans nom se comportent différemment des lignes nommées car src/api.ts convertit true en BotKind.Unknown. detectEvalLengthInconsistency à la longueur 37 imprime bot: true dans la liste des cas, ce qui devient unknown dans un résultat final, tandis que detectMimeTypesConsistent avec une liste incohérente imprime déjà bot: unknown car elle renvoie directement le nom. Un rapport qui n’affiche que unknown porte donc moins d’information que le détecteur qui l’a produit, et le nom du détecteur est la partie qui survit à la perte.

Rendre l’exécution reproductible depuis le relevé

Une sonde n’est utile que si une autre personne peut la rejouer et obtenir les mêmes lignes. Quatre champs du relevé le permettent, et chacun comble une lacune que les autres laissent ouverte.

Le premier est le commit 193c471ac70530afcebe5b09664c09f611d19218, car detectors/index.ts comme sources/index.ts sont modifiés entre versions et une clé de registre présente aujourd’hui peut disparaître demain. Le second est l’étape d’empaquetage, car la sortie d’esbuild n’est pas identique octet pour octet d’une version à l’autre, donc enregistrer l’empreinte de sortie permet de séparer un changement de code d’un changement d’outil. Le troisième est la convention d’états, car la sonde écrit state: 0 pour un succès et state: -1 pour une source illisible, et qui suppose une autre table lira chaque résultat à l’envers. Le quatrième est la liste de cas elle-même, car un false de detectWebDriver signifie « la garde a vu un état non réussi » au cas 8 et « l’indicateur était faux » au cas 3, et seule l’étiquette du cas les distingue.

Deux habitudes gardent le relevé honnête. Préférez une paire discriminante à un cas isolé : les cas 3 et 2 ensemble montrent que nettoyer l’indicateur change quel détecteur parle, ce qu’un cas 3 seul ne montre pas, et les cas 1 et 8 ensemble montrent que présent-et-vrai et absent divergent sur une branche tout en se confondant sur une autre. Et ne décrivez jamais la sonde comme un test de navigateur. Elle exécute des fonctions pures figées sur des dictionnaires écrits à la main ; elle ne lit aucune page, n’ouvre aucun profil et ne contacte aucun service, donc une affirmation sur ce que rapporte une session réelle demande encore une exécution autorisée séparée.

La même discipline s’applique lors d’une montée de version. Rejouez l’empaquetage au nouveau commit, relisez les deux fichiers de registre et comparez la liste de cas plutôt que la ligne de résumé. Un changement du registre change quels détecteurs peuvent parler ; un changement dans un détecteur change ce qu’un cas renvoie ; et un changement dans une source change quels états sont seulement atteignables. Comparer seulement la ligne finale bot: true ou bot: false confond les trois en une, et le lecteur suivant doit redécouvrir lequel s’est produit.

Lire un verdict sans surestimer

Un verdict de cette bibliothèque étaye une affirmation étroite : dans ce commit, l’un des détecteurs enregistrés, avec les états de composants qu’il a reçus, a renvoyé le type nommé. Il n’étaye pas une affirmation sur une personne, sur une intention, ni sur la façon dont un service hébergé traitera la session. Le repliement étant une écrasement, le type rapporté peut nommer un signal moins précis qu’un autre qui s’est aussi déclenché, et comme plusieurs détecteurs renvoient true seul, un type unknown peut remplacer un détecteur qui avait une raison précise.

Gardez trois choses dans le relevé de revue et il reste utile. Le commit immuable, car l’ordre du registre et les listes de propriétés changent entre versions. Les états de composants, car chaque détecteur vérifie un état avant de lire une valeur, et les branches de faux négatif vivent dans cette garde. Le nom du détecteur, car une exécution peut produire des types différents et seul le nom dit lequel a produit le verdict. Un quatrième champ aide quand la question porte sur une surface précise : le nombre de sources collectées, pour qu’un lecteur voie si un signal absent n’a jamais été mesuré ou a été mesuré puis ignoré.

La page de documentation sur la cohérence de l’automatisation décrit comment BotBrowser garde navigator.webdriver cohérent avec le profil chargé et comment vérifier ce réglage dans une page maîtrisée. Cette revue ajoute le côté lecture : ce qu’un détecteur public fait de cette valeur, quelles surfaces compagnes il lit en même temps, et lesquelles de ses propres branches restent muettes. Les deux moitiés répondent à des questions différentes et aucune ne remplace l’autre.

Pour toute conclusion sur un service réel, utilisez un résultat d’intégration autorisé de ce service plutôt que d’extrapoler depuis cette revue de code. Un état de composant relevé dans un fixture et un verdict de production sont deux types de preuve différents, et c’est dans l’écart entre les deux que se produit la surestimation.

Practical conclusion

Épinglez le commit, exécutez les fonctions de détection sur des états de composants explicites et consignez le nom du détecteur à côté du verdict. Dans ce commit le registre expose 18 détecteurs sur 23 sources, detectRTT est exporté mais non enregistré, nettoyer l’indicateur WebDriver laisse intactes les listes de propriétés, et deux gardes, un composant illisible et un document sans focus, se résolvent en aucun verdict.

Sources

Next reading

Consultez collecte et provenance dans FingerprintJS OSS pour le côté collecte du même fournisseur, et détection et prévention de l’automatisation quand la question est quelles surfaces une page peut lire.

#BotD#Détection De Bots#Signaux D’automatisation#États De Composants#Provenance De La Source

Cet article fait partie du guide : Qu'est-ce qu'une empreinte de navigateur ?

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.