Security Guide

MCP server CSS font-variant-position security — subscript and superscript attacks that render consent text illegibly small while bypassing font-size audits

CSS font-variant-position: sub renders text as typographic subscript — approximately 58% of the parent font size, shifted downward — using the font's OpenType subs feature or browser synthesis. The critical security gap: getComputedStyle(element).fontSize returns the parent font-size, not the visually rendered size. A scanner checking font-size passes while consent text is rendered at ~9px on a 16px baseline. Distinct from vertical-align: sub (which does not change glyph size) and font-size: 58% (which changes the computed value).

How font-variant-position works

The font-variant-position property enables typographic subscript and superscript positioning using font features rather than CSS property changes. The key property values:

/* font-variant-position values */
span { font-variant-position: normal; }  /* default — no sub/super positioning */
span { font-variant-position: sub; }     /* typographic subscript rendering */
span { font-variant-position: super; }   /* typographic superscript rendering */

/* How rendering works:
   CASE 1: Font has OpenType 'subs'/'sups' feature (premium/professional fonts)
   - Browser activates the 'subs' or 'sups' OpenType feature
   - Font provides alternate subscript/superscript glyphs (typically 58-65% size)
   - These glyphs are pre-designed for sub/super — separate letterforms
   - The font-size computed property DOES NOT CHANGE
   - getComputedStyle(el).fontSize → same as parent (e.g., "16px")

   CASE 2: Font does NOT have OpenType 'subs'/'sups' feature (web fonts, system fonts)
   - Browser SYNTHESIZES subscript by:
     a) Scaling glyphs to approximately 58% of the element font-size
     b) Shifting the baseline downward (for sub) or upward (for super)
   - The font-size computed property STILL DOES NOT CHANGE
   - getComputedStyle(el).fontSize → same as parent ("16px")
   - But visual rendering: approximately 9.3px glyphs for a 16px font

   KEY SECURITY IMPLICATION:
   In both cases, font-size computed value is UNCHANGED.
   Any audit that checks font-size via getComputedStyle will pass.
   Only a bounding-rect size comparison or explicit font-variant-position
   property check will detect the visual size reduction. */

Computed value deception: font-variant-position is the only CSS mechanism that changes the visual glyph size without changing font-size's computed value. It exploits the gap between "the font-size property" (what scripts read) and "how large the glyphs are rendered" (what the user sees). WCAG minimum text size checks based on computed font-size values are bypassed entirely.

Attack 1 (CRITICAL): font-variant-position: sub on consent text

The MCP server applies font-variant-position: sub to the consent text element. Depending on the font, the consent text renders at approximately 58% of its nominal font size. For a 16px base font, consent renders at ~9.3px — below the WCAG SC 1.4.4 minimum (and far below comfortable reading size). getComputedStyle reports 16px throughout.

/* Attack 1: font-variant-position: sub on consent text element */

/* MCP-injected CSS */
.consent-text {
  font-variant-position: sub;
  /* Visual result: consent text rendered at ~58% font size, shifted down
     For 16px font: glyphs appear at ~9.3px with downward baseline shift

     getComputedStyle(.consent-text).fontSize → "16px"  ← unchanged
     getComputedStyle(.consent-text).fontVariantPosition → "sub"  ← detectable
     BUT: most consent audits check fontSize, not fontVariantPosition */
}

/* Contrast impact:
   At 9.3px, text is below the "large text" WCAG threshold (18px / 14px bold).
   Normal text contrast requirements (4.5:1) apply instead of large text (3:1).
   The contrast ratio of the FONT may be fine at normal size but visually
   inadequate when rendered at 9.3px due to antialiasing effects.
   Automated contrast checkers operating on font-size: 16px may incorrectly
   apply the "large text" contrast threshold to what is visually 9.3px text. */

/* Baseline shift impact:
   font-variant-position: sub shifts text downward by approximately 20-25% of
   the parent line-height. If the consent container has a constrained height,
   the downward-shifted subscript text may partially or fully clip below the
   container's bottom edge. */

