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

PropertyEffectAttack potential
hyphensEnables/disables automatic word breakingEnables the attack surface — all other hyphenation properties are inert without hyphens:auto
hyphenate-characterGlyph inserted at hyphenation break pointsUnicode look-alike substitution, invisible char injection, RTL override char
hyphenate-limit-charsMinimum chars in word / before break / after breakForces words to break at minimum length, exposing short fragments that look like different words
hyphenate-limit-linesMax consecutive hyphenated lines per elementSet 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 au‮thorize" 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

HighSA-CSS-HYPHENS-001 — hyphenate-character set to a non-standard Unicode character (not U+002D HYPHEN-MINUS) on consent elements with hyphens:auto; potential for RTL-override, invisible char, or look-alike hyphen injection at break points
MediumSA-CSS-HYPHENS-002 — hyphenate-limit-chars with minimum-before or minimum-after value of 1 on consent elements; enables single-character word fragments that obscure consent keywords
HighSA-CSS-HYPHENS-003 — hyphenate-limit-lines:1 combined with narrow container (width ≤240px) and overflow:hidden on consent text; forces long consent terms to overflow and be clipped after one hyphenated line
MediumSA-CSS-HYPHENS-004 — hyphens:auto injected on a consent element that previously had the default hyphens:manual; enables the full hyphenation attack surface where none existed before

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

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.