Medición, no enumeración
Nunca ha existido una API web que devuelva la lista de fuentes instaladas. Nunca ha hecho falta, porque medir funciona casi igual de bien y no requiere permiso alguno.
La técnica es la misma desde alrededor de 2007. Renderiza una cadena en una fuente de respaldo conocida y anota su ancho. Luego renderiza la misma cadena pidiendo primero la fuente candidata, con el mismo respaldo detrás. Si el ancho cambia, la candidata existe y se usó. Si no cambia, la candidata falta y el respaldo volvió a dibujarla.
const span = document.createElement('span');
span.style.cssText = 'position:absolute;left:-9999px;font-size:72px';
span.textContent = 'mmmmmmmmmmlli';
span.style.fontFamily = 'monospace';
document.body.appendChild(span);
const base = span.offsetWidth;
span.style.fontFamily = '"Segoe UI", monospace';
const installed = span.offsetWidth !== base;
La cadena de prueba no es arbitraria. mmmmmmmmmmlli mezcla los glifos más anchos y más estrechos de casi cualquier tipografía latina, maximizando la diferencia de ancho entre dos fuentes cualesquiera. A 72 píxeles la diferencia es imposible de no ver.
Pasa eso por una lista de doscientas o trescientas candidatas y ya tienes el perfil de fuentes. Cuesta unas decenas de milisegundos y no deja rastro en ninguna solicitud de permiso.
Por qué las fuentes identifican el sistema operativo
Los conjuntos de fuentes no son aleatorios. Vienen con el sistema operativo, y cada plataforma trae un conjunto característico.
| Fuente | Viene con | Qué significa su presencia |
|---|---|---|
Segoe UI | Windows Vista y posteriores | Windows, casi con certeza |
Segoe UI Variable | Windows 11 | Distingue 11 de 10 |
Helvetica Neue | macOS | Plataforma Apple |
Ubuntu, Liberation Sans | Distribuciones Linux habituales | Linux |
Calibri, Cambria | Microsoft Office | Office instalado; señal débil de Windows |
Arial, Times New Roman | Casi en todas partes | Nada |
Esta es la comprobación que caza gratis a un user agent falseado. Un perfil que dice ser Windows 11 y no puede dibujar Segoe UI Variable se ha contradicho en una sola comparación de offsetWidth: sin datos externos, sin base de reputación, sin aprendizaje automático.
El error inverso es igual de común y más difícil de ver: un perfil que dice ser macOS sobre un anfitrión Windows, donde el Segoe UI del anfitrión sigue instalado y medible. El user agent dice Mac; las fuentes dicen Windows; ganan las fuentes.
Cuánto vale
El trabajo Panopticlick de la EFF situó las listas de fuentes en torno a 13,9 bits de información identificativa: suficiente por sí sola para acotar un navegador a uno entre unos quince mil. Es de las cifras más altas de una sola señal en la literatura sobre fingerprinting, por encima de la resolución de pantalla y muy por encima de la cadena del user agent.
Dos matices para no exagerar. Primero, esa cifra procede de una población autoseleccionada de usuarios interesados en la privacidad y sobreestima la entropía del web general. Segundo, va bajando: Windows 11, las versiones recientes de macOS y las distribuciones Linux mayoritarias traen conjuntos por defecto cada vez más parecidos, y las fuentes web han eliminado casi todo motivo para instalar extras.
Lo que sobrevive a ese descenso es justo la parte que aquí importa. Aunque la lista de fuentes ya no identifique una máquina de forma única, sigue identificando la plataforma — y la identificación de plataforma es lo que revienta un perfil incoherente.
Las otras formas de preguntar
La medición del ancho es el método clásico, no el único. Una implementación que solo engancha offsetWidth se perderá estos.
measureText del canvas
ctx.measureText() devuelve un objeto TextMetrics con el ancho de avance subpíxel y, en navegadores modernos, el ascendente y descendente reales del rectángulo delimitador. Es más preciso que medir sobre elementos y vive en la API de canvas, así que puede ejecutarse dentro de un OffscreenCanvas en un worker, donde los enganches a nivel de página no llegan.
Solo CSS
La detección de fuentes se puede hacer sin nada de JavaScript, usando @font-face con unicode-range y una petición de imagen de fondo que solo se dispara cuando se usa un respaldo concreto. Ningún script que enganchar, ninguna API que sobrescribir.
Local Font Access
Chrome 103 incorporó window.queryLocalFonts(), que devuelve directamente la lista completa de fuentes locales — tras una solicitud de permiso al usuario. En la práctica los scripts de fingerprinting no la usan, precisamente porque pide permiso. Conviene conocerla sobre todo porque un navegador que dice ser Chrome 147 debería tener la API presente, y un perfil que la ha quitado ha creado una incoherencia nueva.
Hacerlo bien
Hay dos enfoques que funcionan, y uno muy común que no.
Lo que no funciona: devolver una lista vacía o mínima. Un navegador con cinco fuentes llama más la atención que uno con doscientas. Es el mismo error que aleatorizar el canvas: has sustituido un valor común por uno raro y lo has llamado privacidad.
Enfoque uno: entregar el conjunto correcto. El perfil dice ser Windows 11, así que la lista de fuentes es el conjunto por defecto de Windows 11, presente y medible. Esto solo se logra del todo cuando la propia pila de fuentes está bajo control: el navegador debe resolver las peticiones de fuente contra un conjunto declarado y no contra lo que tenga instalado el anfitrión. Es un asunto de motor, y por eso es de las cosas más difíciles de añadir a posteriori.
Enfoque dos: no declares una plataforma en la que no estás. Ejecuta los perfiles de Windows en Windows y los de Mac en Mac. Poco glamuroso, completamente eficaz y gratis. Si puedes elegir el anfitrión, es la respuesta correcta más barata que existe.
Tomes el camino que tomes, se aplica la restricción de el artículo sobre coherencia: el conjunto de fuentes tiene que concordar con el user agent, con el renderizador WebGL y con la cadena de plataforma. Las fuentes son sencillamente lo más barato de comprobar de los cuatro, y por eso se comprueban primero.
Comprobar las tuyas
La prueba más rápida es una sola sonda contra el sistema operativo que declaras. Pega esto en la consola de un perfil y lee la respuesta:
function has(f) {
const s = document.createElement('span');
s.style.cssText = 'position:absolute;left:-9999px;font-size:72px';
s.textContent = 'mmmmmmmmmmlli';
document.body.appendChild(s);
s.style.fontFamily = 'monospace'; const base = s.offsetWidth;
s.style.fontFamily = `"${f}", monospace`; const w = s.offsetWidth;
s.remove();
return w !== base;
}
console.log({
windows: has('Segoe UI'),
win11: has('Segoe UI Variable'),
macos: has('Helvetica Neue'),
linux: has('Ubuntu')
});
Exactamente uno de esos debería ser verdadero, y debe ser el que cuadra con tu user agent. Dos valores verdaderos significan que tu perfil lleva puestos dos sistemas operativos a la vez. Para un cuadro más completo, browserleaks.com/fonts y CreepJS enumeran el conjunto completo y, en el caso de CreepJS, te dirá sin rodeos cuándo la lista no concuerda con la plataforma que declaras.