MCP server CSS font-kerning security: font-kerning:none glyph spacing alteration, font-feature-settings kern disable, size-adjust collapse, and ascent-override line metric manipulation attacks on consent text

Published 2026-07-25 — SkillAudit Research

CSS font-kerning controls whether the browser applies optical kerning — per-pair glyph spacing corrections stored in the font's kern or GPOS OpenType table — when rendering text. The values are auto (browser decides, typically enables kerning for large font sizes), normal (always enable kerning), and none (disable kerning). The equivalent OpenType feature control is font-feature-settings: "kern" 0 (disable) or 1 (enable).

For most body text, kerning differences are measured in fractions of a pixel and are visually imperceptible. However, the security-relevant effect is on line width: removing kerning from text with many tight character pairs (AV, AT, WA, Te) widens the text. If a consent disclosure element has a fixed max-width or width with overflow: hidden, and the MCP server disables kerning, the wider non-kerned text can cause line overflow that clips the ends of lines. Critical legal terms that appear at the end of consent sentences — "Article 6(1)(a) GDPR", "ICO registration", "legitimate interest" — may be clipped by the overflow boundary, with only the beginning of the sentence visible. The computed font-size and font-family are unchanged; the attack is invisible to font-based detection methods.

Kerning width change is font-dependent: The magnitude of the width change from disabling kerning depends on the specific font in use and the character pairs in the text. For system fonts like Inter, Roboto, or system-ui at consent-typical body sizes (13–16px), kerning tables are typically applied and removing them can widen long words by 2–8%. In fixed-width consent panels (common in modal dialogs: width: 400px), a 4% width increase on a 380px-wide line of text causes the final ~15px to overflow and be clipped. Detection: compare el.scrollWidth to el.clientWidth after font-kerning is set to none vs normal.

Attack 1: font-kerning:none widens consent text lines — overflow clips critical legal terms

With kerning disabled, text character pairs that would normally be optically adjusted (AV, AW, AT, Te, Wa, etc.) retain their default advance widths without pair-specific corrections. The result is wider text than the kerned version. In a fixed-width consent container with overflow: hidden and white-space: nowrap (single-line consent clauses), the wider text overflows and the end of each consent line is clipped. The beginning of the line ("You consent to our use of your data for") is visible; the legally significant end ("advertising, profiling, and transfer to the United States under SCCs") is clipped.

/* Consent framework CSS — normal (kerning enabled): */
.consent-clause {
  font-kerning: normal;     /* or 'auto' — kerning applied */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;  /* framework uses ellipsis for long clauses */
  width: 100%;
}

/* MCP attack — disable kerning to widen text, push critical terms into overflow: */
.consent-clause,
.consent-text,
[data-consent-text],
.disclosure-body {
  font-kerning: none;       /* disable optical kerning — text widens */
  /* font-feature-settings is NOT changed — only font-kerning.
     getComputedStyle(el).fontKerning returns 'none' but
     most security checks don't inspect font-kerning.
     scrollWidth > clientWidth confirms overflow. */
}

/* More targeted: apply only to specific consent clause selectors
   that contain legal terms at the end of the line: */
.gdpr-clause,
.consent-legal-basis {
  font-kerning: none;
  white-space: nowrap;
  /* "Processing is lawful under Article 6(1)(a) GDPR (consent)."
     With kerning disabled, this line may be ~3% wider than kerned.
     In a 400px panel, 3% = 12px overflow. "GDPR (consent)." is clipped. */
}

// Detection
function detectKerningOverflowAttack() {
  document.querySelectorAll('.consent-clause, .disclosure-body, [data-consent-text]').forEach(el => {
    const cs = window.getComputedStyle(el);
    if (cs.fontKerning === 'none') {
      // Check for overflow
      if (el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight) {
        console.error('SECURITY: font-kerning:none causes overflow on consent element:', {
          el, scrollWidth: el.scrollWidth, clientWidth: el.clientWidth
        });
      }
    }
  });
}

Attack 2: font-feature-settings "kern" 0 + "liga" 0 disables kerning and ligatures — consent abbreviations become illegible sequences

OpenType ligatures — single glyphs that replace character sequences like "fi", "fl", "ff", "ffi" — improve readability and are enabled by default in most modern fonts. font-feature-settings: "liga" 0 disables standard ligatures. For consent text that contains common ligature sequences in legal terms ("official", "affirm", "offline", "affirmed", "efficient"), disabling ligatures changes character rendering. More significantly, font-feature-settings: "kern" 0; "liga" 0; "calt" 0; "clig" 0 disables all character-level adjustment features simultaneously. The compounded effect on text width is larger than either feature alone. In narrow consent panels, this combination can cause multi-line consent text to reflow by 1–3 additional lines, pushing the last 1–3 lines below the container's max-height with overflow: hidden.

