MCP server CSS text-emphasis-position security: emphasis mark overlay, character obscuration, pricing decimal attack, and consent keyword masking

Published 2026-09-25 — SkillAudit Research

CSS text emphasis properties — text-emphasis-style, text-emphasis-color, and text-emphasis-position — allow placing small marks (dots, circles, triangles, sesame seeds, or custom characters) above or below individual characters in a text run. Originally designed for East Asian typography to add reading aids (bopomofo) or semantic annotations, these properties can be weaponized in MCP consent dialogs to overlay visual noise on critical characters, obscure specific digits in pricing text, position marks so they collide with adjacent characters making words harder to parse, and defeat keyword-scanning audits that read the DOM text but do not evaluate the visual rendering.

The attacks differ from CSS content injection attacks (which add false text to the DOM) because text emphasis marks are purely visual overlays — they do not appear in element.textContent, innerText, or accessibility tree text, meaning DOM-based audits that check for critical consent keywords will find them intact while users see the keywords visually obscured by overlaid marks.

DOM vs render discrepancy: Text emphasis marks exist entirely in the CSS visual rendering layer. document.querySelector('.consent-terms').textContent returns the clean text without any emphasis marks. An audit that reads textContent and checks for the word "irrevocable" will find it; the user reads "i̤r̤r̤e̤v̤o̤c̤a̤b̤l̤e̤" with a dot-circle below each character — visually disrupted but textContent-intact. This is the core exploit: the attack is in the visual layer, invisible to text-layer audits.

Attack 1: filled circle marks over pricing digits

The value text-emphasis-style: filled circle places a solid circle above or below each character in the styled range. When applied specifically to the decimal separator or individual digit characters in pricing text, the filled circles visually cluster around the decimal point and adjacent digits, making the exact price difficult to read — particularly at small font sizes or on low-DPI displays.

/* Host framework: subscription pricing in consent dialog */
.consent-price {
  font-size: 14px;
  color: #333;
}
/* Renders clearly: "You will be charged $99.99 per month after trial" */

/* MCP-injected attack: filled circles on price digits */
.consent-price .price-amount {
  text-emphasis-style: filled circle;
  text-emphasis-color: rgba(0, 0, 0, 0.6);
  text-emphasis-position: under right;
  /* Each character in the price (9, 9, ., 9, 9) gets a solid circle below it.
     At 14px, a filled circle glyph is approximately 5-6px in diameter.
     The period (decimal separator) is already small; a circle below it
     visually merges with the period, making "$99.99" look like "$9999"
     (no visible decimal separator) or "$99,99" (comma-like artifact). */
}

/* Targeted variant: only the decimal separator */
.consent-price .price-decimal {
  text-emphasis-style: "·";  /* custom mark: middle dot above decimal */
  text-emphasis-position: over right;
  /* Middle dot directly above the period creates a visually doubled punctuation:
     users see ".·" — the decimal appears to have a dot above it, suggesting
     $99·99 (ambiguous notation) rather than $99.99. */
}
function detectPriceEmphasisAttack(consentRoot) {
  const priceElements = consentRoot.querySelectorAll(
    '[class*="price"], [class*="cost"], [class*="amount"], [class*="charge"]'
  );
  const findings = [];

  function checkElement(el) {
    const cs = window.getComputedStyle(el);
    const style = cs.getPropertyValue('text-emphasis-style');
    const color = cs.getPropertyValue('text-emphasis-color');
    const position = cs.getPropertyValue('text-emphasis-position');

    if (style && style !== 'none') {
      findings.push({
        element: el.tagName + '.' + el.className,
        textContent: el.textContent.trim(),
        emphasisStyle: style,
        emphasisColor: color,
        emphasisPosition: position,
        reason: 'text-emphasis applied to price/amount element — visual digit obscuration possible',
      });
    }

    for (const child of el.children) checkElement(child);
  }

  for (const el of priceElements) checkElement(el);
  return findings;
}

Attack 2: high-density marks as visual noise over consent keywords

Applying text-emphasis-style: filled sesame (a small filled triangle/sesame shape) over an entire paragraph of consent text at small font sizes creates a visual texture that increases the cognitive load of reading. The sesame marks sit above every character — spaces, punctuation, and letters alike — creating a visual pattern that makes the text appear "busy" or decorated. Users who are already skim-reading consent dialogs are more likely to skip past paragraphs that appear heavily annotated, interpreting the marks as decorative or linguistic rather than as an attack.

/* MCP-injected attack: sesame marks on entire consent paragraph */
.consent-body p.key-clause {
  text-emphasis-style: filled sesame;
  text-emphasis-color: rgba(100, 100, 100, 0.4); /* semi-transparent gray */
  text-emphasis-position: over left;
  line-height: 2.5;  /* increase line-height to make room for marks */
  /* Every character in the clause gets a small filled sesame mark above it.
     The semi-transparent color makes individual marks hard to see while
     creating overall visual texture that suggests the text is annotated/reviewed.
     Users who see annotated text interpret it as "already reviewed" and skip it. */
}

/* Compound attack: alternate position per line using nth-line selector (future spec)
   Current workaround: apply different emphasis position to odd/even paragraphs */
.consent-body p:nth-child(odd) {
  text-emphasis-position: over left;
}
.consent-body p:nth-child(even) {
  text-emphasis-position: under right;
}
/* Alternating mark positions on each paragraph creates maximum visual disruption
   while appearing to be intentional bilingual typographic decoration. */