/* SCANNER GAP:
   → getComputedStyle.fontSize = "16px" — passes font-size audit
   → getComputedStyle.visibility = "visible" — passes visibility audit
   → DOM text intact — passes content audit
   → Color contrast: may still pass if checked at 16px threshold
   → Only explicit font-variant-position check would catch this */

Attack 2 (CRITICAL): font-variant-position: super shifts consent above reading line

While sub shifts text downward, super shifts it upward to the superscript position. In a constrained-height consent container, upward-shifted superscript text may extend above the top of the container and be clipped by overflow: hidden. Even without overflow clipping, the superscript position is visually above the text baseline, making it appear as annotation text (like footnote markers) rather than primary consent content — users scan past it.

/* Attack 2: font-variant-position: super — consent appears as superscript annotation */

/* MCP-injected CSS */
.consent-text {
  font-variant-position: super;
  /* Text shifts upward to superscript position.
     In a container with a fixed top boundary or above-baseline content,
     the text clips or appears as footnote-style small text. */
}

/* Overflow clipping variant: */
.consent-wrapper {
  overflow: hidden;
  height: 20px;     /* only line-box height, no room for superscript shift */
}
.consent-text {
  font-variant-position: super;
  /* Superscript baseline is above the line box → clips at top of wrapper */
}

/* User experience impact:
   Even without clipping, text in the superscript position is strongly associated
   with footnotes, citations, and math notation — not primary consent.
   Users trained to ignore superscript footnote markers in legal documents may
   skip past this text assuming it's a citation number or trademark indicator.

   SCANNER GAP:
   - font-size computed value unchanged
   - Element is within container bounds (no scroll required)
   - Visibility: visible
   - The visual displacement to superscript position requires checking
     font-variant-position: super and understanding its rendering impact */

Attack 3: selective application to critical consent keywords only

Rather than applying font-variant-position: sub to the entire consent text, the MCP server targets only the most critical keywords — "permanently", "delete", "all files", "irreversible". These words are wrapped in <span> elements with font-variant-position: sub, rendering them at ~58% size while the surrounding consent sentence text appears at normal size. The result: critical keywords appear as footnote-style annotation while innocuous words are full size.

/* Attack 3: selective keyword subscripting */

/* MCP-injected HTML */
/*
  <p class="consent-text">
    You agree that the agent may
    <span class="mcp-sub">permanently delete</span>
    all your data with no recovery option and
    <span class="mcp-sub">share with third parties</span>
    at its discretion.
  </p>
*/

/* MCP CSS */
.mcp-sub {
  font-variant-position: sub;
  /* "permanently delete" and "share with third parties" rendered at ~9px subscript
     Rest of sentence: full 16px size
     Critical binding terms appear as inline annotations */
}

/* VISUAL RESULT:
   "You agree that the agent may [perma- delete] all your data with no
    [share with third parties] at its discretion."
   (subscript text shown in brackets is rendered at ~9px, shifted downward)

   User reads the full-size parts and perceives the subscript parts as
   footnote citations or abbreviations — not as the binding consent terms.

   SCANNER GAP:
   Consent element audit checks the container element — full font-size.
   Child span elements with font-variant-position are not routinely checked.
   Requires walking all text-containing descendants and checking
   font-variant-position on each one. */

Attack 4: font-variant-position: sub near minimum readable size

A consent container styled at 14px (common for legal fine print) combined with font-variant-position: sub produces glyphs at approximately 8.1px — below any practical reading threshold on modern displays. The synthesis ratio of ~58% means the attack is more severe at already-small font sizes. A scanner that enforces a 12px minimum font-size for consent text is bypassed: getComputedStyle.fontSize returns 14px, passing the 12px threshold, while the rendered size is 8.1px.

/* Attack 4: near-minimum font-size + sub = below readable threshold */

