Huella digital

Fingerprinting de sintesis de voz: rastreo por voces

Cómo la lista de voces de la API SpeechSynthesis revela tu sistema operativo y plataforma, y técnicas para controlar las señales de huella digital basadas en voz.

Documentación

Prefieres la documentación del producto mantenida?

Este artículo tiene una página equivalente en el centro de documentación. Usa los docs para el flujo canónico, las flags actuales y la referencia duradera.

Introducción

La interfaz SpeechSynthesis de la Web Speech API fue diseñada para dar a las aplicaciones web capacidades de texto a voz. Permite a los desarrolladores convertir texto en audio hablado, habilitando funciones de accesibilidad, herramientas de aprendizaje de idiomas y experiencias interactivas de voz. El método speechSynthesis.getVoices() devuelve una lista de voces disponibles, cada una con propiedades como nombre, idioma y si se ejecuta localmente o a través de un servicio remoto.

Si bien la API cumple un propósito claro de accesibilidad, la lista de voces que expone varía significativamente entre sistemas operativos, versiones de navegador y paquetes de idiomas instalados. Un sistema Windows 11 podría reportar más de 40 voces incluyendo Microsoft David, Zira y varias voces de Cortana. Un sistema macOS reporta voces completamente diferentes como Alex, Samantha y una variedad de voces basadas en Siri. Los sistemas Linux usando speech-dispatcher reportan otro conjunto más. Esta variación específica de plataforma hace que la lista de voces sea una señal confiable para identificar el sistema operativo y la configuración subyacente.

Impacto en la privacidad

La lista de voces de SpeechSynthesis es un vector de huella digital particularmente efectivo porque combina alta entropía con baja conciencia. La mayoría de los usuarios no saben que los sitios web pueden enumerar sus voces de texto a voz instaladas, y no hay ninguna solicitud de permisos ni notificación cuando esto sucede.

La preocupación por la privacidad va más allá de la simple identificación del sistema operativo. Las listas de voces varían no solo por sistema operativo sino también por:

  • Versión del SO: Windows 10 y Windows 11 incluyen diferentes conjuntos de voces predeterminadas. macOS Ventura y macOS Sonoma incluyen diferentes voces de Siri.
  • Paquetes de idiomas: Los usuarios que instalan paquetes de idiomas adicionales obtienen nuevas voces, creando una huella digital más distintiva.
  • Software TTS de terceros: Aplicaciones como Balabolka, NaturalReader o lectores de pantalla NVDA pueden añadir voces al sistema, diferenciando aún más el dispositivo.
  • Versión del navegador: Chrome, Firefox y Edge exponen diferentes subconjuntos de las voces disponibles del sistema.

Un estudio de 2021 de investigadores de la Universidad de Iowa demostró que las listas de voces de síntesis de voz, combinadas con otras señales del navegador, podían aumentar la unicidad de la huella digital entre un 12-18% comparado con la huella digital sin datos de voz. La lista de voces es especialmente valiosa porque revela información sobre el sistema operativo que de otra manera es difícil de obtener después de los esfuerzos de reducción del User-Agent.

El evento onvoiceschanged añade otra dimensión: al observar cuándo y cómo se carga la lista de voces, los rastreadores pueden inferir información sobre la secuencia de inicialización interna del navegador, que varía entre plataformas.

Contexto técnico

Cómo se generan las listas de voces

Cuando un navegador se inicia, consulta el subsistema de texto a voz del sistema operativo para obtener las voces disponibles. En Windows, esto significa la Speech API (SAPI) y la plataforma de voz OneCore más moderna. En macOS, el navegador consulta el framework NSSpeechSynthesizer. En Linux, típicamente usa speech-dispatcher o consulta directamente motores instalados como eSpeak, Festival o Piper.

