Volver al Centro de conocimiento
Huella digital

BotD: qué detectores se ejecutan de verdad

Lee el registro fijado de FingerprintJS BotD y ejecuta sus detectores sobre estados de componentes para ver qué comprobaciones disparan, cuáles callan y cuáles fallan en abierto.

BotBrowser Team

Documentación

Quieres la documentación estructurada de Huella digital?

Este artículo forma parte de la biblioteca editorial. Para pasos de configuración, material de referencia y actualizaciones continuas, entra en la sección de docs.

BotBrowser soporta repetir un fixture autorizado del mismo origen bajo un perfil declarado y conservar observaciones acotadas sobre qué superficies de automatización puede leer una página; no puede ejecutar un servicio de detección de terceros, decidir el veredicto de un proveedor ni prometer que limpiar una superficie limpia las demás. Esta revisión sigue el código público de FingerprintJS BotD en el commit 193c471ac70530afcebe5b09664c09f611d19218, lee sus dos archivos de registro y luego ejecuta sus propias funciones detectoras contra estados de componentes explícitos, para que lo afirmado abajo descanse en salida observada y no solo en la lectura del código.

TL;DR

  • BotD recopila 23 fuentes con nombre y expone 18 detectores; un archivo detector del árbol fijado se exporta pero nunca se registra, así que su rama no puede llegar a un veredicto.
  • Un veredicto es un pliegue sobre detectores, no una votación: el último detector que informa un nombre de bot sustituye al anterior, así que el tipo informado depende del orden del registro.
  • Un componente cuya fuente lanzó una excepción conserva un estado de no éxito, y cada detector vigila ese estado, así que un valor ausente suele resolverse en ningún veredicto y no en una conjetura.
  • Ejecutar las funciones fijadas sobre estados sintéticos muestra una superficie que se limpia mientras otra sigue informando automatización, dos ramas exactas de falso negativo y un resultado true sin nombre que pierde el nombre del detector.
  • El comportamiento observado pertenece a este commit, no a todos los detectores ni a una decisión de producción alojada.

Contents

SRC_23 alimenta DET_18, luego WEBDRIVER_FLAG, DISTINCTIVE_PROPS, WINDOW_0X0 con DOC_FOCUS_GUARD y RTT_UNREGISTERED

Qué contiene realmente un registro fijado

src/sources/index.ts registra 23 funciones fuente: android, browserKind, browserEngineKind, documentFocus, userAgent, appVersion, rtt, windowSize, pluginsLength, pluginsArray, errorTrace, productSub, windowExternal, mimeTypesConsistent, evalLength, webGL, webDriver, languages, notificationPermissions, documentElementKeys, functionBind, process y distinctiveProps. Cada fuente es una función simple que devuelve un valor o lanza un error. Una fuente que no puede leer su superficie lanza un BotdError con un valor State, y collect() en src/api.ts convierte ese error en un componente { state, error } en lugar de un valor.

src/detectors/index.ts registra 18 funciones detectoras en un orden de declaración fijo: detectAppVersion, detectDocumentAttributes, detectErrorTrace, detectEvalLengthInconsistency, detectFunctionBind, detectLanguagesLengthInconsistency, detectNotificationPermissions, detectPluginsArray, detectPluginsLengthInconsistency, detectProcess, detectUserAgent, detectWebDriver, detectWebGL, detectWindowExternal, detectWindowSize, detectMimeTypesConsistent, detectProductSub y detectDistinctiveProperties.

El directorio de detectores contiene 19 archivos. src/detectors/rtt.ts exporta detectRTT, y detectRTT no aparece en ese objeto de registro. Comparar las claves del registro con los nombres exportados en el mismo checkout da 19 archivos y 18 nombres registrados, con rtt.ts como único archivo cuya exportación nunca se alcanza. Es un hecho del código de un commit; no dice nada de una versión posterior ni de un servicio alojado que importe la función directamente.

El pliegue en src/api.ts decide cómo se produce un veredicto único. detect() recorre detectors en orden de declaración, llama a cada uno con el diccionario de componentes y asigna finalDetection cada vez que un detector informa un bot. Un detector que devuelve una cadena registra esa cadena como botKind; un detector que devuelve true sin más registra BotKind.Unknown. Como la asignación gana a la acumulación, el tipo informado pertenece al último detector del registro que disparó, no al más específico ni al más confiado. collect() ejecuta las fuentes en paralelo con Promise.all, así que el diccionario está completo antes de que un detector lo vea, y solo el orden de detectores decide el tipo.

Ejecutar los detectores sobre estados de componentes explícitos

