MCP server CSS font-variant-caps security: all-small-caps sub-threshold glyph rendering, petite-caps sub-threshold, unicase mixed rendering, and JS mousedown caps variant injection

Published 2026-08-07 — SkillAudit Research

The CSS font-variant-caps property controls the use of alternate glyphs for capital letters. Values like all-small-caps and petite-caps render lowercase letters as scaled-down uppercase glyph forms. The scaling ratio is font-dependent but typically around 65–75% of the declared em-size. A consent element with font-size: 13px and font-variant-caps: all-small-caps renders its lowercase content at approximately 8.5–9.75px effective glyph height — below the 10px readability threshold used in MCP consent audits. The declared font-size remains 13px; the getComputedStyle(el).fontSize returns '13px'; the threshold check passes. Only getComputedStyle(el).fontVariantCaps reveals the attack.

This property is distinct from the font-variant shorthand — getComputedStyle(el).fontVariant may return a complex shorthand value that does not isolate the caps sub-feature. The fontVariantCaps computed property must be checked separately. Browser support: all-small-caps and petite-caps are supported in Chrome 52+, Firefox 34+, Safari 9.1+. See also font-optical-sizing attacks and font-variant-numeric attacks for related sub-property patterns.

Detection gap: getComputedStyle(el).fontSize returns the declared size, not the rendered glyph height. A threshold check on fontSize alone passes. Detection requires: (1) check getComputedStyle(el).fontVariantCaps for 'small-caps', 'all-small-caps', 'petite-caps', 'all-petite-caps', or 'unicase'; (2) if any sub-threshold caps variant is found, compute the effective glyph size as fontSize × scaling ratio (approximately 0.7) and flag if below 10px.

Attack 1: font-variant-caps:all-small-caps at 13px — effective glyph height ~9px (SA-CSS-FVCP-001)

The consent element sets font-size: 13px and font-variant-caps: all-small-caps. The all-small-caps value converts both uppercase and lowercase letters to small-capital glyph forms. For most system fonts, the small-cap x-height is approximately 65–75% of the declared cap-height. At 13px declared, the actual rendered consent text appears at approximately 8.5–9.75px effective height — below the MCP consent readability threshold of 10px. The font-size: 13px check passes because CSS reports the declared size, not the optical size of the rendered glyphs. The attack targets exactly the gap between the CSS font-size model and the actual pixel height of lowercase glyphs in small-caps form.

/* MCP attack: */
.consent-disclosure {
  font-size: 13px;                      /* above 10px threshold — check passes */
  font-variant-caps: all-small-caps;    /* lowercase rendered as scaled-down caps */
  /* Effective lowercase glyph height: 13px × 0.70 ≈ 9.1px — sub-threshold
     getComputedStyle(el).fontSize === '13px' ← passes
     getComputedStyle(el).fontVariantCaps === 'all-small-caps' ← reveals attack */
}

// Detection:
function detectSmallCapsSubThreshold(el) {
  const cs = window.getComputedStyle(el);
  const fvc = cs.fontVariantCaps;
  const subThresholdCaps = ['small-caps','all-small-caps','petite-caps','all-petite-caps'];
  if (subThresholdCaps.includes(fvc)) {
    const declared = parseFloat(cs.fontSize);
    const effective = declared * 0.70;  // conservative scaling ratio
    if (effective < 10) {
      console.error('SA-CSS-FVCP-001: font-variant-caps renders below 10px threshold', {
        el, fontVariantCaps: fvc, declaredFontSize: declared, effectiveGlyphHeight: effective
      });
    }
  }
}

Attack 2: font-variant-caps:petite-caps at 14px — sub-threshold at smaller scaling ratio (SA-CSS-FVCP-002)

petite-caps uses even smaller glyph forms than small-caps — designed to match the x-height of the font's main lowercase letters, rather than the cap-height. The petite-caps scaling ratio is typically 55–65% of the declared em-size. At 14px declared with petite-caps, the rendered consent text appears at approximately 7.7–9.1px — sub-threshold even at a declared size that clears most MCP consent auditing thresholds by 40%. all-petite-caps applies the same scaling to both uppercase and lowercase letters, ensuring the entire consent text is in petite-caps form rather than a mix.

/* MCP attack: */
.consent-disclosure {
  font-size: 14px;                    /* 40% above threshold — passes easily */
  font-variant-caps: petite-caps;     /* petite scaling ~60% of declared */
  /* Effective height: 14px × 0.60 ≈ 8.4px — clearly sub-threshold
     More aggressive than small-caps; fewer fonts support it
     Fallback to small-caps if petite-caps unsupported — still sub-threshold */
}

/* all-petite-caps variant — applies to both cases: */
.consent-disclosure {
  font-size: 14px;
  font-variant-caps: all-petite-caps; /* uppercase AND lowercase in petite form */
}

// Detection:
function detectPetiteCaps(el) {
  const cs = window.getComputedStyle(el);
  const fvc = cs.fontVariantCaps;
  if (fvc === 'petite-caps' || fvc === 'all-petite-caps') {
    const declared = parseFloat(cs.fontSize);
    const effective = declared * 0.60;
    if (effective < 10) {
      console.error('SA-CSS-FVCP-002: petite-caps renders consent below threshold', {
        el, fontVariantCaps: fvc, declaredFontSize: declared, effectiveGlyphHeight: effective
      });
    }
  }
}

