Implementación de Telemetría Biométrica Pasiva y Entropía Kinestésica para Detección Anti-Bot Zero Trust en Frontend

JAVASCRIPT / FRONTEND 13 de agosto de 2026 122 lecturas
Un motor de telemetría pasiva para navegadores que analiza patrones kinestésicos (velocidad, aceleración y curvatura angular) y genera firmas de entropía firmadas criptográficamente para neutralizar bots headless y automatización L7.

El Colapso de los CAPTCHAs Convencionales y la Necesidad de Biometría Pasiva

En el ecosistema moderno del desarrollo web y la ciberseguridad, los sistemas tradicionales de verificación pasiva y activa —como los desafíos visuales reCAPTCHA, hCaptcha o los tradicionales acertijos de texto deformado— han quedado completamente desfasados. Las granjas de automatización avanzadas y los marcos de prueba headless como Playwright, Puppeteer y Selenium con parches de sigilo (stealth plugins) han integrado modelos de visión por computadora y grandes modelos de lenguaje (LLMs) capaces de resolver estos desafíos con un porcentaje de éxito superior al 95% y en tiempos de respuesta inferiores a los 500 milisegundos.

Esta degradación del modelo de defensa tradicional obliga a las arquitecturas web a adoptar principios rigurosos de Zero Trust (Confianza Cero). Bajo este paradigma, ningún usuario o petición es considerado legítimo por el simple hecho de navegar desde una dirección IP residencial o resolver un desafío aislado. La frontera defensiva debe desplazarse hacia una verificación continua y no intrusiva basada en el análisis del comportamiento biomecánico pasivo del usuario durante la totalidad de su interacción con la interfaz de usuario.

Kinestesia Humana vs. Trayectorias Sintéticas de Automatización

La interacción humana con periféricos físicos —como ratones, trackpads o pantallas táctiles— está gobernada por principios de la biomecánica motora. Cuando un ser humano desplaza el puntero hacia un elemento interactivo, el cerebro ejecuta impulsos neuromusculares que resultan en variaciones constantes de velocidad, aceleración no uniforme (denominada en física como jerk o tirón), micro-temblores causados por la biomecánica articular y correcciones continuas de trayectoria angular a medida que el cursor se aproxima a su objetivo.

Por el contrario, los scripts de automatización sintética, incluso aquellos programados con curvas de Bézier aleatorizadas o suavizado cinemático, exhiben un comportamiento matemáticamente identificable. Tienden a mantener aceleraciones uniformes, transiciones de velocidad limpias y trayectorias angulares carentes de las variaciones estocásticas inherentes a los músculos humanos. Un bot procesa coordenadas de origen a destino con funciones cuadráticas o cúbicas perfectas que reducen drásticamente la entropía general del movimiento.

Arquitectura del Motor de Telemetría e Indicadores Biométricos

Para construir una solución de telemetría de alta precisión sin penalizar el rendimiento del hilo principal de renderizado (JavaScript Main Thread), he diseñado un motor basado en un buffer circular en memoria que captura eventos pasivos a una tasa controlada de aproximadamente 60 cuadros por segundo (16.6ms por muestra). El motor evalúa de forma continua tres dimensiones kinestésicas principales:

  • Varianza de Velocidad Scaled: Mide la relación entre la distancia euclidiana recorrida y el tiempo transcurrido entre muestras de movimiento.
  • Tasa de Cambio de Aceleración (Jerk Analytics): Calcula las derivadas secundarias de la posición para detectar la presencia de temblores y ajustes finos de fuerza neuromuscular.
  • Entropía y Jitter Angular: Analiza las fluctuaciones en el ángulo vector de dirección entre muestras consecutivas, cuantificando los giros microscópicos característicos de un sistema biomecánico vivo.

Desafíos de Implementación: Rendimiento, Criptografía y Zero Trust

Implementar un recolector de telemetría pasiva en el lado del cliente presenta tres retos clave de ingeniería de software:

Primero, el impacto en el Event Loop y la tasa de refresco. Interceptores de eventos como mousemove y touchmove pueden dispararse cientos de veces por segundo en pantallas de alta frecuencia (120Hz o 144Hz). Para evitar la saturación de memoria y pausas por Garbage Collection, he utilizado arreglos tipados estáticos (Float32Array) organizados en estructuras circulares de tamaño fijo (por ejemplo, 60 muestras). Esto garantiza una asignación de memoria previa a la ejecución y cero creaciones dinámicas de objetos durante la navegación del usuario.