El diccionario de componentes es la única entrada que lee cada detector, así que las funciones fijadas pueden ejecutarse contra estados escritos a mano sin navegador, perfil ni petición de red. El comando de abajo empaqueta el árbol fijado con esbuild y llama a los detectores exportados, imprimiendo una línea por caso:

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

La sonda aporta un diccionario base con android: false, browserKind: 'chrome', browserEngineKind: 'chromium' y documentFocus: true, y luego reemplaza uno o dos componentes por caso. Un componente exitoso lleva { value, state: 0 } y uno ilegible lleva { state: -1, error }, igual que el enum State de src/types.ts (Success = 0, Undefined = -1, UnexpectedBehaviour = -3). La salida observada de las funciones fijadas:

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

Los ocho casos de arriba los deciden cuatro funciones pequeñas, que conviene leer directamente en lugar de inferir de la salida. src/detectors/webdriver.ts es una sola línea: devuelve BotKind.HeadlessChrome solo cuando webDriver.state === State.Success && webDriver.value. src/detectors/window_size.ts comprueba primero que windowSize y documentFocus estén en State.Success, vuelve antes si documentFocus.value es falso, y solo entonces compara outerWidth y outerHeight con cero. src/detectors/plugins_array.ts devuelve BotKind.HeadlessChrome cuando pluginsArray.state === State.Success && !pluginsArray.value. src/detectors/distinctive_properties.ts recorre el objeto devuelto y devuelve el primer nombre de bot cuya marca sea verdadera.

La misma disciplina de lectura aplica cuando un detector se limpia y otro no. Para el lado de recolección del mismo proveedor, recopilación y procedencia en FingerprintJS OSS muestra cómo los componentes se vuelven un resumen, mientras que detección y prevención de automatización enumera el conjunto más amplio de superficies que una página puede inspeccionar, así que el veredicto de una sola función nunca es el cuadro completo.

Dónde limpiar una superficie deja otra señal

Los casos 1 a 3 son el resultado más claro de la ejecución. Con webDriver en un true exitoso, detectWebDriver informa headless_chrome. Con webDriver en un false exitoso pero distinctiveProps con { webdriver: true }, detectWebDriver no informa nada y detectDistinctiveProperties informa webdriver en su lugar. Con ambas superficies limpias, detectDistinctiveProperties no informa nada y el caso queda en bot: false.

De ahí salen dos consecuencias, y ambas importan a quien cita un veredicto de esta biblioteca. Las dos comprobaciones son independientes: limpiar la bandera no limpia la lista de propiedades, porque la lista se lee con Object.getOwnPropertyNames(window) y Object.getOwnPropertyNames(document) dentro de src/sources/distinctive_properties.ts, no de la bandera. Y el tipo informado cambia con la superficie que sobrevive: headless_chrome y webdriver son etiquetas distintas producidas por la misma biblioteca desde el mismo diccionario, así que un informe que cita una mientras la otra existía está citando orden de registro, no el conjunto de señales presentes.

La lista de propiedades es lo bastante concreta para verificarla a mano. Cubre 14 tipos de bot y lista 26 entradas de window (una de ellas un patrón y no un nombre literal) y 17 entradas de document, desde awesomium y RunPerfTest en el lado CEF hasta callPhantom y _phantom para PhantomJS. El grupo WebDriver por sí solo lleva webdriver, __webdriverFunc, __lastWatirAlert, _WEBDRIVER_ELEM_CACHE y ChromeDriverw en window, y __webdriver_script_fn, __driver_evaluate, __fxdriver_unwrapped, $cdc_asdjflasutopfhvcZLmcfl_ y $chrome_asyncScriptInfo en document. Una revisión que inspecciona solo window se pierde el grupo de document, y una que inspecciona solo la bandera se pierde ambos.

Qué comprobaciones fallan en abierto con un valor ausente

El caso 8 muestra la primera rama de falso negativo. Cuando el componente webDriver está en State.Undefined, detectWebDriver devuelve false, porque la guarda exige State.Success antes de leer el valor. Un navigator.webdriver ilegible, como una página que oculta la propiedad, un realm donde la lectura lanza un error o un envoltorio que no devuelve nada, queda registrado como ausencia de evidencia de automatización en lugar de como señal positiva. Es un diseño defendible para una biblioteca que prefiere el silencio a la conjetura, pero significa que un valor ausente y un valor limpio son indistinguibles en el veredicto.

