Volver al Centro de conocimiento
Huella digital

Detección de modo sin cabeza en CreepJS: tres veredictos

Una sonda sobre CreepJS: las señales blandas, los bits duros y las comprobaciones de envoltorio se puntúan por separado, así que una página limpia sigue reportando un porcentaje.

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 permite reproducir un fixture del mismo origen autorizado bajo un perfil declarado y guardar un registro acotado de lo que una página reportó sobre su propio entorno, pero no puede hacer que una superficie delgada parezca gruesa, no puede convertir una superficie restaurada en una señal que nunca se comprobó, ni puede cambiar cómo una biblioteca agrupa sus propios hallazgos. Esta revisión lee CreepJS desde su código público, reproduce los tres grupos de veredicto que calcula y registra cuánto vale una sola señal dentro de cada grupo, cuántas señales blandas necesita una página para que el grupo blando se lea como sin cabeza, qué bits duros sobreviven a una página que limpió los obvios, y por qué un porcentaje de un grupo no se puede comparar con el de otro.

Resumen

  • CreepJS reporta el modo sin cabeza como tres porcentajes separados, no uno: likeHeadless sobre 16 señales blandas, headless sobre 3 bits duros, y un grupo de envoltorio sobre 5 comprobaciones.
  • Un escritorio con cabeza y gestor de ventanas real puntúa likeHeadless=0% headless=0% wrapper=0%.
  • El perfil sin cabeza clásico llega a likeHeadless=44% (7/16) mientras su grupo duro se queda en 0%, porque las señales blandas y los bits duros miden cosas distintas.
  • Un controlador que filtra su bandera y su token puntúa headless=100% (3/3), el único grupo que llega al máximo en esta ejecución.
  • Una sola señal vale 6% en el grupo blando, 33% en el duro y 20% en el de envoltorio, así que el mismo hallazgo pesa muy distinto según el grupo.
  • Una página que restaura el objeto chrome, los plugins, los tipos MIME, los datos de UA y el visor PDF sigue reportando likeHeadless=13% (2/16), porque la geometría de pantalla quedó intacta.
  • Fijar un tamaño de ventana plausible lo baja a likeHeadless=6% (1/16), con un renderizador de software como única señal blanda restante.
  • Un envoltorio que descoloca el objeto chrome, rompe su runtime y modifica Function.prototype.toString puntúa wrapper=60% (3/5) mientras los otros dos grupos quedan en cero.
  • La señal noTaskbar es igualdad de geometría pura, así que una ventana maximizada en un escritorio real también la dispara.

Contenido

El primer panel muestra tres grupos de porcentaje separados: dieciséis señales blandas, tres bits duros y cinco comprobaciones de envoltorio. El segundo muestra un perfil sin cabeza clásico llegando a 44% en el grupo blando mientras el duro se queda en cero. El tercero muestra un controlador llegando a 100% en el grupo duro. El cuarto muestra una página limpia que aún reporta 13% y luego 6% al fijar un tamaño de ventana, junto a un envoltorio que puntúa 60% solo en su grupo.

Por qué los tres grupos están separados

La detección de modo sin cabeza suele describirse como una pregunta con una respuesta, y CreepJS informa deliberadamente tres respuestas. Los grupos vienen del mismo módulo y miden tipos distintos de evidencia:

headed-desktop -> likeHeadless=0% (0/16) headless=0% (0/3) wrapper=0% (0/5)

El primer grupo tiene dieciséis señales blandas: sin objeto chrome, sin plugins, sin tipos MIME, plataforma de client hints vacía, visor PDF desactivado, pantalla sin barra de tareas, viewport igual a la pantalla, renderizador de software y varios huecos de capacidad. Cada una es una superficie que parece delgada, y cada una es también una superficie que una implementación legítima puede tener.