Cada objeto de voz devuelto por speechSynthesis.getVoices() tiene varias propiedades:

  • name: El nombre de visualización de la voz (p.ej., "Microsoft David - English (United States)")
  • lang: La etiqueta de idioma BCP 47 (p.ej., "en-US")
  • localService: Si la voz se ejecuta localmente (true) o requiere conexión a internet (false)
  • voiceURI: Un URI que identifica la voz
  • default: Si esta es la voz predeterminada para su idioma

Firmas de voz específicas de plataforma

La lista de voces actúa como una firma de plataforma. Una instalación estándar de Windows 11 reporta voces con nombres que comienzan con "Microsoft" e incluye variantes específicas de la plataforma. macOS reporta voces con nombres específicos de Apple e incluye voces de Siri en versiones recientes. Chrome en Android reporta un conjunto completamente diferente, a menudo incluyendo voces con la marca Google.

Esto crea una matriz de identificadores: el número de voces, sus nombres exactos, su cobertura de idiomas y la división local/remoto contribuyen a una huella digital de plataforma. Incluso el orden en que las voces aparecen en el array puede diferir entre plataformas.

Comportamiento de carga asíncrona

La carga de voces es asíncrona en la mayoría de los navegadores. La llamada inicial a getVoices() puede devolver un array vacío, con la lista completa disponible después de que se dispare el evento voiceschanged. La temporización de este evento, y si getVoices() devuelve una lista vacía inicialmente, varía entre navegadores y plataformas. Este comportamiento de carga es en sí mismo una señal de huella digital.

Voces de red

Algunos navegadores incluyen voces basadas en red que requieren conexión a internet. La disponibilidad de estas voces depende del navegador, el estado de la cuenta de Google del usuario (para Chrome) y la conectividad de red. La presencia o ausencia de voces de red añade otra capa a la huella digital.

Enfoques comunes de protección y sus limitaciones

VPNs y servidores proxy

Las VPNs cambian la dirección IP pero no tienen efecto en la lista de voces de síntesis de voz. Los datos de voz provienen del sistema operativo local, no de la red. Dos dispositivos detrás de la misma VPN reportan listas de voces completamente diferentes según sus respectivas configuraciones de SO e idioma.

Modo incógnito y navegación privada

Los modos de navegación privada no alteran la lista de voces. Las mismas voces están disponibles en modo incógnito que en una ventana normal, porque la lista de voces se lee del sistema operativo, no del almacenamiento del navegador.

Extensiones de navegador

Las extensiones que modifican speechSynthesis.getVoices() enfrentan varios desafíos:

  • Suplantación del valor de retorno: Una extensión puede anular getVoices() para devolver una lista de voces personalizada, pero las voces que reporta deben ser utilizables. Si un sitio web intenta usar una voz reportada y falla, la inconsistencia es evidente.
  • Temporización de eventos: El comportamiento del evento voiceschanged es difícil de controlar desde una extensión. La temporización del evento, cuántas veces se dispara y el comportamiento inicial de array vacío son señales específicas de plataforma que las extensiones tienen dificultades para replicar con precisión.
  • Descriptores de propiedades: Anular getVoices() mediante JavaScript inyectado por extensiones cambia los descriptores de propiedades y la cadena de prototipos de la función, lo cual puede ser detectado.

Bloquear la API

Deshabilitar speechSynthesis por completo es detectable: un sitio web puede verificar si la API existe y si devuelve resultados. Un navegador que reporta speechSynthesis como disponible pero no devuelve voces es en sí mismo una señal distintiva.

El enfoque a nivel de motor de BotBrowser

BotBrowser controla las listas de voces de síntesis de voz a nivel del motor del navegador. Cuando se carga un perfil de huella digital, la lista de voces se configura para coincidir con la plataforma objetivo del perfil antes de que se ejecute cualquier código de página.

Listas de voces controladas por perfil

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

Cuando se carga un perfil que representa un sistema Windows 11, speechSynthesis.getVoices() devuelve la lista exacta de voces esperada en esa plataforma, incluyendo nombres correctos, idiomas, flags localService y orden. Esto es así independientemente del sistema operativo host real.