Segundo, el manejo de datos iniciales y bordes. Un fallo habitual en algoritmos ingenuos de telemetría es inicializar las coordenadas previas en (0,0). Cuando el usuario mueve el ratón por primera vez desde la esquina de la pantalla, se genera una distancia y velocidad artificialmente gigantesca que corrompe la varianza acumulada. El motor implementa una fase de hidratación inicial que descarta el primer evento para establecer un punto de referencia válido antes del registro dinámico.

Tercero, la integridad criptográfica y prevención de falsificaciones. Si el frontend genera un simple JSON con una puntuación en texto plano o codificado en Base64 tradicional (usando btoa), un ingeniero de ingeniería inversa podría inspeccionar el código, calcular una puntuación falsa de 1.0 y enviar la petición directamente al servidor mediante un script de Curl o Python. Para resolver esto, la arquitectura utiliza la API nativa Web Crypto API (crypto.subtle) para firmar criptográficamente el payload con una clave simétrica compartida mediante un hash HMAC-SHA256, garantizando la inmutabilidad y autenticidad de los datos de telemetría antes de llegar al API Gateway.

Casos de Uso e Integración en Endpoints Críticos

Este sistema está optimizado para actuar como un middleware pasivo en puntos de alta sensibilidad dentro de aplicaciones SaaS y plataformas de comercio electrónico:

  • Protección de Formulario de Registro y Login: Recopilación de la firma biomecánica mientras el usuario desplaza el cursor e interactúa con las cajas de texto, denegando el token de autenticación si la entropía recopilada indica una inyección headless.
  • Mitigación de Credential Stuffing: Detección automatizada de intentos masivos de inicio de sesión donde la velocidad de llenado de campos supera los límites kinestésicos reales.
  • Validación de Transacciones Financieras: Adjuntar el token firmado HMAC en los encabezados HTTP (ej. X-Biometric-Entropy-Token) en operaciones de checkout o transferencias electrónicas, permitiendo al backend denegar la transacción o exigir una verificación multifactor (MFA) step-up si la puntuación cae por debajo del umbral de confianza (ejemplo: score < 0.35).
/**
 * Zero Trust Biometric Entropy Shield v2.0
 * Engine de telemetría kinestésica no intrusiva con firma criptográfica HMAC-SHA256.
 */
class BiometricEntropyShield {
    #sampleSize;
    #telemetryBuffer;
    #bufferIndex = 0;
    #isRecording = false;
    #lastX = null;
    #lastY = null;
    #lastTime = null;
    #lastAngle = null;
    #secretKey = null;
    #cryptoKey = null;
    #boundTrackMovement;