El indicador del controlador en ese grupo es el mismo valor que lee un detector de mentiras cuando comprueba si un nativo todavía se reporta, y funciones nativas sustituidas y qué ve una lista de toString lo registra. El segundo grupo tiene tres bits duros, y son duros en otro sentido: la bandera del controlador, un token HeadlessChrome en el user agent, y el mismo token dentro de un Worker. No describen una superficie delgada; describen un navegador que se anunció.

Una cadena de decisión construida con varios grupos se puntúa y se filtra de forma distinta a estos tres porcentajes, y por qué una señal rara no condena a un navegador lo registra. El tercer grupo tiene cinco comprobaciones de envoltorio: si se detecta un proxy de iframe, si el objeto chrome está en una posición implausible del orden de claves de window, si chrome.runtime se comporta como un objeto real, si se modificó Function.prototype.toString, y si los GPU del hilo principal y del Worker discrepan. Describen una página que editó su propio entorno, no una que simplemente parece delgada.

Tres grupos, tres tipos de afirmación. Fundirlos en un número perdería la distinción entre «este entorno es mínimo», «este navegador dijo que está automatizado» y «esta página se editó a sí misma», y esos tres casos piden respuestas completamente distintas.

Cuánto vale una señal en cada grupo

Los porcentajes no son comparables entre grupos, porque cada uno es una parte de un denominador distinto. La sonda declara el peso de un solo hallazgo en cada uno:

weight:likeHeadless -> one signal = 6%
weight:headless -> one signal = 33%
weight:wrapper -> one signal = 20%

Una señal blanda mueve el grupo blando seis puntos, mientras un bit duro mueve el grupo duro treinta y tres. Un informe que imprime «44%» y «33%» uno al lado del otro invita a tratarlos como la misma escala, y no lo son: 44% del grupo blando son siete superficies delgadas, y 33% del grupo duro es un navegador que se anunció.

El grupo blando además necesita muchas señales para leerse como mayormente sin cabeza:

likeHeadless:1-of-16 -> 6%
likeHeadless:4-of-16 -> 25%
likeHeadless:8-of-16 -> 50%
likeHeadless:12-of-16 -> 75%
likeHeadless:16-of-16 -> 100%

La mitad del grupo exige ocho superficies delgadas a la vez. Es un listón difícil de cruzar por accidente, y por eso el porcentaje blando describe cuán mínimo es un entorno en lugar de la probabilidad de que sea sin cabeza.

La línea de comparación dice lo mismo desde el otro lado:

compare -> all-16-soft=100% vs one-hard=33% (groups are reported separately)

Una página con las dieciséis señales blandas se lee como 100% en su grupo, y una página con un bit duro se lee como 33% en el suyo. Ninguno es mayor en ningún sentido significativo, porque responden a preguntas distintas. Quien ordena los dos porcentajes está ordenando dos unidades distintas.

Qué señales sobreviven a una página limpia

Lo interesante es qué ocurre cuando una página ordena las señales obvias. La sonda alimenta un estado donde el objeto chrome, los plugins, los tipos MIME, la plataforma de client hints y el visor PDF se restauraron, y solo quedó la geometría de pantalla como estaba:

cleaned-obvious-tells -> likeHeadless=13% (2/16) headless=0% (0/3) wrapper=0% (0/5)
cleaned-plus-window -> likeHeadless=6% (1/16) headless=0% (0/3) wrapper=0% (0/5)

Restaurar cinco superficies deja todavía un 13%, porque las dos señales de geometría nunca se tocaron. Fijar un tamaño de ventana plausible lo lleva a 6%, con un renderizador de software como última señal blanda. Los grupos duro y de envoltorio se quedan en cero todo el tiempo, que es la parte útil: limpiar superficies no crea hallazgos de envoltorio, solo elimina los blandos.

Esa asimetría importa a quien lee un informe después de un cambio. Una página que pasó de 44% a 6% no se volvió más ni menos sin cabeza; se volvió menos mínima. El grupo duro, que es el que describe un navegador que se anuncia, no se movió nada, y quien solo mire el total pensará que algo mejoró cuando la afirmación de fondo no cambió.

