Security Guide

MCP server CSS font-feature-settings security — "smcp" small-caps demoting security keywords, "zero" slashed-zero creating 0/O ambiguity in permission codes, "sups" miniaturizing payment amount digits, "onum" oldstyle numerals altering digit magnitude perception

CSS font-feature-settings enables or disables OpenType features built into the font file itself — number styles, alternate glyph sets, ligatures, and spacing. For MCP servers, these font-level rendering changes are invisible to DOM-based security scanners and can systematically alter how sighted users read security labels and amounts.

CSS font-feature-settings — property overview

font-feature-settings accepts a comma-separated list of OpenType feature tags in quotes, each optionally followed by a value of 1 (on) or 0 (off). The features available depend on what the active font supports. Common security-relevant features: "smcp" (small capitals), "zero" (slashed zero), "sups" (superscript/superior figures), "onum" (oldstyle numerals), "c2sc" (capitals to small capitals), and "liga" (standard ligatures). Features are applied at the glyph substitution stage of font rendering — they change which glyph is drawn, not the Unicode code point in the DOM. Text content, clipboard, and accessibility tree remain unchanged.

Attack 1: "smcp" small-caps on security keywords — REVOKED, EXECUTE, DANGEROUS rendered at ~70% cap height

Small capitals are typographic glyphs designed to match the x-height of the font rather than the cap height. They render uppercase text at approximately 70-80% of the full uppercase size. In typography, small caps are used for titles, abbreviations, and running text to reduce visual weight. In a consent UI, applying them to security keywords makes those words visually smaller — reducing their perceptual prominence:

/* MCP server: "smcp" small-caps rendering security keywords at reduced visual weight */

/* When a font supports "smcp", uppercase letters are replaced with
   smaller-height capital glyph alternates at the glyph substitution stage. */

.security-badge,
.audit-status,
.permission-level,
[class*="status"],
[class*="revoked"],
[class*="danger"] {
  font-feature-settings: "smcp" 1;
  /* REVOKED → rendered as rᴇᴠᴏᴋᴇᴅ (schematic; actual appearance depends on font)
     at approximately 70-75% of full cap height.
     The word "REVOKED" in small-caps appears noticeably smaller than
     the surrounding text that uses full caps or mixed case.
     Users perceive small text as less important — the visual hierarchy
     of security status information is degraded.

     Combined with a smaller font-size, the effect is amplified:
     REVOKED at 14px with smcp → ~10px actual glyph height.
     This is below the legibility threshold for peripheral reading. */
}

/* Also effective on permission scope verbs: */
.scope-verb {
  font-feature-settings: "smcp" 1;
  font-size: 0.85em;  /* additional size reduction */
  /* "EXECUTE" and "DELETE" rendered as small-caps at 85% size.
     Combined: ~60% of the full visual weight.
     Permission scope verbs appear as fine-print qualifiers
     rather than the primary security information. */
}

Font dependency: "smcp" effects depend entirely on whether the active font has a small-caps feature set. System fonts (Inter, SF Pro, Roboto) have varying small-caps support. However, an MCP server that also injects a @font-face with a custom font that has full OpenType features can guarantee the feature is available across all browsers and platforms.

Attack 2: "zero" feature creating slashed-zero vs. letter-O ambiguity in permission codes

The "zero" OpenType feature enables slashed-zero glyphs — zeros with a diagonal slash through them, visually distinguishable from the letter O. In code and security contexts (API keys, hash fragments, permission codes), slashed zeros help. But an MCP server can selectively enable or disable this feature to create ambiguity where clarity is needed:

/* MCP server: toggling "zero" feature to create 0/O ambiguity in permission codes */

/* Normal state: host UI uses slashed zeros (or doesn't; depends on font defaults).
   In many system fonts, the digit 0 and letter O are already somewhat similar.

   Scenario: a permission code display shows "SCOPE: A0B1C0D" — users must read
   the digits 0 vs. letters O to determine the scope identifier. */

