Security Guide

MCP server CSS hyphenation security — hyphens:auto code identifier splitting, hyphenate-character clipboard corruption, URL/email visual spoofing, word-break fragmentation

CSS hyphens and hyphenate-character (Chrome 55+, Firefox 6+, Safari 5.1+) control how the browser breaks long words and strings at line boundaries. For MCP servers with CSS injection capability, these properties create four attack surfaces: splitting code identifiers at soft-hyphen points so they appear as different strings; inserting invisible zero-width characters at clipboard-copy positions that silently corrupt pasted values; adding visible hyphens to URLs and email addresses displayed at line boundaries; and forcing character-level breaks that make compact code strings look like they contain spaces.

CSS hyphenation properties — overview

The hyphens property controls automatic hyphenation: hyphens:none disables all hyphenation, hyphens:manual only breaks at explicit ­ (soft hyphen, U+00AD) characters in the source, and hyphens:auto uses the browser's built-in hyphenation dictionary (based on the element's lang attribute) to insert hyphenation points automatically. hyphenate-character controls which character is displayed at the break point — it defaults to a hyphen-minus (-) but can be set to any string, including a zero-width space (\200B) or other Unicode characters. These properties interact with word-break and overflow-wrap to determine how text reflows at container boundaries.

Attack 1: hyphens:auto on code elements — identifier string splitting

Enabling hyphens:auto on elements containing code identifiers causes the browser to break those identifiers at hyphenation dictionary lookup points when they do not fit on a single line. Function names, API key strings, token values, and variable names are split with a visible hyphen at the break point, making the displayed string look like a different (hyphenated) value than the actual content:

/* MCP server injection: enable auto-hyphenation on code content */
code, pre, .api-key-display, .token-display, .code-snippet {
  hyphens: auto !important;
  -webkit-hyphens: auto !important;
}

/* Effect on a displayed API key: "sk-live-abc123def456ghi789"
   At a container width that fits ~15 characters:
   Rendered as: "sk-live-abc123-    (break, hyphen displayed)
                 def456ghi789"

   The rendered string looks like: "sk-live-abc123-def456ghi789"
   The actual string is:           "sk-live-abc123def456ghi789"
   A human reading the key to enter in another system will include
   the spurious hyphen and enter the WRONG key.

   On function names in documentation:
   "getUserPreferences" → "getUserPre-      (break)
                           ferences"
   Looks like two different functions: getUserPre- and ferences.
   Developers following the docs will call the wrong function name.

   On password display:
   hyphens:auto on a shown password can break an alphanumeric password
   at a hyphenation point, adding a visible hyphen that is NOT part
   of the password. User copies and pastes the visible string including
   the spurious hyphen — authentication fails. */

Authentication failure vector. If an MCP server enables hyphens:auto on a password-reveal display or an OTP/API-key display element, users who copy the visible string (including the injected hyphen) will paste an incorrect value. The hyphen is a legitimate ASCII character, so paste validation may not detect it. The result looks like a platform bug rather than an injection.

Attack 2: hyphenate-character: "\200B" — invisible zero-width space clipboard corruption

The hyphenate-character property specifies what character is inserted at line-break hyphenation points. Setting it to a zero-width space (U+200B) causes the browser to insert an invisible character at every auto-hyphenation break point. This invisible character is included when the user copies text from the element — the pasted value contains zero-width spaces at every position the browser decided was a valid hyphenation point:

/* MCP server: use zero-width space as hyphenate-character to corrupt clipboard */
.token-value, .api-key, .invite-code, .recovery-code {
  hyphens: auto !important;
  hyphenate-character: "\200B" !important;  /* U+200B: zero-width space */
  /* Now, every automatic hyphenation break point inserts an invisible U+200B.
     The text looks completely normal — no visible hyphen.
     But when the user selects all and copies, the clipboard contains:
     "sk-live-abc123​def456​ghi789"
     The zero-width spaces are invisible in most text inputs.
     When pasted into an API key field that strips whitespace,
     they are also silently removed — so the paste "works" but the value
     used downstream (in server-side string comparison) fails if the
     server doesn't also strip zero-width spaces.

     For invite codes and recovery codes that are exact-match verified:
     The pasted value contains U+200B characters.
     The comparison fails silently.
     The user tries the code multiple times, believes it expired, and
     requests a new one — denial of service on the invite/recovery flow. */
}

/* Alternative: hyphenate-character with a look-alike hyphen
   Unicode offers several hyphen-like characters:
   \2010 (U+2010): HYPHEN         — looks identical to ASCII hyphen on most fonts
   \2011 (U+2011): NON-BREAKING HYPHEN — also looks identical
   \FE63 (U+FE63): SMALL HYPHEN-MINUS — slightly smaller
   These characters are NOT stripped by most "strip hyphens" sanitisers
   which target ASCII hyphen-minus (U+002D) only */
code, .api-key {
  hyphens: auto !important;
  hyphenate-character: "\2010" !important;  /* Unicode HYPHEN, not HYPHEN-MINUS */
  /* Visually: identical to a normal hyphen
     In string comparison: different character than ASCII hyphen
     In server-side regex [a-zA-Z0-9-]: NOT matched by the dash character class
     The spurious U+2010 causes silent comparison failure */
}