Consistencia multiplataforma

Aquí es donde el enfoque a nivel de motor de BotBrowser proporciona el mayor valor. Ejecutar un perfil de Windows en un servidor Linux normalmente expondría voces nativas de Linux (eSpeak, Festival), revelando inmediatamente que el navegador no se está ejecutando en la plataforma reportada. BotBrowser reemplaza la lista de voces con las voces esperadas del perfil, manteniendo la consistencia de plataforma en todas las superficies de huella digital.

La lista de voces se alinea con otras señales de plataforma:

  • navigator.platform coincide con el SO del perfil
  • La cadena User-Agent reporta la plataforma correcta
  • Las listas de fuentes coinciden con el SO objetivo
  • Otras APIs dependientes del SO reportan valores consistentes
  • Las voces de síntesis de voz coinciden con todo lo anterior

Comportamiento realista de voces

BotBrowser no solo devuelve una lista estática. El comportamiento de carga de voces, incluyendo la temporización asíncrona del evento voiceschanged y el patrón de retorno inicial de getVoices(), coincide con el comportamiento esperado para el navegador y plataforma objetivo del perfil. Esto asegura que tanto los datos como el comportamiento de carga sean consistentes.

Fidelidad de objetos de voz

Cada objeto de voz en la lista devuelta tiene propiedades precisas: formato de name correcto, etiquetas lang apropiadas, valores localService adecuados y cadenas voiceURI realistas. Los datos del perfil se capturan de dispositivos reales, asegurando que cada propiedad coincida con lo que reportaría una instalación genuina.

Configuración y uso

Uso básico por CLI

La protección de la lista de voces es automática al cargar un perfil:

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

No se necesitan flags adicionales. El perfil contiene la lista completa de voces para la plataforma objetivo.

Integración con Playwright

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
    ],
    headless: true,
  });

  const context = await browser.newContext({ viewport: null });
  const page = await context.newPage();

  const voices = await page.evaluate(() => {
    return new Promise(resolve => {
      const v = speechSynthesis.getVoices();
      if (v.length > 0) return resolve(v.map(voice => ({
        name: voice.name, lang: voice.lang, local: voice.localService,
      })));
      speechSynthesis.onvoiceschanged = () => {
        resolve(speechSynthesis.getVoices().map(voice => ({
          name: voice.name, lang: voice.lang, local: voice.localService,
        })));
      };
    });
  });

  console.log(`Voice count: ${voices.length}`);
  console.log('Voices:', JSON.stringify(voices, null, 2));
  await browser.close();
})();

Integración con Puppeteer

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
    ],
    headless: true,
    defaultViewport: null,
  });

  const page = await browser.newPage();
  await page.goto('about:blank');

  const voiceCount = await page.evaluate(() => {
    return new Promise(resolve => {
      const check = () => {
        const voices = speechSynthesis.getVoices();
        if (voices.length > 0) resolve(voices.length);
        else speechSynthesis.onvoiceschanged = () =>
          resolve(speechSynthesis.getVoices().length);
      };
      check();
    });
  });

  console.log('Voices available:', voiceCount);
  await browser.close();
})();

Verificación

Después de lanzar BotBrowser con un perfil, verifica la lista de voces:

function getVoices() {
  return new Promise((resolve) => {
    const voices = speechSynthesis.getVoices();
    if (voices.length > 0) return resolve(voices);
    speechSynthesis.onvoiceschanged = () =>
      resolve(speechSynthesis.getVoices());
  });
}

const voices = await getVoices();
console.log(`Voice count: ${voices.length}`);
voices.forEach(v =>
  console.log(`${v.name} (${v.lang}) local: ${v.localService}`)
);

Qué verificar:

  1. El recuento de voces coincide con el esperado para la plataforma objetivo del perfil
  2. Los nombres de las voces usan la convención de nomenclatura correcta de la plataforma (p.ej., prefijo "Microsoft" para Windows)
  3. Las etiquetas de idioma son apropiadas para la configuración de locale objetivo
  4. La propiedad localService es consistente con los tipos de voz esperados de la plataforma
  5. La lista de voces no contiene voces del sistema operativo host
  6. Las herramientas de prueba de huella digital no muestran inconsistencias entre datos de voz y otros indicadores de plataforma