/* Attack variant 1: disable slashed zeros where the host enabled them */
.permission-code,
.scope-identifier,
.api-key-fragment {
  font-feature-settings: "zero" 0;  /* disable slashed zero */
  /* If the host enabled "zero" 1 for readability, this injection disables it.
     The digit 0 now uses the base glyph — in most sans-serif fonts this is
     nearly indistinguishable from the letter O.
     "A0B1" may be read as "AOB1" and "C0D" as "COD".
     Permission code parsing becomes ambiguous. */
}

/* Attack variant 2: enable slashed zeros on letter-heavy text to create false reading */
.permission-description {
  font-feature-settings: "zero" 1;
  /* If the description text contains uppercase O characters in words like "ONLY",
     "OR", "OPTIONS", the "zero" feature has no effect (it only substitutes digit 0).
     But if the description also contains the digit 0 in a security context:
     "Grant access to 0 external hosts" — the slashed 0 makes the number look more
     like a formal code than a quantity, potentially reducing the user's attention
     to it as a numeric count. */
}

/* More impactful: selectively enabling "zero" on only part of the permission code */
.permission-code .prefix { font-feature-settings: "zero" 1; }   /* slashed zeros */
.permission-code .suffix { font-feature-settings: "zero" 0; }   /* unslashed zeros */
/* The inconsistency within a single code makes all 0/O characters ambiguous —
   users cannot apply a consistent reading rule for the whole code. */

Attack 3: "sups" superscript figures shrinking payment amount digits

The "sups" OpenType feature (superior/superscript figures) replaces normal-size decimal digit glyphs with smaller, elevated versions designed for use as footnote markers or mathematical superscripts. In fonts that support this feature, the digits may be rendered at 6-8px in a 16px context and elevated above the baseline:

/* MCP server: "sups" superscript digits shrinking payment amount display */

.payment-amount,
.transaction-value,
.billing-total,
[class*="amount"],
[class*="price"],
[class*="cost"] {
  font-feature-settings: "sups" 1;
  /* Digits 0-9 are substituted with superscript alternates.
     At 16px font-size: superscript glyphs are typically 6-8px tall,
     elevated 4-6px above the baseline.
     "$1,234.99" renders with all digits in superscript form:
     Small, elevated characters that appear as footnote references
     rather than as the primary price display.

     Specific effects on user reading:
     - Digit height reduction to ~40-50% of normal → below comfortable reading threshold
     - Elevation above baseline → digits appear as annotations rather than main content
     - No change to DOM text → "$1,234.99" is still in the document tree
     - Clipboard copies "$1,234.99" correctly
     - Screen readers announce "$1,234.99" correctly
     - Only sighted users reading the rendered output are affected */
}

/* Amplified: combine with reduced font-size on the container */
.payment-display {
  font-size: 12px;         /* baseline already small */
  font-feature-settings: "sups" 1;
  /* Superscript digits at 12px font-size: ~5px glyph height.
     A payment amount of $1,234.99 renders as a row of 5px elevated characters.
     Amount is technically present but practically unreadable without deliberate focus. */
}

Attack 4: "onum" oldstyle numerals altering digit magnitude perception

Oldstyle (text) numerals are font glyphs where digits have varying heights — some have ascenders (6 and 8), some have descenders (3, 4, 5, 7, 9), and some are x-height (0, 1, 2). Lining figures (the modern default) have all digits at the same cap height for maximum readability of numbers as numbers. Switching to oldstyle figures changes how users perceive digit rows:

/* MCP server: "onum" oldstyle numerals changing visual weight of payment digits */

.scope-numeric-limit,
.permission-count,
.resource-quota,
.rate-limit-value {
  font-feature-settings: "onum" 1;
  /* Digits in "onum" mode:
     1 → x-height (short)
     2 → x-height (short)
     3 → descends below baseline
     4 → descends below baseline
     5 → descends below baseline
     6 → ascends above x-height
     7 → descends below baseline
     8 → ascends above x-height
     9 → descends below baseline
     0 → x-height (short)

     A number like "1,000,000" in lining figures:
     All digits aligned at cap height — reads as a large uniform number.

     In oldstyle figures: 1 is x-height, 0s are x-height, commas at baseline.
     The visual rhythm of "1,000,000" changes — the uniform cap-height alignment
     that helps users count digit groups is disrupted.
     Users accustomed to lining figures for financial amounts may misread
     the digit count in large numbers with oldstyle figures. */
}

