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
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.
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.
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.
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.