AL.
🇺🇸 EN
Volver al blog
Seguridad · 9 min de lectura

Como Cace un Enjambre de Bots Escondido en mi Analitica de PostHog

Una historia real de deteccion de trafico de bots con PostHog: como un viewport que no cuadraba con el tamano de pantalla los delato, y como los volvi a fijar cuando parchearon esa pista usando user agent, sistema operativo, referrers sinteticos y rafagas de eventos por debajo del segundo.


Tengo un sitio que sirve cerca de un millón de páginas de información pública. No puedo contar qué es, pero el problema se entiende fácil: son muchísimas URLs, y todas valen la pena de scrapear si sos de los que se dedican a eso. Así que una semana cualquiera apareció un enjambre y se puso a recorrerlo entero.

Lo primero que noté fue la factura. El tráfico se disparó sin que yo hubiera lanzado ni promocionado nada que lo explicara. Lo segundo fue la analítica, que de un día para el otro se veía espectacular, y ese es justo el tipo de cosa que debería hacerte ruido. Subían las sesiones, subían las vistas de página, y no significaba nada, porque casi todo era una máquina leyendo páginas que ningún humano abriría en su vida.

Uso PostHog, y no tenía ganas de instalar un producto anti-bots ni de meter todo detrás de un WAF a ver qué pasaba. Lo que quería era identificar el tráfico primero, para poder medirlo, sacarlo de mis números y recién ahí decidir qué bloquear. Esta es la historia de cómo los encontré, cómo se adaptaron y cómo los volví a encontrar.

Por qué cuesta tanto cazar un bot

Lo jodido es que un crawler bien hecho parece una persona. Pide una página, trae su user agent, ejecuta JavaScript, dispara los mismos eventos que dispararía un navegador. Si te fijás en una sola propiedad, todo cierra. Que el user agent diga Chrome no prueba nada. Que el referrer diga Google, tampoco.

Lo que a mí me funcionó fue dejar de mirar propiedades sueltas y empezar a buscar combinaciones que un navegador de verdad no puede generar. Una persona sentada frente a un dispositivo real arrastra un montón de datos físicos de ese dispositivo, y esos datos tienen que ser coherentes entre sí. Cuando no lo son, ya no estás viendo un dispositivo: estás viendo algo que se hace pasar por uno.

La primera pista: un viewport más grande que la pantalla

PostHog captura solo un montón de propiedades en cada evento, y dos de ellas fueron las que resolvieron todo al principio: el tamaño del viewport y el de la pantalla.

El viewport es el área visible de la página dentro de la ventana del navegador. La pantalla es el monitor físico donde corre ese navegador. Y hay una regla que no admite discusión: el viewport no puede ser más grande que la pantalla. La ventana vive adentro del monitor. La podés achicar todo lo que quieras, agrandarla más allá de la pantalla es imposible.

Los bots reportaban un viewport de 1920x1280 sobre una pantalla de 800x600.

Eso no existe en hardware real. Nadie tiene un monitor de 800x600 mostrando una ventana que mide más del doble de ancho que la propia pantalla. Lo que pasó es que quien armó el crawler puso el viewport en un valor moderno para que las páginas se vieran bien, y se olvidó de las dimensiones de pantalla, que quedaron en el valor por defecto del navegador headless o del emulador de turno. Cada número lo configuró una parte distinta del sistema, y nadie se encargó de que cuadraran entre sí.

Pasar eso a un filtro en PostHog fue directo. Me metí en las propiedades de usuario de las sesiones y armé una cohorte con una sola condición: ancho del viewport mayor que el ancho de la pantalla. En una población normal eso no debería coincidir con casi nadie. Acá coincidía con un bloque gigante y sospechosamente prolijo de sesiones. Por fin tenía un número para ponerle al enjambre, y podía restarlo de mis métricas reales.

Se dieron cuenta, y lo corrigieron

Esta es la parte que nadie te avisa. Si tu detección sirve para algo, la gente que corre los bots va a notar que su tráfico está siendo filtrado, y se van a adaptar. Es el gato y el ratón: no ganás una vez y te vas tranquilo a tu casa.

Al poco tiempo de empezar a filtrar por el desajuste del viewport, la pista se esfumó. El tamaño de pantalla empezó a llegar con un valor normal, que encima cuadraba con el viewport. Alguien del otro lado revisó su setup, o leyó cómo los sitios estaban cazando navegadores headless, y puso unas dimensiones creíbles. La geometría imposible había desaparecido.

Mi cohorte limpia dejó de agarrarlos. Por un rato, el tráfico volvió a parecer gente.

La segunda pista: una combinación que ningún humano real armaría