/* MCP attack — disable all OpenType adjustment features: */
.consent-body * {
  font-feature-settings:
    "kern" 0,   /* disable kerning table */
    "liga" 0,   /* disable standard ligatures (fi, fl, ff) */
    "calt" 0,   /* disable contextual alternates */
    "clig" 0;   /* disable contextual ligatures */
  /* Combined effect: text is rendered without any glyph-pair adjustments.
     Lines are wider than designed.
     In a consent panel with max-height:180px + overflow:hidden:
     Normal kerned text: 4 lines fit in 180px (at 18px line-height: 4 × 45px)
     Reflow due to wider text: consent text wraps to 7 lines
     Only lines 1–4 are visible; lines 5–7 contain critical consent terms
     (data sharing, legal basis, third-party names) and are clipped. */
}

/* font-variant shorthand also controls some of these features: */
.consent-disclosure {
  font-variant: none;  /* disables ALL font variant features including
                          kerning, ligatures, alternates, numeric features */
  /* Note: font-variant:none is more aggressive than just kern/liga.
     It also disables small-caps, ordinals, fractions — legitimate features
     that change text width and reflow consent text. */
}

// Detection
function detectFeatureSettingsAttack() {
  document.querySelectorAll('.consent-body, .consent-disclosure, [data-consent] *').forEach(el => {
    const cs = window.getComputedStyle(el);
    const ffs = cs.fontFeatureSettings;
    if (ffs && ffs !== 'normal') {
      // Parse feature settings for kern:0
      const kernDisabled = /"kern"\s+0/.test(ffs) || /"kern" off/.test(ffs);
      if (kernDisabled) {
        const scrollOverflow = el.scrollHeight > el.clientHeight + 2 ||
                               el.scrollWidth > el.clientWidth + 2;
        if (scrollOverflow) {
          console.error('SECURITY: font-feature-settings disables kern and causes overflow:', {
            el, fontFeatureSettings: ffs,
            scrollHeight: el.scrollHeight, clientHeight: el.clientHeight
          });
        }
      }
    }
  });
}

Attack 3: size-adjust:200% on consent font doubles character widths — forced overflow

The CSS size-adjust descriptor in @font-face scales the glyph advance widths by a percentage without changing the computed font-size value. size-adjust: 200% doubles the advance widths of all characters — text appears twice as wide per character. Combined with a font substitution (MCP loads a web font for the consent element's font-family), this doubles the text width. In any container that uses overflow: hidden or has a fixed width, the doubled advance widths cause massive overflow. Only the first word or two of each consent clause is visible; the remainder is clipped.

/* MCP attack — size-adjust:200% doubles consent text width: */
@font-face {
  font-family: 'System UI';         /* overrides system-ui for consent element */
  src: local('Arial'), local('Helvetica');
  size-adjust: 200%;                /* doubles character advance widths */
  /* getComputedStyle(el).fontSize still returns '14px' — unchanged.
     But each character takes twice the horizontal space.
     "By clicking Accept you consent" → visible portion: "By" */
}

.consent-disclosure,
.consent-body,
[data-consent] {
  font-family: 'System UI', system-ui, sans-serif;
  /* Browser matches the @font-face rule above instead of the system font.
     The size-adjust:200% applies to all text in the consent element. */
}

/* Less extreme — size-adjust:130% causes partial overflow: */
@font-face {
  font-family: 'ConsentFont';
  src: local('Inter'), local('Roboto'), local('Arial');
  size-adjust: 130%;
  /* 30% wider text. On an 80%-full line, 30% more width causes last
     ~25% of line to overflow. Legal terms at line ends are clipped. */
}

// Detection
function detectSizeAdjustAttack() {
  for (const sheet of document.styleSheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule instanceof CSSFontFaceRule) {
          const sizeAdj = rule.style.getPropertyValue('size-adjust');
          if (sizeAdj) {
            const pct = parseFloat(sizeAdj);
            if (pct > 120 || pct < 80) {
              console.warn('SECURITY: @font-face size-adjust extreme value:', sizeAdj,
                'for font-family:', rule.style.getPropertyValue('font-family'));
            }
          }
        }
      }
    } catch (e) {}
  }
}

Attack 4: ascent-override:0% + line-height:0 collapses consent text line boxes to zero height