El caso 5 muestra la segunda rama de falso negativo, y es más fácil de perder porque los componentes están presentes y son válidos. src/detectors/window_size.ts vuelve antes cuando documentFocus.value es falso, antes de que corra la comparación de tamaño cero. Una ventana headless que informa outerWidth y outerHeight de cero solo se marca mientras el documento tiene el foco. Una pestaña abierta en segundo plano, o una captura que corre antes de que llegue el foco, produce medidas idénticas y ningún veredicto. El comentario del código declara la intención, que una pestaña nueva en segundo plano también informa tamaño exterior cero, así que el retorno temprano es deliberado, pero la consecuencia para un lector es que los mismos números llevan a veredictos distintos según el momento del foco.

La tercera laguna de esta ejecución es de otro tipo: no un falso negativo dentro de un detector, sino una rama sin camino al veredicto. detectRTT contiene una condición real que devuelve BotKind.HeadlessChrome cuando rtt.value === 0, y el registro fijado nunca la llama. La fuente rtt sí se recopila, así que el valor se mide y se coloca en el diccionario, pero ningún detector registrado lo lee. Leer rtt.ts solo exagera la biblioteca; leer detectors/index.ts solo esconde la rama. Solo los dos archivos juntos muestran una señal recogida que no puede llegar al veredicto.

Qué ramas reducen el veredicto a un motor o un valor

Los casos restantes hacen visible la especificidad de cada rama, y la especificidad es lo que un lector necesita para saber qué veredicto es siquiera aplicable. Cada fila de abajo es un caso que la sonda ejecutó:

DetectorCondición que aportó la ejecuciónResultado
detectUserAgentcadena de user agent con HeadlessChromeheadless_chrome
detectUserAgentcadena ordinaria de Chrome de escritorio en Windowsfalse
detectWebGLvendor Brian Paul, renderer Mesa OffScreenheadless_chrome
detectWebGLrenderer ANGLE sobre un adaptador NVIDIAfalse
detectProcessobjeto de proceso con tipo renderer y versión de electronelectron
detectNotificationPermissionspermiso en verdadero, tipo de navegador chromeheadless_chrome
detectEvalLengthInconsistencyevalLength 33 en el motor chromiumfalse
detectEvalLengthInconsistencyevalLength 37 en el motor chromiumtrue
detectLanguagesLengthInconsistencyarray de idiomas vacíoheadless_chrome
detectPluginsLengthInconsistencycero plugins, chromium chromeheadless_chrome
detectWindowExternalcadena externa con Sequentumsequentum
detectMimeTypesConsistentvalor de consistencia falsounknown
detectDocumentAttributeslista de claves del elemento document con seleniumselenium
detectProductSubvalor productSub 20100101 en chromeunknown

Dos filas merecen atención aparte porque son contraintuitivas si solo lees nombres de detectores. El par de detectEvalLengthInconsistency muestra que la misma función no devuelve nada para una longitud y un bot para otra en el mismo motor: 33 se acepta porque Chromium está en la lista permitida para esa longitud, mientras 37 se rechaza porque Chromium no está en la lista permitida para esa otra. La función no pregunta si el valor parece raro; pregunta si el valor es raro para el motor que se detectó.

La fila de detectNotificationPermissions muestra una puerta de navegador fácil de pasar por alto. src/detectors/notification_permissions.ts no devuelve nada salvo que browserKind.value === BrowserKind.Chrome, así que el mismo valor de permiso solo puede producir veredicto en una familia de navegador. Quien prueba esa superficie en un perfil que informa otra marca verá ningún veredicto y podría concluir por error que el valor fue aceptado.

Las filas de true sin nombre se comportan distinto de las filas con nombre porque src/api.ts convierte true en BotKind.Unknown. detectEvalLengthInconsistency con longitud 37 imprime bot: true en la lista de casos, lo que en un resultado final se vuelve unknown, mientras que detectMimeTypesConsistent con una lista inconsistente ya imprime bot: unknown porque devuelve el nombre directamente. Un informe que muestra solo unknown lleva por tanto menos información que el detector que lo produjo, y el nombre del detector es la parte que sobrevive a la pérdida.

Hacer la ejecución repetible desde el registro

Una sonda solo sirve si otra persona puede repetirla y llegar a las mismas líneas. Cuatro campos del registro lo hacen posible, y cada uno cierra una laguna que los otros dejan abierta.