Attack 3: font-variant-caps:unicase — mixed normal and small-caps rendering (SA-CSS-FVCP-003)

unicase renders a mix of normal uppercase glyphs and small-cap glyphs for certain characters, creating inconsistent glyph sizing across the consent text. Consent text typically contains a mix of uppercase and lowercase characters — the inconsistent sizing makes some key words appear at sub-threshold sizes while others appear normal. The goal is to render the operative parts of the consent (verb phrases: "grants access to", "can read", "will send") in the smaller glyph form while initial characters of sentences appear at normal size, reducing the apparent aggressiveness of the attack.

/* MCP attack: */
.consent-disclosure {
  font-size: 12px;
  font-variant-caps: unicase;   /* inconsistent sizing — some glyphs sub-threshold */
  /* Uppercase letters render at normal cap-height
     Lowercase-that-map-to-small-caps render at ~8.4px
     Key consent words in mixed case: partially below threshold */
}

// Detection:
function detectUnicase(el) {
  const cs = window.getComputedStyle(el);
  if (cs.fontVariantCaps === 'unicase') {
    const declared = parseFloat(cs.fontSize);
    if (declared < 14) {  // conservative: flag unicase below 14px
      console.error('SA-CSS-FVCP-003: unicase font-variant-caps produces inconsistent glyph sizes', {
        el, fontVariantCaps: 'unicase', declaredFontSize: declared
      });
    }
  }
}

Attack 4: JS mousedown sets font-variant-caps:all-small-caps at install click (SA-CSS-FVCP-004)

At page load, the consent element has standard font-size: 14px with no font-variant-caps setting. Load-time audit sees 14px, normal caps — passes. At mousedown on the install button, JS sets el.style.fontVariantCaps = 'all-small-caps' on the consent element. The effective glyph size drops to approximately 9.8px (14 × 0.70) — just below the 10px readability threshold. The transition is instantaneous and visual — the consent text appears to "sharpen" or change style at the moment of the click, which users may interpret as a focus effect. MutationObserver on the consent element's style attribute detects the fontVariantCaps change.

/* Baseline CSS: */
.consent-disclosure {
  font-size: 14px;
  /* No font-variant-caps — normal rendering */
}

// MCP JS — triggers at install click:
document.querySelector('#install-btn').addEventListener('mousedown', () => {
  const consent = document.querySelector('.consent-disclosure');
  if (consent) {
    consent.style.fontVariantCaps = 'all-small-caps';
    /* Effective glyph height: 14px × 0.70 ≈ 9.8px — below 10px threshold
       Appears as a style change — not obviously suspicious to user */
  }
}, { capture: true });

// Detection:
function detectDynamicFontVariantCaps(el) {
  new MutationObserver(() => {
    const cs = window.getComputedStyle(el);
    const fvc = cs.fontVariantCaps;
    const subThreshold = ['small-caps','all-small-caps','petite-caps','all-petite-caps'];
    if (subThreshold.includes(fvc)) {
      const declared = parseFloat(cs.fontSize);
      const effective = declared * 0.70;
      if (effective < 10) {
        console.error('SA-CSS-FVCP-004: JS set font-variant-caps at install click', {
          el, fontVariantCaps: fvc, effectiveGlyphHeight: effective
        });
      }
    }
  }).observe(el, { attributes: true, attributeFilter: ['style'] });
}

Root detection method: Check getComputedStyle(consentEl).fontVariantCaps as a distinct property from fontVariant. Any value of 'small-caps', 'all-small-caps', 'petite-caps', or 'all-petite-caps' when combined with fontSize < 14px should be flagged as potentially sub-threshold. Compute effective glyph height as fontSize × 0.70 (conservative) and report if below 10px. SkillAudit checks fontVariantCaps independently on every consent element — distinct from the font-variant shorthand check.

Attack summary

IDTechniqueDeclared fontSizeEffective heightfontVariantCapsSeverity
SA-CSS-FVCP-001all-small-caps at 13px — lowercase as scaled-down caps13px~9.1px'all-small-caps'High
SA-CSS-FVCP-002petite-caps at 14px — even smaller than small-caps14px~8.4px'petite-caps'High
SA-CSS-FVCP-003unicase at 12px — inconsistent glyph sizes, key words sub-threshold12pxmixed'unicase'Medium
SA-CSS-FVCP-004JS mousedown sets all-small-caps at install click14px~9.8px (after)'all-small-caps' (after)High

Consolidated findings

High SA-CSS-FVCP-001 — font-variant-caps:all-small-caps at 13px; effective lowercase glyph height ~9.1px — below 10px readability threshold; declared fontSize passes threshold check
High SA-CSS-FVCP-002 — font-variant-caps:petite-caps at 14px; petite scaling ~60% of declared; effective height ~8.4px; check fontVariantCaps separately from fontVariant shorthand
Medium SA-CSS-FVCP-003 — font-variant-caps:unicase at 12px; mixed normal/small-caps rendering; operative consent words in smaller glyph form; inconsistent readability
High SA-CSS-FVCP-004 — JS mousedown sets fontVariantCaps='all-small-caps' at install click; effective height drops below 10px; MutationObserver on consent element style detects

See also: CSS font-optical-sizing attacks | CSS font-variant-numeric attacks | CSS stacking context consent bypass | SkillAudit — free MCP server audit