MCP server CSS font-size zero consent security
CSS font-size:0 collapses all glyph metrics to zero pixels: characters are present in the DOM, selected by text-search APIs, accessible to textContent readers, and occupy space via line-height — but render zero visible pixels on screen. getComputedStyle(el).fontSize returns '0px', making this the most directly detectable font-size attack. Sub-pixel variants (0.01px, 0.5px) are technically non-zero but render no visible glyphs on standard 96 dpi displays, requiring threshold-based detection. Four SA-CSS-FSZ patterns document the complete font-size attack surface for consent text.
Font-size attacks exploit a straightforward property of CSS typography: when a font-size is zero or effectively zero, glyph bitmaps have zero pixel dimensions. The shaping engine produces zero-advance characters — each glyph occupies a zero-width, zero-height rectangle in the line box. The text node still exists in the DOM, still participates in text search, still passes textContent-based visibility checks, and may even occupy vertical space if line-height is set independently. Only the rendered pixels are absent.
This makes font-size:0 a structurally elegant attack: it invalidates the most common audit shortcut — “is the text content non-empty?” — while remaining trivially detectable by any check that reads the computed font-size value. The sub-pixel variants trade this detectability for increased stealth: a font-size of 0.01px is technically non-zero and may pass naive threshold checks set at exactly 0, while still rendering no visible output at any standard display resolution.
SA-CSS-FSZ-001: font-size:0 — zero font-size collapse
Setting font-size:0 collapses all glyph metrics to zero. Each character produces a zero-width advance and a zero-height glyph. The text node remains accessible to textContent, innerText, innerHTML, and all accessibility APIs that read text content from the DOM tree. However, no pixels are rendered. The element box model behavior depends on whether line-height is also zero: if line-height is normal, it is computed as a multiple of font-size and also collapses to zero; if line-height is set to a fixed pixel value, the element retains vertical height while text pixels are zero.
/* Attack: zero font-size on consent text */
.consent-text {
font-size: 0;
/* All glyphs: zero-pixel advance and height.
Text in DOM: accessible to textContent, aria, text-search.
Visual output: zero pixels rendered. */
}
/* Variation: zero font-size with fixed line-height */
.consent-text {
font-size: 0;
line-height: 20px;
/* Element has 20px height (line-height preserved).
getBoundingClientRect().height = 20px (looks normal).
No glyph pixels rendered despite positive element height. */
}
/* Why text-content checks miss this:
el.textContent → "By clicking Allow, you consent to..."
el.innerText → "By clicking Allow, you consent to..."
→ Text is readable to scripts!
→ DOM-based auditors report consent present.
Correct detection:
const fs = parseFloat(getComputedStyle(el).fontSize);
// fs === 0 → CRITICAL (SA-CSS-FSZ-001)
Accessibility tree check:
// AOM: accessible name is still derived from text node
// Screen readers read content → users without visual rendering
// receive text, but sighted users see nothing. */
Text is readable to audit scripts, invisible to users: el.textContent and el.innerText both return the full consent string for a font-size:0 element. DOM-based auditors that check for text presence pass. Only computed font-size evaluation catches this attack. Always read getComputedStyle(el).fontSize and threshold at >1px.
font-size: 0: zero glyph metrics; DOM text intact; textContent readable; zero rendered pixels. Detection: parseFloat(getComputedStyle(el).fontSize) === 0 → CRITICAL.SA-CSS-FSZ-002: font-size:0.01px — sub-pixel threshold attack
A font-size of 0.01px is technically non-zero. CSS parsers accept it as a valid length value. getComputedStyle(el).fontSize returns '0.01px' or a browser-normalized value close to zero. On a standard 96 dpi display (CSS reference pixel = 1/96 inch), one CSS pixel = approximately 0.264 mm. A glyph rendered at 0.01 CSS pixels has physical dimensions of 0.00264 mm — far below the threshold of human visual acuity (~0.1mm). No pixel antialiasing occurs; the glyph vanishes at sub-pixel scale. This pattern targets auditors that check fontSize !== '0px' exactly, without applying a minimum-size threshold.
/* Attack: sub-pixel font-size — technically non-zero */
.consent-text {
font-size: 0.01px;
/* getComputedStyle returns '0.01px' (or browser min-size floor).
Browsers: Chrome/Firefox/Safari all have minimum font-size
in browser settings (default: 0px, user-adjustable).
At page font-size 0.01px, no pixel is rendered for any glyph.
Some browsers may floor to 0px in computed styles. */
}
/* Other sub-pixel variants: */
.consent-text { font-size: 0.5px; }
/* Half a CSS pixel — 0.132mm — below visual threshold on all screens */
.consent-text { font-size: 1px; }
/* 1 CSS pixel = 0.264mm at 96dpi: technically WCAG minimum font-size
checks may not apply below 14px; text unreadable at 1px cap-height */
/* Detection threshold approach:
const fsPx = parseFloat(getComputedStyle(el).fontSize);
if (fsPx < 2) → CRITICAL (SA-CSS-FSZ-002: sub-pixel, invisible)
if (fsPx < 8) → HIGH (below minimum readable font size)
if (fsPx < 11) → MEDIUM (below typical body copy minimum)
// 11px is the WCAG minimum implied size for UI text readability */
Exact-zero checks are insufficient: Auditors that check getComputedStyle(el).fontSize === '0px' miss sub-pixel attacks where the value is '0.01px' or '0.5px'. Use a numeric threshold: parseFloat(fontSize) < 2 flags both exact-zero and sub-pixel cases as CRITICAL.
font-size: 0.01px (sub-pixel): technically non-zero; renders no visible pixels; evades exact-zero check. Detection: parseFloat(getComputedStyle(el).fontSize) < 2 → CRITICAL.SA-CSS-FSZ-003: font-size:0 inherited via ancestor — relative unit bypass
When an ancestor element has font-size:0 and the consent element uses a relative unit (em, rem against a zeroed root, or %), the resolved font-size on the consent element is 0px even though the CSS rule on the element itself contains a non-zero value like font-size: 1em or font-size: 100%. Static CSS analysis of the consent element’s own rules reports a plausible-looking font-size declaration; only the computed value reveals the true size. This pattern specifically defeats auditors that inspect CSS rules (stylesheets, inline styles) rather than reading getComputedStyle().
/* Attack: ancestor sets font-size:0; consent element inherits via 1em */
/* In the ancestor wrapper */
.mcp-consent-wrapper {
font-size: 0;
/* Sets the EM context for all descendants to 0 */
}
/* Consent element looks normal in its own rules */
.consent-text {
font-size: 1em;
/* 1em × 0px (inherited) = 0px resolved font-size.
The rule 'font-size: 1em' looks like valid, normal text sizing.
Static rule analysis reports font-size: 1em — appears normal.
Only getComputedStyle reveals the 0px resolved value. */
}
/* Variant: percentage inheritance */
.consent-text {
font-size: 100%;
/* 100% of 0px = 0px */
}
/* Variant: rem against zeroed :root */
:root { font-size: 0; }
.consent-text { font-size: 1rem; }
/* 1rem × 0px root = 0px */
/* Why static analysis misses this:
Rule on .consent-text: font-size: 1em → looks non-zero
Only computed value check reveals the attack:
getComputedStyle(consentEl).fontSize → '0px' → CRITICAL */
Static stylesheet analysis is insufficient: An auditor that reads CSS rule declarations directly will see font-size: 1em on the consent element and report normal sizing. Always use getComputedStyle() to read the fully resolved font-size, which accounts for all ancestor cascade and inheritance.
font-size: 1em resolves to 0px via ancestor font-size:0: consent rule appears normal; computed value is zero. Detection: getComputedStyle(el).fontSize === '0px' → CRITICAL.SA-CSS-FSZ-004: font-size:1px — above-zero WCAG bypass
A font-size of 1px is unambiguously above zero. getComputedStyle(el).fontSize returns '1px'. WCAG 2.2 Success Criterion 1.4.3 specifies a contrast ratio threshold for “large text” at ≥18pt (24px) or ≥14pt bold (approximately 18.67px); for text below these thresholds it requires 4.5:1 contrast. The criterion does not impose a minimum font-size floor at 1px. An auditor that only checks WCAG contrast passes a 1px black-on-white consent text: contrast is 21:1, requirement is 4.5:1 — it passes on contrast while being physically unreadable. At 1 CSS pixel = 0.264mm at 96 dpi, the tallest glyph cap-height is approximately 0.2mm — far below the human visual acuity threshold for text recognition.
/* Attack: 1px font-size passes WCAG contrast checks */
.consent-text {
font-size: 1px;
color: #000000; /* black text */
background: #ffffff; /* white background */
/* WCAG contrast: #000 on #fff = 21:1 → PASSES 4.5:1 requirement */
/* Computed fontSize: '1px' → above-zero, non-degenerate */
/* But: 1px glyph cap-height ≈ 0.7px on 96dpi → sub-pixel, invisible */
}
/* Why WCAG contrast check alone is insufficient:
Contrast ratio 21:1 → PASSES (required: 4.5:1)
fontSize: 1px → non-zero, not flagged by zero-check
textContent: "By clicking Allow..." → text present
Missing check: minimum readable font-size threshold:
const fsPx = parseFloat(getComputedStyle(el).fontSize);
if (fsPx > 0 && fsPx < 8) → HIGH (effectively unreadable)
if (fsPx >= 8 && fsPx < 11) → MEDIUM (below normal body minimum)
// 8px is the practical floor for any text recognition at 96dpi */
/* Compound variant: 1px with font-weight:100 */
.consent-text {
font-size: 1px;
font-weight: 100; /* ultra-thin stroke: already zero-width at 1px */
/* Sub-pixel stroke + sub-pixel glyph = definitely invisible */
}
WCAG contrast does not imply readability at 1px: WCAG 1.4.3 contrast requirements are designed for normally-sized text. A 1px glyph is physically smaller than a single display pixel on most screens. Auditors must enforce a minimum font-size threshold independent of contrast checks. Flag any consent text with computed fontSize < 8px as HIGH severity.
font-size: 1px: technically non-zero; WCAG contrast passes (21:1); physically unreadable on all displays. Detection: parseFloat(getComputedStyle(el).fontSize) < 8 → HIGH.Summary of SA-CSS-FSZ findings
| ID | Severity | font-size value | Attack mechanism | Detection |
|---|---|---|---|---|
| SA-CSS-FSZ-001 | CRITICAL | font-size: 0 | Zero glyph metrics; textContent intact; zero pixels | getComputedStyle.fontSize === '0px' |
| SA-CSS-FSZ-002 | CRITICAL | font-size: 0.01px | Sub-pixel value; no visible glyph at 96dpi; evades exact-zero check | parseFloat(fontSize) < 2 → CRITICAL |
| SA-CSS-FSZ-003 | CRITICAL | font-size: 1em (resolves to 0px) | Relative unit inherits zero from ancestor; rule appears normal | getComputedStyle(el).fontSize === '0px' |
| SA-CSS-FSZ-004 | HIGH | font-size: 1px | Above-zero; WCAG contrast passes; physically unreadable at 96dpi | parseFloat(fontSize) < 8 → HIGH |