El sistema de detección de bots de TRACIO identifica navegadores automatizados, herramientas headless y ataques con scripts usando una arquitectura de dos capas: recopilación de señales en el cliente y análisis en el servidor. Detecta los frameworks de automatización más habituales, los navegadores headless, los agentes de navegación con IA y los navegadores anti-detección con falsos positivos casi nulos, a la vez que incluye automáticamente en la allowlist a los rastreadores legítimos de motores de búsqueda.
El agente del navegador recopila una serie de señales específicas de bots y de validación cruzada que alimentan las decisiones del servidor. Los IDs de señal de abajo son ilustrativos: los IDs internos no son un contrato público estable:
| Señal | Descripción |
|---|---|
s300 (webdriver) | Flag navigator.webdriver — establecido a true por los frameworks de automatización |
s301 (eval length) | eval.toString().length — Chrome/Edge: 33, Firefox: 37. Otros valores indican instrumentación. |
s305 (bot composite) | Escanea window/document en busca de 14 artefactos de frameworks de automatización |
s97 (headless markers) | Artefactos de CDP, permisos de notificación, número de plugins, dimensiones exteriores |
s157 (automation scan) | Escaneo exhaustivo de Selenium, Puppeteer, PhantomJS, Playwright y 10 más |
s158 (iframe webdriver) | Crea un iframe oculto y lee navigator.webdriver dentro de él. Atrapa herramientas que parchean el frame principal pero olvidan los iframes. |
s159 (native function) | Elimina Function y Object, prueba si los getters son realmente nativos. Detecta instrumentación basada en Proxy. |
s155 (window CRC32) | Escaneo de hash CRC32 de cada propiedad de window para detectar globales de automatización inyectadas |
s163 (devtools trap) | Trampa de getter de consola — detecta cuándo las DevTools están abiertas activamente |
El servidor ejecuta un conjunto de detectores cuyas salidas ponderadas se combinan en una
puntuación de bot. La puntuación se asigna a un veredicto mediante umbrales —bot cuando es ≥ 0.70,
suspicious cuando es ≥ 0.30, y human en el resto de casos— y una señal de fallo forzado (como
navigator.webdriver) fuerza directamente un veredicto bot. Los veredictos de bot llevan una
etiqueta bot_type de forma libre.
Detectores representativos y su efecto:
| Detector | Efecto |
|---|---|
| Flag de webdriver | Fallo forzado → bot (tipo webdriver) |
| Escaneo compuesto de automatización / framework | Peso alto → bot (tipo {framework}) |
| Marcadores headless | Ponderado → bot (tipo headless) |
| Anomalía de eval-length | Ponderado → bot (tipo unknown) |
| Patrón de UA de bot malicioso | Ponderado → bot (tipo {label}) |
| Bot bueno verificado (DNS inverso) | Allowlist → human |
| Comportamental (tasa / entropía / interacción) | Ponderado → bot (tipo rateBot) |
| VM (Enterprise) · Emulador · DevTools (Business+) | Señales ponderadas de dispositivo/manipulación |
Nivel y disponibilidad. La detección de DevTools se ejecuta en Business y Enterprise; la detección de VM es de Enterprise; la detección de emuladores se ejecuta en todos los planes. Todas funcionan en el agente web. La detección de root, jailbreak, apps clonadas/duales, Frida y restablecimiento de fábrica depende de señales de móvil nativo y no es efectiva en el despliegue solo web (en desarrollo). Apóyese en los veredictos de automatización, headless, agente de IA, anti-detección, VM/emulador y comportamentales, que están totalmente activos en web.
La detección de bots escribe un campo bot_result en cada evento (y bot.result en
el payload del webhook).
bot_result es uno de tres valores canónicos:
| Resultado | Descripción |
|---|---|
human | No se encontraron indicadores de automatización. Los rastreadores de motores de búsqueda verificados también se resuelven a human. |
bot | Se detectó acceso automatizado o con scripts. Le acompaña una etiqueta bot_type de forma libre. |
uncertain | Señales mixtas o débiles — ni claramente humano ni claramente automatizado. |
Esto se asigna al campo de negocio decision (real, fake o suspicious)
usado en todo el panel, y a bot.result en el
payload del webhook.
"bot")bot_type es una cadena de forma libre. Los valores de abajo son ejemplos ilustrativos de
lo que emiten los detectores, no un enum exhaustivo:
| Tipo | Método de detección |
|---|---|
webdriver | navigator.webdriver es true |
selenium | Propiedades window/document específicas de Selenium detectadas |
puppeteer | Indicadores headless de Puppeteer/Chromium coincidentes |
playwright | Marcadores específicos de Playwright o cadena de UA detectados |
headless | Se activaron 2+ de 11 marcadores headless |
phantomjs | Globales de PhantomJS detectados (callPhantom, _phantom) |
nightmare | Globales de NightmareJS detectados |
unknown | Anomalía de eval length u otros indicadores de automatización no específicos |
rateBot | El análisis comportamental de tasa superó los umbrales |
La detección más directa. Todos los frameworks de automatización principales establecen navigator.webdriver = true según la especificación de WebDriver. Esto se comprueba tanto en el frame principal (s300) como en un iframe oculto (s158) para atrapar herramientas que parchean solo el frame principal.
Escaneo exhaustivo de las propiedades de window y document en busca de artefactos específicos de cada framework:
| Framework | Propiedades detectadas |
|---|---|
| Selenium | _Selenium_IDE_Recorder, _selenium, calledSelenium, __selenium_evaluate, $cdc_* (ChromeDriver) |
| Puppeteer | domAutomation, domAutomationController, UA de HeadlessChrome |
| Playwright | __playwright, flag automation-controlled |
| PhantomJS | callPhantom, _phantom |
| NightmareJS | __nightmare, nightmare |
| CefSharp | CefSharp |
| Awesomium | awesomium |
| WebDriverIO | wdioElectron |
| Cypress | Propiedad de window __cypress |
El escaneo también usa un patrón regex /^([a-z]){3}_.*_(Array|Promise|Symbol)$/ para detectar las globales auxiliares inyectadas por Selenium.
Se evalúan 11 marcadores, con un umbral de 2 o más para la clasificación:
| Marcador | Señal | Condición |
|---|---|---|
| webdriver | s300 | = "true" |
| variantes de vendor ausentes | s28 | Sin objetos chrome/safari en window |
| CDP headless chrome | s305 | headlessChrome = true |
| notificación denegada | s88 | Notification.permission = "denied" en la primera visita |
| chrome sin runtime | s89 | window.chrome existe pero falta chrome.runtime |
| idioma único | s90 | navigator.languages.length <= 1 |
| pantalla por defecto 800x600 | s5 | Resolución por defecto de Chrome headless |
| UA de HeadlessChrome | UA | El User-Agent contiene "HeadlessChrome" |
| dimensiones exteriores a cero | s97 | outerWidth = 0 y outerHeight = 0 |
| WebGL SwiftShader | s70 | El renderizador contiene "swiftshader" (GPU por software) |
| WebGL llvmpipe | s70 | El renderizador contiene "llvmpipe" (GPU por software) |
Los bots buenos conocidos se reconocen por el patrón de User-Agent. Cada uno se verifica mediante DNS inverso para prevenir la suplantación:
.googlebot.com)| Bot | Dominios permitidos |
|---|---|
.googlebot.com, .google.com | |
| Bing | .search.msn.com |
| Apple | .applebot.apple.com |
| Yahoo | .crawl.yahoo.net |
| Yandex | .yandex.com, .yandex.net, .yandex.ru |
| DuckDuckGo | .duckduckgo.com |
Si una solicitud afirma ser Googlebot pero el DNS inverso de su IP se resuelve a un dominio ajeno (una discrepancia de PTR confirmada, no solo una búsqueda ausente o con timeout), se clasifica como bot (tipo spoofed:google).
Detección basada en tasa con puntuación de entropía:
| Tasa (sol./min) | Umbral de entropía | Acción |
|---|---|---|
| 120+ | < 0.1 | bot (tipo: rateBot) |
| 60-120 | < 0.3 | bot (tipo: rateBot) |
| 45-60 | < 0.1 | bot (tipo: rateBot) |
| < 45 | Cualquiera | Normal |
Regla adicional: la ausencia de eventos de ratón/teclado después de 30+ segundos de antigüedad de la sesión clasifica la visita como bot con tipo noInteraction.
eval.toString().length produce valores consistentes por motor:
Cualquier otro valor indica que la función eval ha sido interceptada por un framework de automatización, clasificando la visita como bot con tipo unknown.
El SDK del cliente expone un booleano de conveniencia, result.bot.detected:
const result = await tracio.getResult()
if (result.bot.detected) { // Log and block console.warn(`Bot detected (confidence ${result.bot.confidence})`) showCaptcha() return}
// Proceed with loginawait login(credentials)En su servidor, actúe sobre la entrega del webhook. La detección de bots
vive en el objeto bot — bot.result y bot.type:
// `event` is the webhook delivery body (/docs/webhooks)app.post("/webhook/tracio", async (req, res) => { const event = req.body
if (event.bot.result === "bot") { await db.blockedRequests.insert({ visitorId: event.visitorId, botType: event.bot.type, ip: event.ip, timestamp: new Date(), }) }
res.status(200).send("OK")})La detección de bots de TRACIO está diseñada para falsos positivos casi nulos. Sin embargo, pueden darse casos límite:
| Escenario | Riesgo | Mitigación |
|---|---|---|
Extensiones de navegador que modifican navigator | Bajo | La validación cruzada de múltiples señales evita activaciones por una sola señal |
| Software de seguridad corporativo | Muy bajo | La detección headless requiere 2+ marcadores |
| Herramientas de accesibilidad | Ninguno | Las APIs de accesibilidad no activan ninguna señal de detección |
| Usuarios de VPN/proxy | Ninguno | El uso de VPN se rastrea por separado de la detección de bots |
Si se encuentra con falsos positivos, revise el campo bot.type para entender qué detector se activó y ajuste su política en consecuencia.