Lo contrario también vale. El grupo de envoltorio puede subir sin ninguna señal blanda:

wrapper-wrapped -> likeHeadless=0% (0/16) headless=0% (0/3) wrapper=60% (3/5)

Una página que mantiene toda superficie gruesa pero descoloca el objeto chrome, rompe su runtime y modifica Function.prototype.toString puntúa sesenta por ciento en el grupo de envoltorio mientras los otros dos quedan vacíos. Es un hallazgo completamente distinto de un entorno delgado, y solo es visible porque los grupos están separados.

Dónde las señales blandas exageran un caso

Varias señales blandas son condiciones ordinarias que una implementación legítima también produce, y la sonda fija dos.

La señal de permisos necesita que dos condiciones independientes coincidan antes de dispararse:

permissions-bug -> prompt+denied=true prompt+default=false granted+denied=false

Solo se dispara cuando una consulta de permisos reporta prompt mientras el permiso de notificaciones es denied. Ninguna de las dos condiciones sola la dispara, lo que es una restricción deliberada: un navegador que simplemente deniega notificaciones por política no queda marcado, y solo el par inconsistente sí.

La señal de la barra de tareas es el caso opuesto, y es la más débil del grupo:

no-taskbar -> 1080/1080+1920/1920=true 1080/1040+1920/1920=false

Es igualdad simple entre la pantalla y su área disponible, así que cualquier ventana maximizada en un escritorio real con la barra oculta la dispara, igual que un kiosco o un equipo a pantalla completa. Un solo punto del grupo blando no vale mucho por sí mismo, y este vale menos de lo que sugiere su seis por ciento.

La lección para una implementación es sobre qué superficies dedicar esfuerzo. Restaurar plugins y tipos MIME mueve el grupo blando, pero la geometría y el nombre del renderizador son los que quedan después de ordenar todo lo demás. Un perfil que acierta las superficies fáciles y deja la geometría intacta se lee como seis por ciento blando, y quien no conozca los tamaños de grupo lo leerá como «casi nada» cuando en realidad es una señal concreta.

Registrar una lectura sin cabeza

Una lectura sin cabeza sostiene una afirmación acotada: esta versión de la biblioteca, estos estados de señal, produjeron estos tres porcentajes. Cuatro campos la mantienen verificable:

  1. El commit de la biblioteca y las tres listas de señales, porque una lista que gana o pierde una clave cambia cada porcentaje sin ningún cambio en la página.
  2. Los nombres de las señales disparadas por grupo, porque seis por ciento y treinta y tres por ciento describen tipos distintos de evidencia y el número solo esconde cuál.
  3. Los denominadores de cada grupo, porque quien compara un porcentaje blando con uno duro está comparando dos unidades distintas.
  4. Las señales que no se evaluaron, porque varias necesitan una página viva y una reproducción estática las aporta como entradas.

Fuentes públicas

Conclusión

Lee los tres porcentajes como tres hallazgos separados en lugar de un solo marcador, porque el porcentaje blando describe cuán mínimo es un entorno, el duro describe un navegador que se anunció, y el de envoltorio describe una página que se editó a sí misma. Trata una caída de cuarenta y cuatro por ciento a seis por ciento como un cambio en cuánto entorno se expone, no como un cambio en si el navegador es sin cabeza, ya que el grupo duro no se movió. Cuando un informe muestra un grupo subiendo mientras los otros quedan vacíos, busca las comprobaciones de envoltorio en lugar de superficies ausentes. Para cómo una biblioteca reduce los resultados de grupo a una decisión ordenada, lee los grupos de señales de CreepJS y cómo los combina; para lo que puede ver una sola comprobación de texto fuente, lee funciones nativas sustituidas y qué ve una lista de toString.

#CreepJS#Detección Sin Cabeza#Señales Del Navegador#Privacidad#Validación

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.