El primero es el commit 193c471ac70530afcebe5b09664c09f611d19218, porque tanto detectors/index.ts como sources/index.ts se editan entre versiones y una clave de registro que hoy existe puede desaparecer mañana. El segundo es el paso de empaquetado, porque la salida de esbuild no es idéntica byte a byte entre versiones, así que registrar el resumen de la salida permite separar un cambio de código de un cambio de herramienta. El tercero es la convención de estados, porque la sonda escribe state: 0 para éxito y state: -1 para fuente ilegible, y quien asuma otro mapeo leerá cada resultado al revés. El cuarto es la propia lista de casos, porque un false de detectWebDriver significa «la guarda vio un estado de no éxito» en el caso 8 y «la bandera era falsa» en el caso 3, y solo la etiqueta del caso los distingue.

Dos hábitos mantienen honesto el registro. Prefiere un par discriminante a un caso suelto: los casos 3 y 2 juntos muestran que limpiar la bandera cambia qué detector habla, algo que un caso 3 aislado no muestra, y los casos 1 y 8 juntos muestran que presente-y-verdadero y ausente divergen en una rama mientras colapsan en otra. Y nunca describas la sonda como una prueba de navegador. Ejecuta funciones puras fijadas sobre diccionarios que un revisor escribió a mano; no lee ninguna página, no abre ningún perfil y no contacta ningún servicio, así que una afirmación sobre lo que informa una sesión real sigue necesitando una ejecución autorizada aparte.

La misma disciplina se aplica al actualizar el código. Vuelve a correr el empaquetado en el nuevo commit, relee los dos archivos de registro y compara la lista de casos en vez de la línea de resumen. Un cambio en el registro cambia qué detectores pueden hablar; un cambio dentro de un detector cambia qué devuelve un caso; y un cambio en una fuente cambia qué estados son siquiera alcanzables. Comparar solo la línea final bot: true o bot: false junta los tres tipos de cambio en uno, y el siguiente lector tiene que redescubrir cuál ocurrió.

Leer un veredicto sin exagerar

Un veredicto de esta biblioteca sostiene una afirmación estrecha: en este commit, uno de los detectores registrados, dados los estados de componentes que recibió, devolvió el tipo nombrado. No sostiene una afirmación sobre una persona, sobre intención ni sobre cómo tratará la sesión un servicio alojado. Como el pliegue es una sobrescritura, el tipo informado puede nombrar una señal menos específica que otra que también disparó, y como varios detectores devuelven true sin nombre, un tipo unknown puede sustituir a un detector que tenía una razón concreta.

Guarda tres cosas en el registro de revisión y el registro sigue siendo útil. El commit inmutable, porque tanto el orden del registro como las listas de propiedades cambian entre versiones. Los estados de componentes, porque cada detector vigila un estado antes de leer un valor, y las ramas de falso negativo viven en esa guarda. El nombre del detector, porque una ejecución puede producir tipos distintos y solo el nombre dice cuál produjo el veredicto. Un cuarto campo ayuda cuando la pregunta es sobre una superficie concreta: el número de fuentes recogidas, para que un lector vea si una señal ausente nunca se midió o se midió y se ignoró.

La página de docs sobre consistencia de automatización describe cómo BotBrowser mantiene navigator.webdriver coherente con el perfil cargado y cómo confirmar ese ajuste en una página controlada. Esta revisión añade el lado de la lectura: qué hace un detector público con ese valor, qué superficies acompañantes lee a la vez y cuáles de sus ramas callan. Las dos mitades responden preguntas distintas y ninguna sustituye a la otra.

Para cualquier conclusión sobre un servicio real, usa un resultado de integración autorizado de ese servicio en lugar de extrapolar desde esta revisión de código. Un estado de componente registrado en un fixture y un veredicto de producción son tipos distintos de evidencia, y la brecha entre ambos es donde ocurre la exageración.

Practical conclusion

Fija el commit, ejecuta las funciones detectoras sobre estados de componentes explícitos y registra el nombre del detector junto al veredicto. En este commit el registro expone 18 detectores sobre 23 fuentes, detectRTT se exporta pero no se registra, limpiar la bandera de WebDriver deja intactas las listas de propiedades, y dos guardas, un componente ilegible y un documento sin foco, resuelven en ningún veredicto.

Sources

Next reading

Usa recopilación y procedencia en FingerprintJS OSS para el lado de recolección del mismo proveedor, y detección y prevención de automatización cuando la pregunta es qué superficies puede leer una página.

#BotD#Detección De Bots#Señales De Automatización#Estados De Componentes#Procedencia De La Fuente

Este artículo forma parte de la guía: ¿Qué es la huella digital del navegador?

Lleva BotBrowser de la investigación a producción

Usa estas guías para entender el modelo y después avanzar hacia validación multiplataforma, contextos aislados y despliegue de navegador preparado para escalar.