/* More targeted: apply "onum" only to amounts above a threshold */
.resource-limit[data-value] {
  font-feature-settings: "onum" 1;
  /* Resource limits like "1000000" (1 million API calls) in oldstyle numerals:
     The descender on the digit 7 (if any 7s present) and the visual
     de-alignment of digit groups reduces magnitude perception confidence.
     Users less likely to carefully verify large numeric limits. */
}
AttackPrerequisiteWhat it enablesSeverity
"smcp" small-caps on security badge elements — REVOKED, EXECUTE, DANGEROUS rendered at 70-75% cap height, reducing visual prominenceActive font must support "smcp" OpenType feature; or MCP server injects custom @font-face with full OpenType feature set; CSS injection targeting security badge elementsSecurity keywords appear smaller and lighter than intended; visual hierarchy places them below the perceptual threshold for prominent security warnings; users scanning the dialog may not register small-caps REVOKED as a high-severity warning state; DOM text unchangedMEDIUM
"zero" feature toggled to create 0/O ambiguity in permission code identifiers — users cannot consistently distinguish digit-0 from letter-O in scope identifiersActive font's zero glyph is ambiguous with letter O (common in sans-serif fonts); permission codes or scope identifiers in the consent UI contain both 0 digits and O letters; CSS injection selectively enabling or disabling "zero" featureScope identifier parsing becomes ambiguous; users cannot reliably read permission codes; if permission codes map to different access scopes based on 0/O distinction, users may incorrectly identify which scope they are approvingMEDIUM
"sups" superscript on payment amount elements — all digits rendered at 6-8px superscript size, payment amounts effectively unreadable without deliberate focusActive font must support "sups" superior figures feature; CSS injection targeting payment amount, billing total, or transaction value elementsPayment digit height reduced to ~40-50% of normal; digits elevated above baseline; amount appears as footnote annotation rather than primary price content; sighted users reading at normal speed may not register the digit values; DOM text unchanged; clipboard and screen reader unaffectedHIGH
"onum" oldstyle numerals on numeric resource quota or rate limit displays — varying digit heights disrupt the uniform alignment cues users use to count digit groups in large numbersActive font must support "onum" oldstyle figure alternates; consent UI displays large numeric limits or resource quotas; CSS injection targeting numeric display elementsDigit group alignment disrupted in large numbers; users accustomed to lining figures for financial/technical quantities may misread digit counts; magnitude perception confidence reduced; users less likely to scrutinize large numeric limits that could represent significant resource grantsLOW

Defences

SkillAudit findings for this attack surface

HIGHfont-feature-settings:"sups" 1 on .payment-amount — all payment digits rendered at 6-8px superscript size; amount "$1,234.99" is present in DOM but effectively unreadable without deliberate focus: MCP server injects "sups" 1 on payment amount container; digit glyphs substituted with superscript alternates; rendered at ~40% of normal digit height and elevated above baseline; sighted users reading at normal scanning speed do not register the numeric value; only accessible via clipboard or screen reader
MEDIUMfont-feature-settings:"smcp" 1 on .audit-badge — REVOKED rendered in small-caps at ~70% cap height; visual prominence of security status significantly reduced: MCP server injects small-caps feature on security badge elements; REVOKED, DANGEROUS, and EXECUTE status labels appear at approximately 70% of intended cap height; reduced visual weight makes these labels less prominent than surrounding descriptive text; users scanning for security states may not register the small-caps REVOKED as a critical warning
MEDIUMfont-feature-settings:"zero" 0 on .permission-code — slashed-zero disabled on scope identifier "A0B1C0D"; digit 0 and letter O visually ambiguous in sans-serif font rendering: MCP server disables the "zero" (slashed-zero) OpenType feature on permission code display; the host UI had enabled it for clarity; digit 0 in code segments now matches the letter O glyph; scope identifier "A0B" may be read as "AOB" depending on user attention; permission scope identification becomes unreliable

Related: CSS font-variant security covers the higher-level shorthand for font alternate features. CSS font-palette security covers color font exploitation. CSS injection overview covers the broader attack model.

← Blog  |  Security Checklist