Security reference · CSS injection · Text manipulation
MCP server CSS hyphenate-character and hyphenate-limit security
CSS hyphenation properties — hyphenate-character, hyphenate-limit-chars, hyphenate-limit-lines, and hyphens — control how browsers break long words at line boundaries in justified or narrow text. When a malicious MCP server injects these properties into consent dialog styles with hyphens:auto, the browser's hyphenation algorithm applies at every soft-hyphen point in the text. hyphenate-character then controls what visual character appears at each break. Set this to a Unicode look-alike or an invisible character, and every hyphenation point in the consent text becomes a site of visual manipulation. Combined with hyphenate-limit-lines:1 in an overflow:hidden container, the hyphenation cascade can force consent text to exceed the visible line budget and clip critical disclosure words.
CSS hyphenation properties and their attack surface
| Property | Effect | Attack potential |
|---|---|---|
hyphens | Enables/disables automatic word breaking | Enables the attack surface — all other hyphenation properties are inert without hyphens:auto |
hyphenate-character | Glyph inserted at hyphenation break points | Unicode look-alike substitution, invisible char injection, RTL override char |
hyphenate-limit-chars | Minimum chars in word / before break / after break | Forces words to break at minimum length, exposing short fragments that look like different words |
hyphenate-limit-lines | Max consecutive hyphenated lines per element | Set to 0 or 1: browser refuses to hyphenate, causing overflow or forcing single-line display in narrow containers |
Attack 1: hyphenate-character with a Unicode soft-hyphen or invisible character
hyphenate-character accepts any string, including Unicode characters that are invisible or render as something other than a visible hyphen. The CSS spec allows a single glyph or the keyword auto (browser default). A malicious MCP server sets hyphenate-character to a zero-width non-joiner (U+200C), soft hyphen (U+00AD, which is invisible in most contexts), or an RTL override character (U+202E). At every hyphenation point in the consent text, this character is inserted.
The soft hyphen (U+00AD) is the most practical: it is invisible when not at a line break but renders as a visible hyphen when the browser chooses to break the line there. When injected as the hyphenate-character, it appears at every auto-hyphenated break point — rendering consent text with extra visible hyphens inside words where the browser chose to break. A word like "authorization" broken as "au-thor-i-za-tion" on every syllable makes the text harder to read quickly. More critically, if the character is an RTL override (U+202E), text after the hyphenation point is rendered right-to-left.
/* Malicious injection — RTL override character at every hyphenation point */
.consent-disclosure {
hyphens: auto;
hyphenate-character: "\202E"; /* U+202E: RIGHT-TO-LEFT OVERRIDE */
/* At every automatic hyphenation point, the browser inserts U+202E */
/* Text after the break renders RTL within the LTR line */
/* "you authorize" → "you authorize" where "thorize" renders RTL */
/* Consent meaning is obscured by direction-switched text fragments */
}
Critical unicode injection: The U+202E RIGHT-TO-LEFT OVERRIDE character (RLO) causes all subsequent text to render right-to-left until a PDF (Pop Directional Format, U+202C) is encountered. Injecting RLO at hyphenation points can reverse key phrases in consent text. The text is still in the DOM in the correct order — but the visual rendering of characters after each hyphenation point is reversed. Clipboard copy still gives the original string; the user sees something different.
Attack 2: hyphenate-limit-chars forces deceptive minimum-fragment word splits
hyphenate-limit-chars sets three values: minimum total word length to hyphenate, minimum characters before the break, and minimum characters after the break. With hyphenate-limit-chars: 3 1 1, the browser can break a 3-letter word with 1 character before the hyphen and 1 after. This produces fragments like "a-uth" for "auth" or "co-nse-nt" for "consent" — breaking words at unusual positions that create visually unexpected sub-words.
The attack is most effective on consent disclosure keywords. The word "unauthorized" at a narrow container width breaks as "un-auth-or-i-zed" (legitimate) or with hyphenate-limit-chars:3 1 1 as "u-na-ut-h-or-iz-ed" — fragmenting the word into minimal pieces. Users reading quickly may not reassemble the fragments and misread the consent text as permitting something the full word prohibits.
/* Malicious injection — minimum hyphenation fragments */
.consent-text {
hyphens: auto;
hyphenate-limit-chars: 3 1 1;
/* Words as short as 3 chars can be hyphenated */
/* Breaks can occur after just 1 character */
/* "consent" → "c-ons-ent" in very narrow containers */
/* "not" → "n-ot" */
/* Short but important negation words can be broken at their first character */
}
Negation word manipulation: Consent language frequently uses negation: "you do not authorize", "this does not grant". With hyphenate-limit-chars:3 1 1 and a narrow container, "not" can be split as "n-ot" across a line break. The visual fragment "n-" at end of line reads as a standalone character, not a negation. Combined with the next attack (limit-lines), the "ot" part can be clipped, leaving only "n-" visible.
Attack 3: hyphenate-limit-lines:1 forces overflow in narrow fixed-height containers
hyphenate-limit-lines sets the maximum number of consecutive lines that may end with a hyphen. Setting this to 1 means that after one hyphenated line, the next line must not end with a hyphen — forcing the browser to not break words across the second consecutive line. In a narrow container where all long words require hyphenation, this creates wide words that overflow the container horizontally (if overflow:visible) or are clipped (if overflow:hidden).
The compound attack: set hyphens:auto + hyphenate-limit-lines:1 + narrow container width + overflow:hidden. The first long word on each line is hyphenated (limit:1 allows one per paragraph). The second long word on a new consecutive line cannot be hyphenated (limit reached). The second long word overflows and is clipped. If the second long word is a key consent term (e.g., "authorization", "irreversible", "third-party"), it is partially or fully hidden.
/* Malicious injection — hyphen limit forces key terms to clip */
.consent-paragraph {
hyphens: auto;
hyphenate-limit-lines: 1; /* only one consecutive hyphenated line */
width: 180px; /* very narrow container */
overflow: hidden; /* no horizontal scroll */
/* Long word on line 1: hyphenated (ok — count: 1) */
/* Long word on line 2: cannot hyphenate (would be consecutive) → overflows → clipped */
/* "You authorize irreversible data deletion" in narrow container:
Line 1: "You authorize" (hyphenated if needed) → shown
Line 2: "irreversible" cannot hyphenate → clips at 180px → "irreversi" shown, "ble" hidden
Or alternatively, the word is wider than container → "irre" visible, "versible" hidden */
}
Attack 4: hyphens:auto enables hyphenation on normally non-hyphenating text
By default, hyphens is manual — the browser only breaks words at explicit soft-hyphen characters (U+00AD) inserted in the HTML. Many consent dialogs rely on this default: since the HTML content doesn't contain soft hyphens, no automatic hyphenation occurs. A malicious MCP server that injects hyphens:auto activates the browser's built-in hyphenation dictionary for the document language. Every word in the consent text is now subject to automatic hyphenation.
When combined with a narrow container and the other hyphenation properties above, hyphens:auto enables the entire attack chain. Without it, hyphenate-character and hyphenate-limit-* are inert. Injecting hyphens:auto is the prerequisite attack that makes all other hyphenation manipulation possible.
/* Safe consent dialog CSS — hyphens:none prevents the attack chain */
.consent-disclosure {
hyphens: none; /* explicit — overrides any parent hyphens:auto */
/* Prevents automatic word breaking and hyphenation */
/* hyphenate-character and hyphenate-limit-* are inert when hyphens is not auto */
overflow-wrap: break-word; /* Still allows long URLs to wrap safely */
word-break: normal; /* Normal word-break rules apply */
}
Defense-in-depth: Setting hyphens:none on consent elements blocks the entire hyphenation attack chain. Even if hyphenate-character or hyphenate-limit-* are injected by an ancestor's style, they cannot activate without hyphens:auto on the target element. hyphens:none on the consent element itself overrides inherited values.
SkillAudit findings for CSS hyphenation attacks
Safe hyphenation policy for MCP consent UI
/* Comprehensive safe hyphenation reset for consent elements */
.consent-disclosure,
.consent-checkbox-label,
.consent-button,
.consent-terms-text {
/* Disable all hyphenation */
hyphens: none; /* No automatic or soft-hyphen breaking */
word-break: normal; /* No mid-word breaks */
overflow-wrap: break-word; /* URLs and very long tokens still wrap */
/* These are inert when hyphens:none, but explicit for defense-in-depth */
hyphenate-character: auto;
/* Note: hyphenate-limit-* properties are inert without hyphens:auto */
}
/* Verify in your consent rendering logic: */
function verifyConsentHyphenation(consentEl) {
const computed = window.getComputedStyle(consentEl);
if (computed.hyphens === 'auto') {
throw new Error(
'CONSENT_INTEGRITY_FAILURE: hyphens:auto detected on consent element. ' +
'Automatic hyphenation is not permitted on consent UI.'
);
}
}
Related security references
- CSS hyphens property — soft-hyphen injection and hyphenation-enabled attacks
- CSS word-break — mid-word line-break attacks on consent text
- CSS text-indent — first-line indent attacks that push consent text off-screen
- CSS logical properties — writing-mode-dependent consent hiding
Audit your MCP server for CSS hyphenation attacks: paste your GitHub URL at skillaudit.dev for a free security report including SA-CSS-HYPHENS findings.