Messen statt aufzählen
Es gab nie eine Web-API, die eine Liste installierter Schriftarten zurückgibt. Es musste nie eine geben, weil Messen fast genauso gut funktioniert und überhaupt keine Erlaubnis braucht.
Die Technik ist dieselbe wie etwa seit 2007. Rendere eine Zeichenkette in einer bekannten Ersatzschrift und merke dir die Breite. Dann rendere dieselbe Zeichenkette und fordere zuerst die Kandidatenschrift an, mit derselben Ersatzschrift dahinter. Ändert sich die Breite, existiert die Kandidatin und wurde benutzt. Ändert sie sich nicht, fehlt die Kandidatin, und die Ersatzschrift hat erneut gezeichnet.
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;
Die Testzeichenkette ist nicht beliebig. mmmmmmmmmmlli mischt die breitesten und schmalsten Glyphen der meisten lateinischen Schriften und maximiert so den Breitenunterschied zwischen zwei Schriften. Bei 72 Pixeln ist der Unterschied nicht zu übersehen.
Lass das über eine Liste von zwei- bis dreihundert Kandidaten laufen, und du hast das Schriftartenprofil. Es kostet ein paar Dutzend Millisekunden und hinterlässt in keiner Berechtigungsabfrage eine Spur.
Warum Schriftarten das Betriebssystem verraten
Schriftartensätze sind nicht zufällig. Sie kommen mit dem Betriebssystem, und jede Plattform liefert einen unverwechselbaren Satz.
| Schriftart | Kommt mit | Was ihr Vorhandensein bedeutet |
|---|---|---|
Segoe UI | Windows Vista und neuer | Windows, nahezu eindeutig |
Segoe UI Variable | Windows 11 | Unterscheidet 11 von 10 |
Helvetica Neue | macOS | Apple-Plattform |
Ubuntu, Liberation Sans | Gängige Linux-Distributionen | Linux |
Calibri, Cambria | Microsoft Office | Office installiert; schwaches Windows-Signal |
Arial, Times New Roman | Fast überall | Nichts |
Das ist die Prüfung, die einen gefälschten User Agent gratis erwischt. Ein Profil, das Windows 11 behauptet und Segoe UI Variable nicht rendern kann, hat sich in einem einzigen offsetWidth-Vergleich selbst widersprochen — ohne externe Daten, ohne Reputationsdatenbank, ohne maschinelles Lernen.
Der umgekehrte Fehler ist genauso häufig und schwerer zu sehen: ein Profil, das auf einem Windows-Host macOS behauptet, während das Segoe UI des Hosts weiterhin installiert und messbar ist. Der User Agent sagt Mac; die Schriftarten sagen Windows; die Schriftarten gewinnen.
Wie viel es wert ist
Die Panopticlick-Arbeit der EFF bezifferte Schriftartenlisten auf rund 13,9 Bit identifizierender Information — genug, um einen Browser allein damit auf etwa einen von fünfzehntausend einzugrenzen. Das gehört zu den höchsten Einzelsignalwerten der Fingerprinting-Literatur, über der Bildschirmauflösung und deutlich über dem User-Agent-String.
Zwei Einschränkungen halten das ehrlich. Erstens stammt diese Zahl von einer selbstgewählten Gruppe datenschutzinteressierter Nutzer und überschätzt die Entropie für das allgemeine Web. Zweitens sinkt sie: Windows 11, aktuelle macOS-Versionen und gängige Linux-Distributionen liefern zunehmend ähnliche Standardsätze aus, und Webfonts haben fast jeden Grund beseitigt, zusätzliche Schriften zu installieren.
Was diesen Rückgang überlebt, ist genau der Teil, auf den es hier ankommt. Selbst wenn die Schriftartenliste eine Maschine nicht mehr eindeutig identifiziert, identifiziert sie weiterhin die Plattform — und die Plattformbestimmung ist es, die ein unstimmiges Profil auffliegen lässt.
Die anderen Wege zu fragen
Die Breitenmessung ist die klassische Methode, nicht die einzige. Eine Umsetzung, die nur offsetWidth hookt, verpasst diese hier.
measureText auf dem Canvas
ctx.measureText() liefert ein TextMetrics-Objekt mit subpixelgenauer Vorschubbreite und, in modernen Browsern, den tatsächlichen Ober- und Unterlängen der Begrenzungsbox. Das ist genauer als eine Messung am Element und lebt in der Canvas-API, lässt sich also in einem OffscreenCanvas innerhalb eines Workers ausführen, wohin Hooks auf Seitenebene nicht reichen.
Reines CSS
Schrifterkennung geht ganz ohne JavaScript, mit @font-face samt unicode-range und einer Hintergrundbild-Anfrage, die nur ausgelöst wird, wenn eine bestimmte Ersatzschrift zum Zug kommt. Kein Skript zum Hooken, keine API zum Überschreiben.
Local Font Access
Chrome 103 brachte window.queryLocalFonts(), das die vollständige lokale Schriftartenliste direkt zurückgibt — hinter einer Berechtigungsabfrage. In der Praxis nutzen Fingerprinting-Skripte das nicht, eben weil es nachfragt. Wissenswert ist es vor allem deshalb, weil ein Browser, der Chrome 147 behauptet, die API haben sollte — und ein Profil, das sie entfernt hat, eine neue Unstimmigkeit geschaffen hat.
Es richtig machen
Zwei Wege funktionieren, ein sehr verbreiteter nicht.
Was nicht funktioniert: eine leere oder minimale Liste zurückgeben. Ein Browser mit fünf Schriftarten fällt mehr auf als einer mit zweihundert. Es ist derselbe Fehler wie beim Randomisieren des Canvas — du hast einen gewöhnlichen Wert durch einen seltenen ersetzt und das Privatsphäre genannt.
Weg eins: den richtigen Satz ausliefern. Das Profil behauptet Windows 11, also ist die Schriftartenliste der Standardsatz von Windows 11, vorhanden und messbar. Vollständig erreichbar ist das nur, wenn der Schriftstapel selbst unter Kontrolle ist — der Browser muss Schriftanfragen gegen einen deklarierten Satz auflösen statt gegen das, was der Host installiert hat. Das ist eine Sache der Engine, und deshalb gehört es zu dem, was sich am schwersten nachrüsten lässt.
Weg zwei: keine Plattform behaupten, auf der du nicht bist. Windows-Profile auf Windows, Mac-Profile auf Mac. Unglamourös, vollkommen wirksam und kostenlos. Wenn du den Host frei wählen kannst, ist das die billigste richtige Antwort.
Welchen Weg du auch nimmst, es gilt die Einschränkung aus dem Konsistenz-Artikel: Der Schriftartensatz muss zum User Agent, zum WebGL-Renderer und zum Plattform-String passen. Schriftarten sind schlicht das billigste der vier Dinge zum Prüfen, und deshalb werden sie zuerst geprüft.
Die eigenen prüfen
Der schnellste Test ist eine einzelne Probe gegen das Betriebssystem, das du behauptest. Füge das in die Konsole eines Profils ein und lies die Antwort:
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')
});
Genau eines davon sollte wahr sein, und zwar das, das zu deinem User Agent passt. Zwei wahre Werte heißen, dass dein Profil zwei Betriebssysteme gleichzeitig trägt. Für ein vollständigeres Bild zählen browserleaks.com/fonts und CreepJS beide den kompletten Satz auf, und CreepJS sagt dir rundheraus, wenn die Liste nicht zur behaupteten Plattform passt.