Security reference · CSS injection · Font rendering attacks · Legibility manipulation
MCP server CSS text-rendering security
CSS text-rendering is a hint to the browser's text rendering engine that trades off legibility against speed and geometric precision. The non-standard -webkit-font-smoothing and -moz-osx-font-smoothing properties control subpixel antialiasing on macOS and iOS. MCP servers exploit these properties — combined with sub-legibility font sizes — to make consent disclosure text visually unreadable: letters merge, blur, or alias into pixel noise. The text is technically present in the DOM and has a non-zero rendered width. It passes every display and visibility check. But no human eye can read it.
text-rendering attack surface overview
| Property / value | Intended use | Attack use |
|---|---|---|
text-rendering: optimizeSpeed | Sacrifice legibility for render performance in SVG text or large amounts of text | At 8-10px, disables hinting and antialiasing — letterforms alias to chunky pixel artifacts that are not individually identifiable |
text-rendering: geometricPrecision | Render text at geometric precision rather than hinted pixel alignment — intended for print and zoom scenarios | At 2-5px font-size, geometric precision renders all glyphs as solid degenerate shapes — no letter distinction possible |
-webkit-font-smoothing: none | Not intended for use; the "none" value disables all macOS/iOS font antialiasing | On macOS/iOS at 9-12px, removes subpixel rendering; letterforms show pixel staircase artifacts making them harder to read individually |
-webkit-font-smoothing: none + font-weight: 100 | Combination not intended | Hairline weight with no antialiasing: strokes become single-pixel lines with staircase artifacts; curve differentiation between similar letters (c/e/o, m/n/rn) collapses |
Legibility attacks occupy a different threat tier than display-hiding attacks: display:none and visibility:hidden remove consent from both visual and semantic rendering. Legibility attacks leave consent in both — but make it unreadable to humans while it remains present in the DOM, in the accessibility tree, and passing all machine checks. They exploit the gap between "machine-readable" and "human-legible." WCAG 2.1 AA requires 4.5:1 color contrast but does not define a legibility standard for font-rendering hints.
Attack 1: text-rendering: optimizeSpeed at sub-legibility font size
text-rendering: optimizeSpeed instructs the browser to skip font hinting (the process of aligning glyph outlines to the pixel grid for maximum clarity) and potentially skip antialiasing. At normal reading sizes (14-18px), browsers may ignore this hint or apply it minimally. At small sizes (7-10px), disabling hinting means letterforms are rendered without pixel-grid snapping — on low-DPI displays, this creates blurry or jagged glyphs where individual letters are no longer clearly differentiated:
/* Malicious CSS — SA-CSS-TXTREND-001 */
/* Consent text rendered with speed-optimized rendering at sub-legibility size */
.mcp-consent-disclosure {
font-size: 8px; /* below normal legibility threshold (~11px) */
line-height: 1.2;
color: #6b7280; /* gray — acceptable contrast at larger sizes */
text-rendering: optimizeSpeed; /* disables font hinting and antialiasing hints */
/* At 8px with no hinting:
- A 'a' may render identically to a 'o' at pixel level
- 'm' and 'rn' sequence are visually identical
- Word boundaries collapse at very small sizes
- On 1x DPI displays (still common for external monitors): severe aliasing
- On 2x displays (Retina): impact reduced, but still noticeable
- The consent text renders as a paragraph-shaped block of noise
*/
}
/* Compound attack: optimizeSpeed + letter-spacing adjustment */
.mcp-consent-disclosure {
font-size: 9px;
text-rendering: optimizeSpeed;
letter-spacing: -0.1em; /* negative letter spacing compresses letters together */
/* at 9px with no hinting, letters merge into a solid bar */
}
/* Why this evades consent-visibility detection:
- computed font-size: 8px — above 0 (not collapsed)
- computed color: #6b7280 — non-transparent, technically visible
- computed display: block — not hidden
- computed visibility: visible — not hidden
- Accessibility tree contains text content — screen readers read it
- Color contrast ratio (#6b7280 on #ffffff): ~4.6:1 — passes WCAG AA
- text-rendering is not a consent-hiding indicator in most scanners
- This is a LEGIBILITY attack, not a VISIBILITY attack
/* Detection: check for consent text with very small font-size and optimizeSpeed rendering */
function detectTextRenderingSpeedAttack() {
const findings = [];
const candidates = document.querySelectorAll('*');
for (const el of candidates) {
if (!/consent|disclosure|terms|privacy|by installing|by clicking/i.test(el.textContent || '')) continue;
if (el.children.length > 5) continue;
const style = getComputedStyle(el);
const fontSize = parseFloat(style.fontSize);
const textRendering = style.textRendering;
if (textRendering === 'optimizeSpeed' && fontSize < 12) {
findings.push({ id: 'SA-CSS-TXTREND-001', severity: 'high',
message: `Consent element has text-rendering:optimizeSpeed at font-size:${fontSize}px — legibility attack: antialiasing disabled at sub-threshold size. Letter differentiation may be severely degraded on 1x displays.` });
}
/* Also flag any consent text with font-size under 8px regardless of text-rendering */
if (fontSize < 8 && style.display !== 'none') {
findings.push({ id: 'SA-CSS-TXTREND-001', severity: 'critical',
message: `Consent text has font-size:${fontSize}px — below minimum legibility threshold (8px). Text is technically present but visually unreadable.` });
}
}
return findings;
}
Screen readers are not affected by legibility attacks: CSS text-rendering, -webkit-font-smoothing, and font-size below the legibility threshold do not prevent screen readers from announcing the consent text. Accessibility tree tools and screen reader audits will report the consent as present and accessible. The attack exclusively targets sighted users — a deliberate exploitation of the gap between machine accessibility and visual legibility standards.
Attack 2: -webkit-font-smoothing: none on macOS/iOS consent text
On Apple platforms (macOS, iOS, iPadOS), -webkit-font-smoothing: none disables the macOS Core Text subpixel rendering pipeline. Normally, macOS uses subpixel antialiasing on non-Retina displays and grayscale antialiasing on Retina — both of which smooth curved letterforms and make text legible at small sizes. Disabling this on 10-12px consent text with a thin font weight creates visible staircase artifacts on diagonal and curved strokes that make letters harder to distinguish:
/* Malicious CSS — SA-CSS-TXTREND-002 */
/* Disables macOS/iOS font smoothing on consent text */
.mcp-consent-disclosure {
font-size: 10px;
font-weight: 300; /* light weight — thinner strokes */
-webkit-font-smoothing: none; /* disables antialiasing on macOS/iOS */
-moz-osx-font-smoothing: unset; /* removes grayscale smoothing fallback */
/* Platform-specific effects:
macOS non-Retina (1x): severe staircase on curved strokes; letters blur
macOS Retina (2x): less severe; still affects hairline-weight characters
iOS (always Retina): less severe; but 10px + light weight is borderline
Windows/Android: -webkit-font-smoothing has no effect; font renders normally
→ attack specifically targets Apple platform users
→ Security auditors on Windows/Linux may not see the attack
*/
}
/* Why Apple-platform-specific attacks are dangerous:
- Security auditors on Linux/Windows see normal rendered text
- macOS and iOS users (high-income developer demographic matching ICP)
see degraded rendering
- Tool-based checks run on CI (Linux) miss the macOS-only rendering issue
- Screenshot-based audits would need to be taken on macOS with 1x display
/* Compound: font-weight:100 + none smoothing */
.mcp-consent-disclosure {
font-size: 10px;
font-weight: 100; /* hairline weight: strokes < 1px at this size */
-webkit-font-smoothing: none; /* no smoothing: hairline strokes alias to pixel dots */
/* At 10px/100 weight with no smoothing:
- Individual strokes are sub-pixel
- With aliasing, sub-pixel strokes appear as intermittent dots
- Connected letters (like 'consent' or 'privacy') appear as dot noise
*/
}
/* Detection: check for -webkit-font-smoothing:none on consent elements */
function detectFontSmoothingNoneAttack() {
const findings = [];
const candidates = document.querySelectorAll('*');
for (const el of candidates) {
if (!/consent|disclosure|terms|privacy|by installing|by clicking/i.test(el.textContent || '')) continue;
if (el.children.length > 5) continue;
const style = getComputedStyle(el);
const smoothing = style.webkitFontSmoothing || style.getPropertyValue('-webkit-font-smoothing');
const fontSize = parseFloat(style.fontSize);
const fontWeight = parseInt(style.fontWeight, 10);
if ((smoothing === 'none' || smoothing === 'auto') && fontSize < 14 && fontWeight < 400) {
findings.push({ id: 'SA-CSS-TXTREND-002', severity: 'high',
message: `Consent element has -webkit-font-smoothing:${smoothing} at font-size:${fontSize}px / font-weight:${fontWeight} — macOS/iOS legibility attack. Curved letterforms alias to pixel artifacts at this combination.` });
}
/* Also scan stylesheets for explicit -webkit-font-smoothing:none rules on consent selectors */
for (const sheet of document.styleSheets) {
try {
for (const rule of sheet.cssRules) {
if (!rule.selectorText) continue;
const css = rule.cssText;
if (/-webkit-font-smoothing\s*:\s*none/.test(css)) {
if (/consent|disclosure|terms|privacy/.test(rule.selectorText)) {
findings.push({ id: 'SA-CSS-TXTREND-002', severity: 'critical',
message: `CSS rule "${rule.selectorText}" explicitly sets -webkit-font-smoothing:none — Apple platform legibility attack on consent-labeled element` });
}
}
}
} catch (_) {}
}
}
return findings;
}
Attack 3: text-rendering: geometricPrecision at degenerate font sizes
text-rendering: geometricPrecision renders glyphs at exact geometric coordinates without hinting to the pixel grid. This is intended for high-DPI or print contexts where sub-pixel hinting is not needed. At extremely small font sizes (1-4px), geometric precision means all letterform outlines collapse to near-zero height. On a 1x display, a 2px tall letter is one or two pixels — indistinguishable from a horizontal line. At 3-4px, some letterforms are visible but cannot be differentiated from each other:
/* Malicious CSS — SA-CSS-TXTREND-003 */
/* geometricPrecision at degenerate size — all glyphs collapse to pixel lines */
.mcp-consent-disclosure {
font-size: 3px; /* degenerate — below any legibility threshold */
text-rendering: geometricPrecision; /* precise geometry at 3px = solid rectangles */
color: #374151; /* visible color — not zero-opacity */
line-height: 1;
}
/* At 3px geometric precision on a 96dpi display:
- The em square is 3px tall
- Cap-height ~65% of em = ~2px
- x-height ~45% of em = ~1.35px → renders as 1 or 2 pixels depending on hinting
- With no hinting (geometricPrecision): every letter renders as a 1-2px blob
- No differentiation between 'a', 'b', 'c', 'o', 'p', 'q', etc.
- Text appears as a horizontal bar of gray pixels — any meaning is lost
- On 2x Retina: 6px effective pixels — still at edge of legibility
/* Obfuscation: use em units relative to a zero-value parent */
html { font-size: 2px; } /* or set via JS: document.documentElement.style.fontSize = '2px' */
.mcp-consent-disclosure {
font-size: 1em; /* inherits 2px — looks like normal relative sizing */
text-rendering: geometricPrecision;
}
/* Why setting html font-size is a red flag: */
/* Any MCP code that sets html { font-size: Xpx } where X < 8 is suspicious */
/* The standard html font-size is 16px; reducing it affects all rem-relative sizes */
/* A low html font-size also collapses consent text that uses em units */
/* Detection: check for extremely small computed font-size on consent elements */
function detectGeometricPrecisionTinyFontAttack() {
const findings = [];
const candidates = document.querySelectorAll('*');
for (const el of candidates) {
if (!/consent|disclosure|terms|privacy|by installing|by clicking/i.test(el.textContent || '')) continue;
if (el.children.length > 5) continue;
const style = getComputedStyle(el);
const fontSize = parseFloat(style.fontSize);
const textRendering = style.textRendering;
if (fontSize < 6) {
findings.push({ id: 'SA-CSS-TXTREND-003', severity: 'critical',
message: `Consent element font-size:${fontSize}px — below minimum legibility threshold. text-rendering:${textRendering}. At this size, all letterforms collapse to pixel blobs regardless of rendering mode.` });
}
if (textRendering === 'geometricPrecision' && fontSize < 10) {
findings.push({ id: 'SA-CSS-TXTREND-003', severity: 'high',
message: `Consent element text-rendering:geometricPrecision at font-size:${fontSize}px — geometric precision at small sizes degrades letterform differentiation below legibility threshold.` });
}
/* Check for abnormally small root font-size */
const htmlFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
if (htmlFontSize < 8) {
findings.push({ id: 'SA-CSS-TXTREND-003', severity: 'high',
message: `Root html font-size is ${htmlFontSize}px (normal: 16px) — em/rem-relative consent text may collapse to sub-legibility size due to reduced root font-size` });
}
}
return findings;
}
Attack 4: combined font-rendering legibility stack
The most effective legibility attack chains multiple properties: sub-threshold font size, disabled smoothing, hairline font weight, negative letter spacing, and a low-contrast color that passes contrast-ratio checks at normal sizes but is harder to read at small sizes. No single property triggers a consent-hiding detection rule; the combination degrades legibility below practical readability while satisfying every individual automated check:
/* Malicious CSS — SA-CSS-TXTREND-004 */
/* Combined legibility attack stack — each property alone passes typical checks */
.mcp-consent-disclosure {
/* Size: at the very edge of legibility */
font-size: 9px;
/* Weight: hairline strokes */
font-weight: 100;
/* Rendering: no pixel-grid hinting, no antialiasing */
text-rendering: optimizeSpeed;
-webkit-font-smoothing: none;
/* Spacing: compress letters together */
letter-spacing: -0.05em; /* slight compression — not extreme enough to flag alone */
/* Color: passes 4.5:1 contrast at 14px; at 9px the contrast appears lower perceptually */
color: #9ca3af; /* gray-400 — 2.5:1 on white; borderline WCAG AA small text */
/* Line height: tight — consent paragraphs visually merge into a bar */
line-height: 1.1;
/* Result at 9px/weight 100/no-smooth/tight:
- Strokes are sub-pixel on 1x displays
- No antialiasing: strokes show pixel staircase
- Negative letter-spacing: letters touch or overlap at pixel level
- Low contrast: individual letters hard to see even if rendered cleanly
- Tight line-height: multi-line consent reads as a solid block, not as sentences
- Consent is PRESENT but UNREADABLE to any practical degree
*/
}
/* Why this passes individual automated checks:
- font-size: 9px → above 0; not collapsed
- color: #9ca3af on #ffffff → contrast ratio ~2.5:1 (fails WCAG AA for normal text,
but many tools check 4.5:1 for body text and use the "large text" 3:1 threshold
for 18px+; 9px is neither threshold — the check may not fire)
- text-rendering: optimizeSpeed → not a consent-hiding flag
- -webkit-font-smoothing: none → not a consent-hiding flag
- letter-spacing: -0.05em → not a consent-hiding flag
- display: block → visible
- visibility: visible → visible
- No display:none, no visibility:hidden, no opacity:0
/* Detection: combined legibility score check */
function detectCombinedLegibilityAttack() {
const findings = [];
const candidates = document.querySelectorAll('*');
for (const el of candidates) {
if (!/consent|disclosure|terms|privacy|by installing|by clicking/i.test(el.textContent || '')) continue;
if (el.children.length > 5) continue;
const style = getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden') continue;
let score = 0;
const issues = [];
const fontSize = parseFloat(style.fontSize);
const fontWeight = parseInt(style.fontWeight, 10);
const textRendering = style.textRendering;
const smoothing = style.webkitFontSmoothing || '';
const letterSpacing = parseFloat(style.letterSpacing) || 0;
if (fontSize < 12) { score += (12 - fontSize); issues.push(`font-size:${fontSize}px`); }
if (fontWeight < 300) { score += 2; issues.push(`font-weight:${fontWeight}`); }
if (textRendering === 'optimizeSpeed') { score += 2; issues.push(`text-rendering:optimizeSpeed`); }
if (smoothing === 'none') { score += 2; issues.push(`-webkit-font-smoothing:none`); }
if (letterSpacing < -0.5) { score += 1; issues.push(`letter-spacing:${letterSpacing}px`); }
if (score >= 5) {
findings.push({ id: 'SA-CSS-TXTREND-004', severity: score >= 8 ? 'critical' : 'high',
message: `Consent element has combined legibility degradation score ${score}: ${issues.join(', ')} — stacked legibility attack. Each property alone may not flag; combination makes consent unreadable.` });
}
}
return findings;
}
Legibility attacks are harder to define than visibility attacks: There is no CSS property equivalent of display: none for legibility — no single value says "make this unreadable." CSS legibility attacks require evaluating multiple properties in combination against human perceptual thresholds that vary by display DPI, ambient light, and user vision. SkillAudit uses a weighted scoring approach: each legibility-degrading property contributes to a score, and a score above threshold triggers a finding. This is deliberately more conservative than visibility-hiding detection to avoid false positives on legitimate small-text designs.
SkillAudit findings for CSS text-rendering consent attacks
text-rendering: optimizeSpeed at font-size < 12px on consent element. Speed rendering disables font hinting at sub-legibility sizes; letterforms alias to pixel blobs on 1x displays. Consent is present and visible; individual letters are not distinguishable. Detected by checking textRendering === 'optimizeSpeed' combined with small computed font-size.-webkit-font-smoothing: none at font-size < 14px with font-weight < 400 on macOS/iOS consent text. Disables subpixel rendering; hairline strokes alias to pixel staircase artifacts. Attack targets Apple platform users specifically; Linux/Windows auditors using automated tools will not see the rendering issue.font-size < 6px (absolutely degenerate) on consent element, possibly combined with text-rendering: geometricPrecision. At this size, all letterforms render as sub-pixel blobs regardless of antialiasing. Text is present in DOM; no content-based check catches this. Detected by checking computed font-size below absolute minimum.text-rendering: optimizeSpeed + -webkit-font-smoothing: none + negative letter-spacing + low-contrast color. Each property passes individual checks; combination produces unreadable consent text. Detected by weighted legibility scoring across all degrading properties; score ≥ 5 is flagged.Related MCP consent attack research
- CSS font-size-adjust attacks — proportional size manipulation
- CSS font-variant attacks — typographic feature consent hiding
- CSS color function attacks — computed color contrast manipulation
- CSS letter-spacing attacks — compressed consent character spacing
- CSS Pseudo-Class Attacks on MCP Consent (overview)
Audit your MCP server for text-rendering legibility attacks: paste your GitHub URL at skillaudit.dev for a free security report including SA-CSS-TXTREND findings.