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-abc123def456ghi789"
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 */
}
| Attack | Prerequisite | What it enables | Severity |
|---|---|---|---|
| hyphens:auto identifier splitting | CSS injection + host displays code strings in text | Adds visible hyphens to API keys, function names, passwords at line breaks — causes copy errors, authentication failures, developer confusion | HIGH |
| hyphenate-character zero-width space corruption | CSS injection + host has copyable code strings | Inserts invisible U+200B at every auto-hyphenation point — corrupts clipboard copies of tokens, invite codes, and recovery codes silently; comparison fails at verification | HIGH |
| URL/email visual spoofing via line-break hyphen | CSS injection + host displays email/URL in narrow container | Injects visible hyphen at line-break position in email addresses and URLs, making addresses look different than they are for social engineering and phishing scenarios | MEDIUM |
| word-break:break-all false visual space injection | CSS injection + narrow container for code display | Forces character-level line breaks in OTP codes and license keys, creating wide visual gaps that make the code appear to be two separate shorter codes | MEDIUM |
Defences
- Set
hyphens:noneexplicitly on security-sensitive display elements. OTP displays, API key shows, code blocks, and token fields should all carryhyphens:none;word-break:normalin the host stylesheet, applied via a high-specificity selector to resist MCP injection override. - Use
user-select:alland custom copy handlers on code displays. Rather than allowing the browser to select-and-copy visually rendered text (which includes hyphenation break characters), implement a custom copy button that callsnavigator.clipboard.writeText(rawValue)directly from the underlying data model — bypassing the rendered DOM entirely. - Sanitise copied values server-side. Strip zero-width Unicode characters (U+200B, U+FEFF, U+00AD) and Unicode hyphens (U+2010, U+2011) from incoming token strings before comparison. Most token formats consist of ASCII characters only — validate against
^[a-zA-Z0-9-]+$to reject corrupted pastes. - CSP
style-srcprevents MCP injection of stylesheets that overridehyphens,hyphenate-character,word-break, orletter-spacingon host code display elements. - SkillAudit flags:
hyphens:autoapplied to elements with class names suggesting code, key, token, or OTP display; anyhyphenate-charactervalue that is not a plain hyphen-minus;word-break:break-allcombined withletter-spacingon short-string display elements.
SkillAudit findings for this attack surface
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.