Mejores prácticas

  1. Siempre usa un perfil completo. La protección de la lista de voces depende de que el perfil proporcione datos de voz precisos para la plataforma objetivo. Las configuraciones parciales o personalizadas pueden producir listas de voces incompletas.

  2. Verifica la consistencia multiplataforma. Al ejecutar perfiles en un SO diferente al objetivo, verifica que la lista de voces coincida con la plataforma objetivo, no con el host. Esta es la fuente más común de filtraciones de huella digital de voz.

  3. Considera la alineación de locale. Un perfil configurado para un locale japonés debería incluir voces japonesas. Los perfiles de BotBrowser capturados de dispositivos reales incluyen las voces apropiadas específicas del locale.

  4. No instales extensiones TTS junto con BotBrowser. Las extensiones de TTS de terceros para el navegador pueden registrar voces adicionales que entren en conflicto con la lista de voces controlada del perfil.

Preguntas frecuentes

¿Todos los navegadores exponen la misma lista de voces?

No. Chrome, Firefox, Edge y Safari exponen diferentes subconjuntos de las voces disponibles del sistema operativo. Los perfiles de BotBrowser son específicos del navegador, así que un perfil de Chrome devuelve voces apropiadas para Chrome y un perfil de Edge devuelve voces apropiadas para Edge.

¿Pueden los sitios web realmente usar las voces para síntesis?

La lista de voces de BotBrowser está diseñada para la consistencia de la huella digital. La funcionalidad real de texto a voz depende de las capacidades del sistema host. En la mayoría de los flujos de trabajo automatizados, la reproducción TTS no es necesaria, pero la lista de voces debe estar presente y ser precisa para la consistencia de la huella digital.

¿La lista de voces cambia entre versiones del navegador?

Sí. Las actualizaciones del navegador a veces añaden o eliminan soporte de voces. Los perfiles de BotBrowser tienen versiones e incluyen la lista de voces esperada para la versión específica del navegador que representa el perfil.

¿Cuántas voces reporta una plataforma típica?

Windows 11 típicamente reporta 30-50 voces dependiendo de los paquetes de idiomas instalados. macOS reporta 60-80 voces incluyendo variantes de Siri. Chrome en Android reporta 5-15 voces. El recuento exacto es una de las señales de huella digital que BotBrowser controla.

¿La protección de la lista de voces funciona en modo headless?

Sí. BotBrowser aplica la lista de voces del perfil independientemente de si el navegador se ejecuta en modo con interfaz gráfica o headless. Esto es importante porque los entornos headless típicamente no tienen subsistema TTS, y una lista de voces vacía en modo headless es una señal de detección fuerte.

¿Qué pasa con la temporización del evento voiceschanged?

BotBrowser controla la temporización y el comportamiento del evento voiceschanged para coincidir con el patrón esperado de la plataforma objetivo del perfil. Esto incluye si getVoices() devuelve inicialmente un array vacío y qué tan rápido se dispara el evento después de la carga de la página.

Resumen

La lista de voces de la API SpeechSynthesis es una señal de huella digital de alta entropía que revela el sistema operativo, la versión del navegador y la configuración de idioma. Las herramientas de privacidad estándar no pueden abordarla porque los datos de voz provienen del SO, no de la red ni del almacenamiento del navegador. BotBrowser controla las listas de voces a nivel del motor a través de su sistema de perfiles, asegurando consistencia multiplataforma y alineación con todas las demás señales de huella digital. Para protección relacionada, consulta protección de propiedades del navegador, control de huella digital de fuentes y configuración de zona horaria e idioma.

#Speech-Synthesis#fingerprinting#Privacy#Tts#Voice#Platform

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.