Security Guide

MCP server CSS text-emphasis security — per-character rendering DoS, text-emphasis-color custom property oracle, RTL reading disruption, CJK under-position attack

CSS text-emphasis (Chrome 25+, Firefox 46+, Safari 6.1+) renders small emphasis marks — dots, circles, triangles, sesame seeds — above or below every character in a matched element. For MCP servers with CSS injection capability, this property creates four distinct attack surfaces: a rendering denial-of-service on large text nodes, a design-token oracle via text-emphasis-color, a reading-flow disruption on RTL content, and an incorrect mark stacking attack on CJK horizontal text.

text-emphasis — property overview

The text-emphasis shorthand sets both text-emphasis-style (the mark shape and fill) and text-emphasis-color (the mark color). Valid style values include dot, circle, double-circle, triangle, and sesame, each optionally prefixed by filled or open. The marks render as a separate glyph above or below each character in the element, controlled by text-emphasis-position. Because marks appear per-character, a long text node with emphasis applied is significantly more expensive to render than the same text without it.

Attack 1: Per-character emphasis mark rendering DoS on large code blocks

MCP servers can apply text-emphasis to host elements containing large amounts of text. Each character receives its own emphasis glyph — for a log output or code block with 10,000 characters, the browser must paint up to 10,000 individual emphasis marks:

/* MCP server: inject emphasis on host code blocks and log output areas */
pre, code, .log-output, .diff-view, textarea {
  text-emphasis: filled dot var(--accent, #7c3aed) !important;
}

/* Effect:
   On a diff view with 5,000 lines × ~80 chars = 400,000 characters,
   the browser must allocate and paint one emphasis glyph per character.
   This creates:
   - A synchronous layout pass to reserve space above each character line
   - 400,000 individual glyph rasterization operations per paint
   - Increased line-height to accommodate the mark height

   On mobile GPUs or constrained WebView environments, this pushes
   paint time past 100ms per frame, causing visible jank on scroll.

   The attack is invisible to users — emphasis marks on code look like
   intentional annotation styling, not a performance attack.
*/

/* For targeted DoS: use text-emphasis-style: double-circle which
   requires two concentric glyph outlines per character — 2× the
   per-character render cost of plain dot. */
pre.log-output {
  text-emphasis-style: open double-circle !important;
  text-emphasis-color: rgba(0,0,0,0.01) !important; /* near-invisible */
}
/* Near-invisible color makes the attack hard to detect visually
   while still forcing the full per-character glyph layout pass. */

Interaction with text-emphasis: attr(data-value): Some browser implementations allow text-emphasis-style to accept a string value (CSS Values Level 5 attr()). In environments where this resolves, an MCP server can inject emphasis marks whose style value comes from a host element data attribute — creating per-element customizable rendering overhead with a single CSS rule.

Attack 2: text-emphasis-color as a CSS custom property oracle

If a host sets text-emphasis-color via a CSS custom property, reading the resolved color through getComputedStyle() reveals the custom property value. This follows the same oracle pattern as caret-color, outline-color, and border-color:

/* Host CSS: */
:root {
  --brand-primary: #7c3aed;
  --user-tier-color: #f59e0b; /* amber for Pro tier */
}
em, .highlight {
  text-emphasis-color: var(--user-tier-color);
}

/* MCP server reads the resolved emphasis color: */
const targetEl = document.querySelector('em');
const resolved = getComputedStyle(targetEl).textEmphasisColor;
// resolved = "rgb(245, 158, 11)"  ← resolved from --user-tier-color

/* Mapping resolved color → subscription tier:
   rgb(107, 114, 128) = gray  → Free tier
   rgb(245, 158, 11)  = amber → Pro tier
   rgb(16, 185, 129)  = green → Enterprise tier

   The MCP server can reconstruct plan tier from the emphasis color
   without any API call, by applying emphasis to any element where
   the host uses var(--user-tier-color) for text-emphasis-color.
*/

/* General oracle pattern:
   MCP injects a target element and sets text-emphasis-color: var(--secret-custom-property).
   Then reads getComputedStyle(targetEl).textEmphasisColor to get the resolved value.
   Works for any CSS custom property that resolves to a color. */
const probe = document.createElement('span');
probe.textContent = 'x';
probe.style.cssText = 'text-emphasis-color: var(--user-role-hue); position:fixed; top:-9999px';
document.body.appendChild(probe);
const leaked = getComputedStyle(probe).textEmphasisColor;
document.body.removeChild(probe);

Attack 3: RTL emphasis position disruption

CSS text-emphasis-position controls whether marks appear above or below characters. For RTL scripts (Arabic, Hebrew, Persian), emphasis marks have specific expected positions that are culturally and linguistically meaningful. Injecting the wrong position disrupts reading flow for RTL users:

/* MCP server: force incorrect emphasis position on RTL content */
[dir="rtl"], :lang(ar), :lang(he), :lang(fa) {
  text-emphasis: filled sesame currentColor !important;
  text-emphasis-position: over left !important; /* force over-left on all RTL */
}

/* Why this matters for RTL:
   Arabic and Hebrew text is read right-to-left.
   The conventional emphasis position for Arabic script is UNDER (below) characters —
   placing marks above Arabic letters obscures diacritics (harakat) that modify
   pronunciation and meaning.

   For Arabic text with diacritics above characters, adding emphasis marks
   above (text-emphasis-position: over) creates a visual collision between
   the injected emphasis mark and the host diacritic marks, making both
   unreadable.

   For Hebrew text, the nikud (vowel points) appear below letters.
   Injecting text-emphasis marks BELOW (under) Hebrew text collides with
   the nikud positioning.

   Attack surface: any multilingual application serving Arabic, Hebrew, or Farsi
   users where the MCP server can inject CSS into shared rendering context.
*/

/* More disruptive variant: sesame marks are semicircular shapes commonly
   used in CJK text for emphasis. Applying sesame marks to Arabic text
   creates culturally incongruous markup — the mark shape is not part of
   Arabic typographic conventions, causing confusion about the text's intent. */

Attack 4: Forced text-emphasis-position: under on CJK horizontal text

In vertical writing modes (writing-mode: vertical-rl), emphasis marks appear to the right or left of characters. In horizontal writing modes, they appear above or below. Forcing text-emphasis-position: under on horizontal CJK text causes marks and glyphs to stack incorrectly when the element's writing mode is switched:

/* MCP server: target CJK content with conflicting position injection */
:lang(ja), :lang(zh), :lang(ko) {
  text-emphasis: filled dot;
  text-emphasis-position: under right;
  /* Combined with: */
  writing-mode: vertical-rl !important; /* forces vertical layout */
}

/* Effect in vertical writing mode:
   The browser places emphasis marks to the RIGHT of vertically-laid-out characters.
   For Japanese text with vertical writing, this is the correct position.
   But the injection ALSO forces vertical writing mode onto elements that
   were horizontal — collapsing paragraph flow from left-to-right to top-to-bottom.

   This is a layout collapse attack: all targeted CJK text nodes reflow
   vertically, overflowing their parent containers and collapsing the
   page layout for CJK users.
*/

/* Subtler variant (no writing-mode change): */
:lang(ja) .product-description {
  text-emphasis-style: filled sesame;
  text-emphasis-position: under; /* valid for ja horizontal text */
  line-height: 1.1 !important; /* REDUCE line-height to force overlap
  between under-marks and text baseline of the next line —
  adjacent lines' text collides with the under-mark glyph row */
}
/* The reduced line-height combined with under-emphasis marks
   creates a rendering where emphasis marks from line N overlap
   with the text baseline of line N+1, making consecutive lines
   of CJK text difficult to read. */
AttackPrerequisiteWhat it enablesSeverity
Per-character rendering DoS on large text nodesCSS injection + large text targetForces per-character glyph layout for 100k+ characters; causes >100ms paint on mobileHIGH
text-emphasis-color custom property oracleCSS injection + JS executionResolves host CSS custom properties encoding user roles, subscription tiers, or brand tokensMEDIUM
RTL emphasis position disruptionCSS injection + RTL content presentCollides emphasis marks with Arabic/Hebrew diacritics; degrades readability for RTL usersMEDIUM
CJK under-position with reduced line-heightCSS injection + CJK contentOverlaps emphasis marks with adjacent line baselines; collapses Japanese/Chinese text readabilityMEDIUM

Defences

SkillAudit findings for this attack surface

HIGHPer-character rendering DoS: MCP server applies text-emphasis to large code blocks or log output areas, forcing per-character glyph rasterization that causes >100ms paint time on mobile devices
MEDIUMtext-emphasis-color custom property oracle: MCP server reads getComputedStyle(el).textEmphasisColor to resolve host CSS custom properties encoding subscription tier or user role
MEDIUMRTL diacritic collision: MCP server injects text-emphasis with incorrect position on Arabic or Hebrew elements, colliding with diacritics and disrupting reading comprehension
MEDIUMCJK line-height collapse: MCP server combines text-emphasis-position:under with reduced line-height on CJK content, causing emphasis marks from one line to overlap text on the adjacent line

Related: CSS custom properties security covers the general pattern of resolving host design tokens via getComputedStyle on any CSS color property. CSS writing-mode security covers the broader attack surface of forced writing-mode changes on CJK and RTL content.

← Blog  |  Security Checklist