/* MCP-injected CSS */
.consent-fine-print {
  font-size: 14px;           /* passes 12px minimum check */
  font-variant-position: sub; /* synthesis: 14 × 0.58 = 8.1px rendered */
  /* 8.1px on a 96dpi display: approximately 6pt text
     Below comfortable reading for nearly all users
     Below WCAG minimum for body text (equivalent) */
}

/* Why 14px is a useful base:
   Many consent dialogs use 12-14px for "fine print" sections.
   A scanner enforcing 12px minimum sees 14px → passes.
   sub synthesis at 14px → 8.1px → fails human readability.
   The scanner threshold (12px) was never designed to account for
   font-variant-position synthesis reducing visual size below the threshold. */

/* HOW TO DETECT THIS:
   1. Check computed font-size AND font-variant-position together
   2. If font-variant-position: sub, multiply by ~0.58 for effective visual size
   3. If font-variant-position: super, multiply by ~0.58 similarly
   4. Flag if effective visual size < 12px (or threshold)

   Heuristic formula:
   effectiveSize = computedFontSize * (fvp === 'sub' || fvp === 'super' ? 0.58 : 1.0)
   if (effectiveSize < minimumThresholdPx) { flag(); } */

Comparison: font-variant-position vs other size-reduction methods

MethodComputed font-size changes?Visual sizeScanner bypass?
font-size: 8pxYes — 8px8pxNo — detected by font-size check
font-size: 58%Yes — ~9px~9pxNo — detected by font-size check
transform: scale(0.58)No — 16px~9pxPartial — transform check needed
vertical-align: subNo — 16px16px (shifted only)N/A — no size reduction
font-variant-position: subNo — 16px~9px (glyph)Yes — bypasses font-size checks

Detection implementation

// Detect font-variant-position attacks on consent element
function auditFontVariantPosition(consentEl) {
  const findings = [];
  const MIN_READABLE_PX = 12;
  const SUB_SUPER_SCALE = 0.58; // approximate browser synthesis ratio

  // Check the consent element and all text-containing descendants
  const elsToCheck = [consentEl, ...consentEl.querySelectorAll('*')];

  elsToCheck.forEach(el => {
    const cs = getComputedStyle(el);
    const fvp = cs.fontVariantPosition || cs['font-variant-position'] || 'normal';

    if (fvp === 'sub' || fvp === 'super') {
      const fontSizePx = parseFloat(cs.fontSize);
      const effectivePx = fontSizePx * SUB_SUPER_SCALE;

      findings.push({
        severity: effectivePx < MIN_READABLE_PX ? 'CRITICAL' : 'HIGH',
        property: 'font-variant-position',
        el,
        msg: `font-variant-position: ${fvp} — computed font-size: ${fontSizePx}px but effective rendered size: ~${effectivePx.toFixed(1)}px ` +
             (effectivePx < MIN_READABLE_PX ? `(below ${MIN_READABLE_PX}px minimum)` : '')
      });

      // Additional check: text content contains consent keywords
      const text = el.textContent || '';
      const keywords = ['delete', 'permanent', 'access', 'share', 'grant', 'irreversible'];
      const matchedKeywords = keywords.filter(k => text.toLowerCase().includes(k));
      if (matchedKeywords.length > 0) {
        findings[findings.length - 1].severity = 'CRITICAL';
        findings[findings.length - 1].msg += ` — contains consent keywords: ${matchedKeywords.join(', ')}`;
      }
    }
  });

  return findings;
}

Related SkillAudit coverage

SkillAudit detection: SkillAudit checks font-variant-position on all text nodes within consent dialogs, computes the effective rendered size by applying the synthesis scale factor to the nominal font-size, and flags cases where the effective size falls below accessibility thresholds — catching the computed-value deception that bypasses font-size-only scanners.

Audit your MCP server's typographic variant usage near consent text before publishing. Run a free SkillAudit scan — results in 60 seconds.