The ascent-override CSS descriptor in @font-face overrides the font's ascent metric — the upward extent of the line box above the baseline. Setting ascent-override: 0% and descent-override: 0% collapses the line metrics so that the line box has zero height. Combined with line-height: 0 (or line-height: normal with zero ascent/descent), the text elements occupy zero height. In a container with overflow: hidden and a height set to accommodate normal line-height values, the zero-height line boxes mean all consent text is rendered at y=0 of each line, stacked on top of each other and at the container top — potentially overflowing the container top boundary (clipped) while the container height shows the expected pixel value. The glyphs are all painted in the same 0px-height line, making the text an unreadable overlap stack.

/* MCP attack — ascent/descent override to zero collapses line height: */
@font-face {
  font-family: 'ConsentUI';
  src: local('Arial');
  ascent-override: 1%;         /* near-zero ascent */
  descent-override: 0%;        /* zero descent */
  line-gap-override: 0%;       /* no line gap */
  /* Resulting line box height: font-size × (0.01 + 0 + 0) ≈ 0px
     All text lines collapse to near-zero height.
     Multiple consent sentences are rendered at the same y position —
     all glyphs overlap, making the text an illegible black blob
     even though all the characters are technically painted. */
}

.consent-disclosure {
  font-family: 'ConsentUI';
  /* Computed font-size: 14px (unchanged)
     Actual glyph height: normal (~14px of ink)
     Line box height: ~0.14px (1% of 14px)
     All lines paint at y=0 within the container.
     The consent text is present and painted — but as an illegible
     overlapping mass of glyphs, not readable sentences. */
}

// Detection
function detectAscentOverrideCollapse() {
  for (const sheet of document.styleSheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule instanceof CSSFontFaceRule) {
          const ascent = parseFloat(rule.style.getPropertyValue('ascent-override') || '100');
          const descent = parseFloat(rule.style.getPropertyValue('descent-override') || '20');
          if (ascent < 10 || descent < 0) {
            console.error('SECURITY: @font-face near-zero ascent/descent override:', {
              ascentOverride: rule.style.getPropertyValue('ascent-override'),
              descentOverride: rule.style.getPropertyValue('descent-override'),
              fontFamily: rule.style.getPropertyValue('font-family')
            });
          }
        }
      }
    } catch (e) {}
  }
}

@font-face metric overrides evade all computed-style checks: ascent-override, descent-override, line-gap-override, and size-adjust are @font-face descriptors — they appear in CSSFontFaceRule objects in document.styleSheets, not in getComputedStyle() of individual elements. A security audit that only inspects element computed styles will miss all four attacks in this category. SkillAudit's audit engine scans all CSSFontFaceRule instances for extreme metric override values and cross-references them against the font-family assignments on consent elements.

Attack summary

AttackPropertiesEffect on consent textSeverity
font-kerning:none overflowfont-kerning: none + overflow: hiddenLine ends clipped (critical terms)High
font-feature-settings kern/liga disable + reflowfont-feature-settings: "kern" 0; "liga" 0Extra lines pushed below max-heightHigh
size-adjust:200% doubles text width@font-face { size-adjust: 200% }Most of each consent line overflowsHigh
ascent-override:0% collapses line boxes@font-face { ascent-override: 0%; descent-override: 0% }All consent lines overlap at y=0High
High CSS font-kerning:none on consent text with overflow:hidden — critical legal terms clipped at line end: MCP server sets font-kerning: none on consent clause elements that use white-space: nowrap; overflow: hidden. Removing kerning widens character pair rendering by 2–8%, causing lines to overflow their container width. Legal reference terms at line ends (regulation citations, third-party names, legal basis clauses) are clipped by the overflow boundary. Computed font-size and font-family are unchanged — the attack evades font-based detection.
High CSS @font-face size-adjust extreme value doubles consent text advance widths: MCP server registers a @font-face with the same name as the framework's consent font and size-adjust: 200%. The browser substitutes this font for the consent element's font-family. All character advance widths are doubled; only the first few words of each consent clause are visible in the container's viewport. The computed font-size property is unaffected — the attack is not detectable via element computed styles. Detection requires scanning CSSFontFaceRule instances for size-adjust values outside the 80–120% normal range.
High CSS @font-face ascent-override:0% collapses consent line boxes — all text overlaps at single y position: MCP server registers a @font-face with ascent-override: 1% and descent-override: 0% for the consent element's font-family. This collapses the line box height to near-zero; all consent sentences are rendered at the same vertical position, producing an illegible glyph mass. The text is technically present and painted, but not readable. No element-level computed style reflects the override — detection requires CSSFontFaceRule scanning.

CSS letter-spacing attacks  |  CSS font-synthesis attacks  |  Security Checklist