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
| ID | Technique | Declared fontSize | Effective height | fontVariantCaps | Severity |
|---|---|---|---|---|---|
| SA-CSS-FVCP-001 | all-small-caps at 13px — lowercase as scaled-down caps | 13px | ~9.1px | 'all-small-caps' | High |
| SA-CSS-FVCP-002 | petite-caps at 14px — even smaller than small-caps | 14px | ~8.4px | 'petite-caps' | High |
| SA-CSS-FVCP-003 | unicase at 12px — inconsistent glyph sizes, key words sub-threshold | 12px | mixed | 'unicase' | Medium |
| SA-CSS-FVCP-004 | JS mousedown sets all-small-caps at install click | 14px | ~9.8px (after) | 'all-small-caps' (after) | High |
Consolidated findings
See also: CSS font-optical-sizing attacks | CSS font-variant-numeric attacks | CSS stacking context consent bypass | SkillAudit — free MCP server audit