    /**
     * @param {Object} config
     * @param {number} [config.sampleSize=60] Tasa de muestreo en buffer circular.
     * @param {string} [config.secretKey] Clave simétrica para firma HMAC de payload.
     */
    constructor(config = {}) {
        this.#sampleSize = config.sampleSize || 60;
        this.#secretKey = config.secretKey || 'default-zero-trust-secret-key';
        
        // Estructura de datos flat optimizada para performance
        this.#telemetryBuffer = {
            velocity: new Float32Array(this.#sampleSize),
            acceleration: new Float32Array(this.#sampleSize),
            angularJitter: new Float32Array(this.#sampleSize)
        };

        this.#boundTrackMovement = this.#trackMovement.bind(this);
        this.#initCryptoKey();
        this.#bindEvents();
    }

    async #initCryptoKey() {
        if (!window.crypto || !window.crypto.subtle) return;
        const encoder = new TextEncoder();
        const keyData = encoder.encode(this.#secretKey);
        this.#cryptoKey = await window.crypto.subtle.importKey(
            'raw',
            keyData,
            { name: 'HMAC', hash: 'SHA-256' },
            false,
            ['sign']
        );
    }

    #bindEvents() {
        const options = { passive: true };
        window.addEventListener('mousemove', this.#boundTrackMovement, options);
        window.addEventListener('touchmove', this.#boundTrackMovement, options);
    }

    #trackMovement(event) {
        if (!this.#isRecording) return;

        const timeNow = performance.now();
        if (this.#lastTime === null) {
            const touch = event.touches ? event.touches[0] : null;
            this.#lastX = touch ? touch.clientX : event.clientX;
            this.#lastY = touch ? touch.clientY : event.clientY;
            this.#lastTime = timeNow;
            return;
        }

        const deltaTime = timeNow - this.#lastTime;
        // Limitación a frecuencia de renderizado (~60Hz / 16ms)
        if (deltaTime < 15) return;

        const touch = event.touches ? event.touches[0] : null;
        const clientX = touch ? touch.clientX : event.clientX;
        const clientY = touch ? touch.clientY : event.clientY;

        const deltaX = clientX - this.#lastX;
        const deltaY = clientY - this.#lastY;
        const distance = Math.hypot(deltaX, deltaY);
        
        // Ignorar micromovimientos estáticos insignificantes
        if (distance < 0.5) return;

        const velocity = distance / deltaTime;
        const currentAngle = Math.atan2(deltaY, deltaX);

        // Cálculo de métricas derivadas
        const prevVelocity = this.#telemetryBuffer.velocity[this.#bufferIndex % this.#sampleSize] || 0;
        const acceleration = (velocity - prevVelocity) / deltaTime;

        let angularJitter = 0;
        if (this.#lastAngle !== null) {
            let angleDiff = Math.abs(currentAngle - this.#lastAngle);
            if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff;
            angularJitter = angleDiff;
        }

        // Actualización de buffer circular
        const index = this.#bufferIndex % this.#sampleSize;
        this.#telemetryBuffer.velocity[index] = velocity;
        this.#telemetryBuffer.acceleration[index] = acceleration;
        this.#telemetryBuffer.angularJitter[index] = angularJitter;

        this.#bufferIndex++;
        this.#lastX = clientX;
        this.#lastY = clientY;
        this.#lastTime = timeNow;
        this.#lastAngle = currentAngle;
    }

    start() {
        this.#bufferIndex = 0;
        this.#lastX = null;
        this.#lastY = null;
        this.#lastTime = null;
        this.#lastAngle = null;
        this.#telemetryBuffer.velocity.fill(0);
        this.#telemetryBuffer.acceleration.fill(0);
        this.#telemetryBuffer.angularJitter.fill(0);
        this.#isRecording = true;
    }

    stop() {
        this.#isRecording = false;
    }

    destroy() {
        this.stop();
        window.removeEventListener('mousemove', this.#boundTrackMovement);
        window.removeEventListener('touchmove', this.#boundTrackMovement);
    }

    /**
     * Evalúa la variabilidad multivariable de los datos kinestésicos.
     * @private
     */
    #calculateVariance(array, validSamples) {
        if (validSamples === 0) return 0;
        let sum = 0;
        for (let i = 0; i < validSamples; i++) sum += array[i];
        const mean = sum / validSamples;

        let varianceSum = 0;
        for (let i = 0; i < validSamples; i++) {
            varianceSum += Math.pow(array[i] - mean, 2);
        }
        return varianceSum / validSamples;
    }

    /**
     * Calcula la puntuación global de humanidad basada en la combinación
     * de varianza kinestésica y entropía angular.
     * @returns {number} Puntuación de 0.0 (Bot estricto) a 1.0 (Humano confirmado)
     */
    generateHumanityScore() {
        const validSamples = Math.min(this.#bufferIndex, this.#sampleSize);
        if (validSamples < 12) return 0.0;

        const velVariance = this.#calculateVariance(this.#telemetryBuffer.velocity, validSamples);
        const accVariance = this.#calculateVariance(this.#telemetryBuffer.acceleration, validSamples);
        const jitterVariance = this.#calculateVariance(this.#telemetryBuffer.angularJitter, validSamples);

        // Ponderación de factores kinestésicos
        const velocityWeight = Math.min(velVariance * 80, 0.4);
        const accelerationWeight = Math.min(accVariance * 1200, 0.3);
        const angularWeight = Math.min(jitterVariance * 2.5, 0.3);

        const score = velocityWeight + accelerationWeight + angularWeight;
        return Number(Math.min(Math.max(score, 0.0), 1.0).toFixed(4));
    }

    /**
     * Construye un payload firmado criptográficamente con HMAC-SHA256
     * para consumo seguro en el API Gateway / Backend.
     * @returns {Promise<string>} Base64URL del JSON concatenado con su firma HMAC.
     */
    async generateSignedPayload() {
        const score = this.generateHumanityScore();
        const validSamples = Math.min(this.#bufferIndex, this.#sampleSize);
        
        const payloadData = {
            score,
            samples: validSamples,
            timestamp: Date.now(),
            nonce: Math.random().toString(36).substring(2, 10)
        };

        const jsonString = JSON.stringify(payloadData);
        
        if (!this.#cryptoKey || !window.crypto || !window.crypto.subtle) {
            return btoa(jsonString);
        }

        const encoder = new TextEncoder();
        const dataBuffer = encoder.encode(jsonString);
        const signatureBuffer = await window.crypto.subtle.sign('HMAC', this.#cryptoKey, dataBuffer);

        const signatureArray = Array.from(new Uint8Array(signatureBuffer));
        const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');

        return btoa(JSON.stringify({
            data: payloadData,
            signature: signatureHex
        }));
    }
}
¿Qué te pareció?
🔥 Brillante 0
💡 Me sirvió 0
🚀 A otro nivel 0

¿Te resultó útil este snippet? Explora más código y soluciones en AndresSY.dev.

Volver a Snippets