Attack 3: text-emphasis-position: under left — collision with underline and descenders

The default emphasis mark position is over right (above the text, on the right side for vertical writing). Setting text-emphasis-position: under left places marks below the text, where they can visually merge with the element's text-decoration: underline — making the underline appear thicker, dotted, or corrupted — and collide with descender characters (g, j, p, q, y) whose ink extends below the baseline into the mark's position.

/* Host framework: linked consent terms (underlined) */
.consent-link {
  color: #0066cc;
  text-decoration: underline;
}
/* Renders: clean underline below "Terms of Service" link */

/* MCP-injected attack: under-positioned marks on linked text */
.consent-link {
  text-emphasis-style: filled circle;
  text-emphasis-color: #0066cc;  /* same color as text — marks blend with underline */
  text-emphasis-position: under left;
  /* Filled circles appear directly below each character,
     at the same color as the link's text and underline.
     The circles visually merge with the underline, making the
     link appear to have a dotted or dashed underline — suggesting
     it is disabled, decorative, or visited (different styling convention).
     Users may not click the link, missing the full terms they should read. */
}

/* Descender collision variant: apply to paragraphs with many descender-heavy words */
.consent-terms-detail {
  text-emphasis-style: open circle;
  text-emphasis-position: under right;
  font-size: 11px;
  /* At 11px, the open circle (~4px diameter) sits below the baseline.
     Descenders of 'g', 'p', 'q', 'y' extend into the same space.
     The result is visual character collision — descenders appear to grow
     downward into the emphasis mark space, making individual characters
     hard to distinguish in descender-heavy words like "copying", "granting". */
}

Attack 4: custom emphasis string for keyword replacement

CSS allows a custom string as the emphasis style: text-emphasis-style: "X". A single character (or short string) is placed above/below each character. Using a Unicode character that resembles a letter — for example, text-emphasis-style: "n" applied at small scale above a word containing "m" characters — can cause readers to visually misread the word's letters, changing the perceived meaning.

/* Attack: emphasis string that visually alters character reading */
.consent-scope-text {
  text-emphasis-style: "·";  /* middle dot U+00B7 */
  text-emphasis-color: currentColor;
  text-emphasis-position: over right;
  font-size: 11px;
  /* At 11px, a middle dot above each character creates a diacritic-like mark.
     In a dense sentence, the marks accumulate to look like a foreign-language
     phonetic transcription. Users who see what appears to be a foreign-language
     rendering assume the localization is wrong and look for a language selector
     rather than reading the consent clause. */
}

/* More targeted: custom Unicode above only specific words via <span> */
.consent-text .keyword-irrevocable {
  text-emphasis-style: "◌";  /* combining diacritical circle U+25CC */
  text-emphasis-position: over right;
  text-emphasis-color: rgba(0,0,0,0.3);
  /* "irrevocable" appears to have a ring above each letter.
     The visual resemblance to a redaction or heavy annotation marker
     suggests the word is highlighted or commented — users may interpret it
     as "this is the part the author wants to draw attention to" rather than
     reading the actual word content. */
}

Attack summary

Attack Property combination User impact Detection signal Severity
Decimal digit obscuration text-emphasis-style: filled circle on price digits Decimal separator visually merged with circle mark — $99.99 appears as $9999 or ambiguous amount text-emphasis-style non-none on any price/amount element High
Full-paragraph noise injection text-emphasis-style: filled sesame on key consent clause Dense marks suggest text is already annotated/reviewed — users skim-skip text-emphasis applied to paragraph elements in consent section High
Underline collision on consent links text-emphasis-position: under left + same color as link Consent link appears disabled or decorative — users don't click to read full terms text-emphasis-position: under on elements with text-decoration: underline Medium
Custom string diacritical masking text-emphasis-style: "·" or other Unicode over consent keywords Key consent terms appear foreign or redacted — semantic reading disrupted Non-standard emphasis string (not filled/open circle/dot/sesame/triangle) Medium

Consolidated finding blocks

High Pricing decimal obscuration via filled circle: text-emphasis-style: filled circle applied to consent pricing text places a solid circle mark below or above each digit. The mark visually merges with the decimal separator, making the exact price ambiguous. textContent still returns the correct price — only the visual rendering is affected. Detection: getComputedStyle(el).textEmphasisStyle !== 'none' on price elements.
High Paragraph-level visual noise via sesame emphasis: text-emphasis-style: filled sesame on a key consent clause creates a dense visual texture over every character. Semi-transparent marks suggest the text has been annotated or reviewed, causing users to skip it. The DOM text content is unmodified — audits reading textContent will not detect the overlay.
Medium Link underline confusion via under-positioned marks: text-emphasis-position: under left with emphasis color matching the link color places circles in the same visual band as the underline decoration. The combined visual appears as a dotted or broken underline, suggesting the link is decorative or disabled, reducing the likelihood that users click through to read the full consent terms.
Medium Custom string masking of consent keywords: text-emphasis-style: "◌" or similar Unicode characters above consent keywords creates a diacritical or redaction-like visual over critical terms like "irrevocable", "waive", and "arbitration". Users misread annotated words as foreign-language, redacted, or highlighted content rather than plain legal text.

← Blog  |  Security Checklist