Así que volví al mismo principio. Una propiedad sola no prueba nada, pero la combinación justa describe una máquina concreta, no a una multitud de personas. Dejé de buscar la bandera mágica y me puse a mirar la forma del tráfico en varias dimensiones a la vez.

Cuatro cosas encajaron:

  • Un user agent muy puntual. No “Chrome” a secas, sino una cadena exacta, hasta el número de versión, apareciendo muchísimo más de lo que cualquier reparto real de versiones daría. La gente de verdad se distribuye entre decenas de builds de navegador y sistema operativo. Esto era una sola cadena, repetida hasta el cansancio.
  • Linux como sistema operativo. Hay tráfico humano real en Linux, y no estoy diciendo que Linux sea sinónimo de bot. Pero para esta audiencia en particular la proporción de Linux estaba completamente fuera de escala, y encima venía pegada al user agent de arriba, en vez de repartirse como se reparten los usuarios de Linux de verdad.
  • Un referrer inventado. Todas estas sesiones llegaban con un referrer puesto, y siempre era la misma fuente fabricada, no la mezcla desordenada de directo, buscador y redes que ves en la gente real. Estaba ahí para parecer legítimo, y fue justo eso lo que lo delató en cuanto dejé de creerles a los referrers por sí solos.
  • Ráfagas de eventos por debajo del segundo, en ventanas de tiempo apretadísimas. Este fue el clavo final. Una persona carga una página y hace cosas de persona: se frena, hace scroll, lee, pasan segundos entre una interacción y la siguiente. Estas sesiones disparaban varios eventos dentro del mismo segundo, una y otra vez, y la sesión completa entraba en una ventana de tiempo minúscula. Ni las manos ni los ojos se mueven así. Eso es un bucle corriendo.

Cualquiera de esas por separado tiene defensa. Un user agent puntual pasa. Linux pasa. Un referrer pasa. Los eventos rápidos pasan. Pero una sesión que es las cuatro al mismo tiempo, el mismo user agent exacto, en Linux, con el mismo referrer inventado, escupiendo tormentas de eventos por debajo del segundo, ya no es la casualidad de cuatro hechos sueltos. Es un solo script con cuatro disfraces.

Armando el filtro en PostHog

Llevar eso a algo usable en PostHog fue cuestión de apilar las condiciones en una sola cohorte y dejar que el AND hiciera el trabajo. Cada condición es floja a propósito. Por sí sola, cualquiera se llevaría puesto tráfico inocente. Juntas, caen sobre el enjambre y casi nada más.

La ráfaga por debajo del segundo fue lo que más me hizo pensar, porque una propiedad de evento sola no la captura. Lo que la captura es el tiempo entre eventos dentro de una misma sesión: muchas interacciones cayendo en el mismo segundo, y una duración de sesión de un par de segundos en lugar de minutos. Mirando los eventos en la línea de tiempo de una sesión se veía cantado cuál era una persona y cuál un for-loop, y de ahí pude describir el patrón como filtro en vez de andar mirándolo a ojo.

Con la cohorte definida, todo lo que de verdad me importaba se volvió fácil. La saqué de mis dashboards y mis números reales de engagement volvieron a significar algo. Pude calcular con precisión cuánta de la carga era el enjambre, que es el número que importa cuando estás mirando la factura. Y me quedó una descripción exacta y con evidencia del tráfico para pasársela a la capa que hace el bloqueo de verdad, en lugar de un “hay unos bots dando vueltas” sin más.

Lo que te diría si estás en la misma situación

La lección que me quedó es que a los bots los cazás por coherencia, no por una señal aislada. Un visitante real es un dispositivo físico en manos de una persona, y todo en ese dispositivo tiene que sostenerse junto: el viewport entra dentro de la pantalla, el reparto de navegadores parece una población, los referrers son un desorden y los tiempos tienen los huecos de un humano. Un bot es una configuración, y las configuraciones tienen costuras. Tu trabajo es encontrar dos datos que no pueden ser verdad al mismo tiempo.

La otra lección es que esto no se arregla una sola vez. El truco del viewport funcionó hasta que dejó de funcionar. Cuando lo parchearon, la solución no fue un truco mejor, fueron más dimensiones apiladas, porque cada pista que sumás es una costura más que ellos tienen que coser bien, y coserlas todas a la vez es muchísimo más difícil que corregir un valor de pantalla por defecto.

Si tenés algo lo bastante grande como para que valga la pena scrapearlo, PostHog ya trae casi todo lo que necesitás para ver esto. No hace falta un producto sofisticado para arrancar. Hace falta dejar de creerles a las propiedades de a una y empezar a preguntarte si de verdad pueden ser todas ciertas al mismo tiempo.

Si esto te ahorró una tarde de mirar la analítica sin entender nada, pasáselo a alguien que esté viendo su tráfico subir sin ninguna razón que lo explique.