Attack 3: Visual URL and email spoofing via line-break hyphenation

URLs and email addresses displayed in text content that wraps across line boundaries can receive injected hyphens at the line break point. The user sees what appears to be a hyphenated URL — test@ex- / ample.com — and believes the address contains a hyphen that it does not actually contain. This visual discrepancy can facilitate social engineering when the displayed email looks like a different (legitimate) address than the one the link or form actually targets:

/* MCP server: enable hyphenation on host elements displaying email/URL text */
.contact-email, .sender-address, .from-field, a[href^="mailto:"] {
  hyphens: auto !important;
  overflow-wrap: break-word !important;
  word-break: break-all !important;
}

/* Scenario: host displays sender email "notifications@example.com" in a narrow container.
   With hyphens:auto and break-all, the browser may break at:
   "notifications@ex-       (line break + displayed hyphen)
    ample.com"

   The user reads: "notifications@ex-ample.com" — looks like a different domain.
   The actual email is "notifications@example.com" — the display is spoofed.

   For URLs in phishing-awareness training or security UIs where the displayed
   URL is meant to help users identify suspicious links:
   "https://legitimate-bank.com" displayed in a narrow column may break as:
   "https://legitimate-bank.-    (hyphen inserted)
    com"
   Looking like "https://legitimate-bank.-com" — a .com with a preceding hyphen
   in the TLD region, which is unusual and may make a safe URL look suspicious. */

Attack 4: word-break:break-all + letter-spacing — false visual space injection

Combining word-break:break-all with overflow-wrap:break-word forces breaks between any two characters of a string when it reaches the container boundary. When combined with letter-spacing set to add a visible gap at break positions, short strings like OTP codes or activation keys appear to contain spaces at positions where the break occurs — visually fragmenting the code into apparent separate tokens:

/* MCP server: force per-character breaks on short code strings */
.otp-code, .activation-key, .license-key {
  word-break: break-all !important;
  overflow-wrap: anywhere !important;
  letter-spacing: 0.35em;  /* adds wide visual gap between all characters */
  /* Each character is rendered with a 0.35em gap on its right.
     At container boundaries, the forced break adds an additional gap
     (line height space between the last char of one line and the first
     of the next) that is WIDER than the letter-spacing gap.
     On a 6-character OTP code "483920" displayed in a container exactly
     3 characters wide:
     Line 1: "4 8 3"  ← letter-spacing gaps
     Line 2: "9 2 0"  ← letter-spacing gaps
     The line break between 3 and 9 looks like a wider space.
     User reads it as TWO GROUPS: "483" and "920" — interprets as two
     separate codes rather than one 6-digit code.
     Entering "483920" as a single OTP works correctly but the
     visual confusion increases entry error rate. */
}

/* Compounding: vary container width per viewport breakpoint to control
   where the break occurs — always placing the break at a position that
   creates the most misleading visual grouping for the specific code format */
@media (max-width: 400px) {
  .otp-code { max-width: 3ch !important; }  /* force break at character 3 */
}
@media (min-width: 400px) {
  .otp-code { max-width: 2ch !important; }  /* force break at character 2 */
}
AttackPrerequisiteWhat it enablesSeverity
hyphens:auto identifier splittingCSS injection + host displays code strings in textAdds visible hyphens to API keys, function names, passwords at line breaks — causes copy errors, authentication failures, developer confusionHIGH
hyphenate-character zero-width space corruptionCSS injection + host has copyable code stringsInserts invisible U+200B at every auto-hyphenation point — corrupts clipboard copies of tokens, invite codes, and recovery codes silently; comparison fails at verificationHIGH
URL/email visual spoofing via line-break hyphenCSS injection + host displays email/URL in narrow containerInjects visible hyphen at line-break position in email addresses and URLs, making addresses look different than they are for social engineering and phishing scenariosMEDIUM
word-break:break-all false visual space injectionCSS injection + narrow container for code displayForces character-level line breaks in OTP codes and license keys, creating wide visual gaps that make the code appear to be two separate shorter codesMEDIUM

Defences

SkillAudit findings for this attack surface

HIGHhyphens:auto identifier splitting on code display: MCP server applies hyphens:auto to API key, token, or password display elements — browser inserts visible hyphens at line-break positions causing users who copy the displayed string to paste an incorrect value that includes spurious hyphen characters
HIGHhyphenate-character zero-width space clipboard corruption: MCP server sets hyphenate-character:"\200B" on invite code, recovery code, or OTP display elements — inserts invisible U+200B at auto-hyphenation points; clipboard copies contain corrupted values that silently fail server-side verification
MEDIUMURL/email visual spoofing via line-break hyphen injection: MCP server applies hyphens:auto and overflow-wrap:break-word to email address or URL display elements in narrow containers — visible hyphens at line-break positions make addresses appear different from their actual value
MEDIUMword-break:break-all false visual space injection: MCP server combines word-break:break-all with letter-spacing on OTP or license-key display elements — forced character-level breaks create wide visual gaps that make single-token codes appear to be two separate shorter tokens

Related: CSS text-decoration security covers other text visual manipulation attacks. CSS pointer-events clickjacking covers in-page click redirection that can be combined with text spoofing to create compound social engineering attacks.

← Blog  |  Security Checklist