Desde la versión 148 de Chrome, una función tan discreta como Math.tanh() se convirtió en una herramienta de fingerprinting matemático: los sistemas anti-bot descubrieron que, con una sola llamada, pueden saber si un navegador corre sobre Windows, macOS o Linux — incluso cuando el User-Agent dice otra cosa. El motivo no está en el código JavaScript, sino en los últimos bits del número que la función devuelve.
TL;DR
- Desde Chrome 148 (V8 14.8.57), Math.tanh() usa la librería matemática del sistema operativo en vez de un algoritmo interno de V8.- En Math.tanh(0.8), Linux (glibc), macOS y Windows devuelven tres resultados distintos, con diferencias de 1 a 2 ULP.- Las funciones trigonométricas de CSS (sin, cos, atan2) también leen la libm del sistema y filtran el mismo dato.- macOS tiene dos bibliotecas matemáticas que no coinciden entre sí: libsystem_m y Accelerate, con divergencias de 10% a 89%.- El cambio llegó con el commit c1486295ae5 de V8, que sustituyó fdlibm interno por una llamada a std::tanh del sistema.- Chrome 147 y versiones anteriores no filtran este dato; Chrome 148, 149 y 150 sí.- Fingir un sistema operativo con matemática de otro contradice la propia identidad que declara el navegador.
Introducción al fingerprinting matemático
La huella digital de navegadores (fingerprinting) suele apoyarse en señales conocidas: el hash de un canvas, los parámetros de WebGL, la lista de fuentes instaladas o las particularidades del motor de audio. Todas comparten un problema para quien construye herramientas anti-detección: son señales visibles, documentadas y, con suficiente esfuerzo, replicables. La investigación publicada por el equipo de Scrapfly describe una señal distinta, mucho más difícil de falsificar, escondida en algo que cualquier desarrollador considera trivial: el resultado de una operación matemática.
El hallazgo tiene un nombre técnico simple pero un efecto profundo: la función Math.tanh() de JavaScript devuelve bits distintos según el sistema operativo que ejecuta el navegador. No se trata de una diferencia de configuración ni de una extensión instalada — es una consecuencia directa de cómo cada sistema operativo implementa sus funciones matemáticas de bajo nivel.
Qué pasó
El experimento es reproducible en cualquier consola de DevTools. Al ejecutar Math.tanh(0.8) en Chrome 150 genuino, instalado en tres máquinas reales, se obtienen tres resultados diferentes:
Math.tanh(0.8);
// Linux (glibc): 0.6640367702678491
// macOS (libsystem_m): 0.664036770267849
// Windows (UCRT): 0.6640367702678489
La diferencia parece insignificante — estamos hablando del último o penúltimo dígito de un número de punto flotante de doble precisión — pero es sistemática y reproducible. No es ruido ni una condición de carrera: la misma máquina, con el mismo Chrome, siempre devuelve el mismo patrón de bits para el mismo sistema operativo. Eso convierte una operación matemática ordinaria en una tabla de consulta: bastan uno o dos valores de entrada bien elegidos para distinguir Linux, macOS y Windows entre sí.
No todos los valores de entrada sirven. Math.tanh(0.5), por ejemplo, da exactamente el mismo resultado en los tres sistemas — es una de las aproximadamente tres de cada cuatro entradas donde todos coinciden, lo que la vuelve inútil como sonda. Math.tanh(0.8), en cambio, separa a los tres sistemas operativos al mismo tiempo, con un spread de hasta 2 ULP (unit in the last place, la unidad más pequeña de diferencia representable entre dos números de punto flotante consecutivos).
¿Por qué la versión importa?
Esta filtración es reciente y tiene una fecha de nacimiento precisa. Hasta Chrome 147, el motor V8 calculaba tanh() con su propia implementación interna, un puerto de la librería fdlibm empaquetado dentro del propio navegador. Esa implementación era idéntica en cualquier sistema operativo, así que no filtraba absolutamente nada. El commit c1486295ae5 de V8 reemplazó esa lógica interna por una llamada directa a std::tanh del lenguaje C++, que a su vez delega en la librería matemática (libm) del sistema operativo anfitrión. El cambio se integró en V8 14.8.57, la versión que corresponde a Chrome 148.
Contexto e historia
Para entender por qué esto ocurre hay que volver al estándar que define cómo se representa un número decimal en una computadora: IEEE 754. El estándar especifica con exactitud cómo se almacena un número de tipo double —signo, exponente, mantisa— pero deliberadamente no exige que operaciones como sin, cos, tanh o exp devuelvan el resultado matemáticamente correcto redondeado al bit más cercano.
La razón es de rendimiento. Calcular una función trascendental con redondeo perfecto en cada llamada es costoso en ciclos de CPU. Por eso cada fabricante de sistema operativo —GNU/Linux con glibc, Apple con libsystem_m, Microsoft con la Universal C Runtime (UCRT)— implementa su propia aproximación, con sus propios coeficientes minimax, tablas de consulta precalculadas y constantes de reducción de rango. Cada una de esas implementaciones es correcta según el estándar; simplemente no todas producen el mismo bit final. El resultado es que tres librerías distintas, resolviendo el mismo problema matemático, coinciden casi siempre y difieren justo lo suficiente como para servir de firma.
💭 Clave: No toda la aritmética de Math filtra el sistema operativo. La mayoría de las funciones de V8 (exp, pow, atan, sin, cos) usan una librería matemática empaquetada dentro del propio Chrome, idéntica en cualquier plataforma. Math.tanh() es, por ahora, la única excepción — y esa asimetría es en sí misma una señal detectable: un navegador que finge ser Windows pero responde como Linux en tanh() y como Windows en todo lo demás resulta más sospechoso que uno consistente.
Un mismo cálculo, tres resultados según el sistema operativo.Datos y cifras
La divergencia entre librerías no es uniforme; depende del valor de entrada. Medido sobre Chrome 150 real en las tres plataformas:
- Math.tanh(0.5): los tres sistemas coinciden — resultado inútil como sonda.- Math.tanh(0.7): Linux difiere en solitario por 1 ULP; macOS y Windows coinciden.- Math.tanh(0.8): los tres sistemas difieren entre sí, con un spread de 2 ULP.- Math.tanh(0.9): Windows difiere en solitario por 1 ULP; Linux y macOS coinciden.
A escala, Linux y macOS discrepan en aproximadamente una de cada cuatro entradas posibles, casi siempre por 1 ULP. Windows, a través de la UCRT, discrepa de ambos en apenas unos pocos puntos porcentuales de los casos — pero cuando lo hace, alcanza para identificarlo sin ambigüedad.
El fenómeno no se limita a JavaScript. Las funciones trigonométricas de CSS —sin(), cos(), atan2() y el resto de las siete funciones trigonométricas del estándar CSS Values— corren por un camino de código distinto al de Math.sin o Math.cos: el motor de layout reduce el ángulo en grados, y solo después llama a la función std::sin de la plataforma sobre el valor ya reducido. Ese orden de operaciones produce un resultado distinto al de una llamada directa en radianes, y como también termina en la libm del sistema, las siete funciones trigonométricas de CSS filtran el mismo dato que Math.tanh().
El caso más curioso es el de macOS, donde en realidad conviven dos bibliotecas matemáticas que ni siquiera coinciden entre sí. Apple Silicon incluye la librería escalar libsystem_m y, por separado, las rutinas vectoriales del framework Accelerate (vvsin, vvtanh, entre otras). Sobre un millón de entradas de prueba, ambas divergen entre el 10% y el 89% de las veces según la función. Un ejemplo concreto: cos(0) devuelve exactamente 1.0 en la versión escalar, pero 0.9999999999999999 en Accelerate. Chrome usa la ruta escalar para Math.tanh(), la trigonometría de CSS y el compresor de audio; usa Accelerate para la FFT, el álgebra vectorial y los filtros biquad del motor de Web Audio. \"Reproducir la matemática de Apple\" es, literalmente, una pregunta mal planteada hasta que se sabe cuál de las dos bibliotecas responde en cada punto del código.
El camino que sigue una llamada a Math.tanh() dentro de V8.
graph LR
A["Math.tanh(x) en V8"] --> B{"Chrome >= 148?"}
B -- "No" --> C["fdlibm interno (idéntico en todo SO)"]
B -- "Sí" --> D["std::tanh del sistema"]
D --> E["glibc (Linux)"]
D --> F["libsystem_m (macOS)"]
D --> G["UCRT (Windows)"]
E --> H["Tabla de huellas anti-bot"]
F --> H
G --> H
Impacto y análisis
Para un sistema anti-bot, esta señal de fingerprinting matemático es casi ideal: no requiere ningún permiso especial, no dispara advertencias de privacidad, corre en un solo call de JavaScript y compara contra una tabla estática de bits conocidos. Basta con almacenar de antemano qué bits devuelve un Chrome genuino en Linux, macOS y Windows para un puñado de entradas bien elegidas, y comparar en tiempo real contra lo que responde el navegador que se está conectando. Si el User-Agent dice macOS pero los bits de Math.tanh(0.8) corresponden a Linux, hay una contradicción que delata a un navegador automatizado, una máquina virtual con spoofing de plataforma, o una granja de scraping corriendo Chrome headless sobre servidores Linux disfrazados de escritorios Mac.
Para quien construye un navegador que necesita parecerse a uno genuino en cientos de señales a la vez —el caso de Scrapfly, que opera infraestructura de scraping a escala—, la respuesta obvia (\"reimplementar las funciones de Mac\") se rompe por al menos tres motivos documentados:
- No toda la matemática filtra. Solo Math.tanh() lee la libm del sistema; el resto de Math.* usa código empaquetado dentro de V8 e idéntico en todas partes, así que tocar funciones que no debían cambiar crea una inconsistencia nueva y detectable.- JavaScript y CSS no comparten código. Falsificar Math.tanh() sin falsificar también las siete funciones trigonométricas de CSS deja un rastro: ambas rutas deberían ser consistentes entre sí en un navegador genuino.- macOS no tiene una sola \"matemática de Apple\". Cualquier intento de replicar los resultados de Mac necesita saber, función por función, si el navegador real llama a libsystem_m o a Accelerate — algo que solo se puede determinar observando un Chrome genuino en tiempo real sobre hardware real, no leyendo documentación.
⚠️ Ojo: El artículo original de Scrapfly documenta un cuarto obstáculo, ligado a que estas bibliotecas cambian entre versiones de sistema operativo y arquitectura de CPU (Intel frente a Apple Silicon). Eso vuelve cualquier tabla de huellas un objetivo móvil: lo que era válido para macOS 25 puede no serlo para macOS 26, y un spoofing \"correcto\" hoy puede quedar desactualizado con la siguiente actualización del sistema operativo.
El caso también interesa más allá del scraping. Cualquier proyecto de automatización de navegador —testing end-to-end con Playwright o Puppeteer, herramientas de accesibilidad, monitoreo sintético— corre Chrome en contenedores Linux con frecuencia, incluso cuando simula tráfico de escritorio. Si un sitio adopta detección basada en Math.tanh(), ese tráfico legítimo también puede quedar marcado como sospechoso simplemente por correr sobre la infraestructura equivocada, un efecto colateral que rara vez se discute en las coberturas sobre anti-bot.
Qué sigue
Es previsible que los proveedores de detección anti-bot amplíen sus tablas de huellas a medida que V8 continúe migrando más funciones matemáticas de implementaciones internas hacia llamadas al sistema — cada migración de ese tipo abre una nueva señal potencial. Del otro lado, los proyectos de automatización y anti-detección (variantes stealth de Playwright y Puppeteer, navegadores especializados en scraping) tendrán que decidir entre interceptar y reescribir estas llamadas al vuelo o, más costoso, ejecutar el tráfico realmente sobre el sistema operativo que dicen representar.
También cabe la posibilidad de que el propio equipo de Chromium reconsidere el cambio si la comunidad de privacidad lo señala como una regresión de fingerprinting — no sería la primera vez que V8 ajusta un comportamiento por ese motivo. Hasta entonces, cualquier desarrollador que construya o audite defensas anti-bot tiene en Math.tanh() una señal barata, difícil de neutralizar sin conocimiento profundo de bibliotecas matemáticas de bajo nivel, y disponible desde Chrome 148 en adelante.
📖 Resumen en Telegram: Ver resumen
Preguntas frecuentes
¿Qué es Math.tanh() y por qué sirve para identificar el sistema operativo?
Es la función tangente hiperbólica de JavaScript. Desde Chrome 148 no la calcula el propio motor V8, sino la librería matemática del sistema operativo (glibc en Linux, libsystem_m en macOS, UCRT en Windows), y cada una redondea los resultados de forma ligeramente distinta.
¿Desde qué versión de Chrome existe esta filtración?
Desde Chrome 148 (V8 14.8.57), cuando el commit c1486295ae5 reemplazó la implementación interna de fdlibm por una llamada a std::tanh del sistema. Chrome 147 y anteriores no filtran este dato.
¿Afecta a otros navegadores basados en Chromium, como Edge o Brave?
Cualquier navegador que use una versión de V8 igual o posterior a 14.8.57 hereda el mismo comportamiento, porque el cambio vive en el motor JavaScript compartido, no en una capa específica de Chrome.
¿Cambiar el User-Agent evita esta detección?
No. El User-Agent es un texto arbitrario que el navegador puede declarar sin restricciones, pero los bits que devuelve Math.tanh() dependen de la librería matemática real del sistema operativo que ejecuta el proceso, no de lo que el navegador dice ser.
¿Qué otras funciones filtran información parecida?
Las siete funciones trigonométricas de CSS (sin(), cos(), atan2(), entre otras) también leen la libm del sistema. El resto de Math.* en JavaScript, por ahora, usa código interno de V8 idéntico en todas las plataformas.
¿Es esto una vulnerabilidad de seguridad?
No en el sentido tradicional: no permite ejecutar código ni robar datos. Es una técnica de fingerprinting que reduce el anonimato y facilita distinguir tráfico automatizado de tráfico humano genuino.
Referencias
- Scrapfly — Your Browser Does Math Differently on Every OS — el análisis original con las mediciones sobre Chrome 150 en Linux, macOS y Windows.- MDN — Math.tanh() — documentación oficial de la función y su comportamiento en JavaScript.- Wikipedia — IEEE 754 — el estándar que define la representación de números de punto flotante y por qué no exige redondeo correcto en funciones trascendentales.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
Top comments (0)