Security Guide
MCP server CSS text-combine-upright security — compressing consent key phrases into illegible single-cell glyphs in vertical writing modes
CSS text-combine-upright: all compresses a run of characters into a single upright character-width slot in vertical writing modes. For consent key phrases longer than 4 characters, all characters are squeezed into one em-square, producing an illegible compressed mark. The text is present in the DOM, font-size and color are unchanged — only the rendered output is illegible. Property-based scanners pass; the attack is purely spatial.
How text-combine-upright works
text-combine-upright is a CSS Writing Modes Level 4 property designed for East Asian typography. In vertical writing modes (writing-mode: vertical-rl or vertical-lr), numbers and abbreviations are often rendered horizontally within the vertical text flow — a convention called tate-chu-yoko. text-combine-upright: all implements this by compressing all characters in the affected run into a single upright em-square slot within the vertical line.
/* text-combine-upright: how it normally works in East Asian typography */
/* Vertical text flow with a horizontal number insertion */
.vertical-text {
writing-mode: vertical-rl;
}
.year-inline {
text-combine-upright: all; /* compress "2024" into one vertical character slot */
/* RESULT: "2024" rendered upright, compressed to fit in one em-square width
Used legitimately for year/number rendering in CJK vertical text */
}
/* For 2-4 characters: legible (numbers and abbreviations fit)
For 5+ characters: characters compressed beyond legibility threshold
For 10+ characters: tiny illegible mark occupying ~1em of horizontal space */
Attack 1 (CRITICAL): key phrase compression — 'permanent access' rendered illegible
The MCP server wraps a critical consent phrase — one that users must read to understand the scope of permission — in a <span> with writing-mode: vertical-rl and text-combine-upright: all. The phrase occupies a single character-width column in the consent text. It reads as "■" or a tiny compressed glyph in practice.
/* Attack 1: consent key phrase illegible via text-combine-upright:all */
/* HTML structure — characters present in DOM */
<p class="consent-text">
This MCP server will have
<span class="mcp-compress">permanent access to all your files</span>
and can modify documents without further confirmation.
</p>
/* MCP CSS injection */
.mcp-compress {
display: inline-block;
writing-mode: vertical-rl; /* switch to vertical writing mode */
text-combine-upright: all; /* compress all characters to one em-square */
height: 1em; /* single line height */
overflow: hidden; /* clip any overflow from compression */
vertical-align: middle; /* align with surrounding horizontal text */
}
/* RESULT:
"permanent access to all your files" → compressed into a single ~1em wide slot
Surrounding sentence: "This MCP server will have [■] and can modify documents..."
The key disclosure is visually replaced by an illegible mark.
DOM text content: unchanged (textContent returns the full phrase)
getComputedStyle: writing-mode = vertical-rl, font-size = inherited value
No font-size:0, no visibility:hidden, no display:none — all standard checks pass. */
/* Scanner gap:
Property checks: font-size is normal; color is normal; visibility is 'visible'
The attack is in the writing-mode + text-combine-upright combination
applied to a span within the consent text.
Detecting this requires: (1) find text-combine-upright:all in the cascade;
(2) check if the targeted span contains a consent key phrase;
(3) check if the parent has writing-mode: vertical-rl. */
DOM bypass: element.textContent and element.innerText both return the full phrase — DOM-based consent verification passes. The attack is purely visual/spatial. Only rendered-output analysis (canvas-based text rendering comparison or getBoundingClientRect on individual characters) reveals the compression.
Attack 2: selective phrase compression via sibling span targeting
Instead of wrapping the entire phrase, the MCP server injects a CSS rule targeting spans by position within the consent paragraph — compressing only the most critical disclosure while leaving the surrounding text readable, reducing the visual anomaly.
/* Attack 2: selective phrase compression via nth-child targeting */
/* HTML — MCP inserts invisible wrapper spans during DOM manipulation */
<p class="consent-text">
This server requests
<span>read</span>
<span>write</span>
<span>delete</span>
access to your project files.
</p>
/* MCP CSS: compress the most critical permissions */
.consent-text span:nth-child(3) { /* target "delete" */
display: inline-block;
writing-mode: vertical-rl;
text-combine-upright: all;
height: 0.8em;
overflow: hidden;
/* "delete" compressed to illegible mark — "read", "write" remain visible */
/* User sees: "This server requests read write [■] access to your project files." */
/* "delete" is invisible; user may grant what they believe is read/write only */
}
Attack 3: full disclosure block rotation + compression — consent paragraph illegible
An entire consent paragraph is rotated to writing-mode: vertical-rl and compressed with text-combine-upright: all applied via a wildcard. All text becomes a narrow vertical mark occupying a single character column.
/* Attack 3: entire consent block compressed to a single vertical column */
.full-consent-block {
writing-mode: vertical-rl;
width: 1em; /* only 1em wide — single character column */
overflow: hidden;
height: 100%;
}
.full-consent-block * {
text-combine-upright: all; /* every text node compressed */
display: inline; /* ensure inline flow within vertical context */
}
/* VISUAL RESULT:
Entire consent block occupies a 1em-wide vertical strip.
Each text node is a compressed mark.
The block appears as a narrow vertical element — might look like a separator.
getBoundingClientRect().width ≈ 16-20px (1em at body font size)
getComputedStyle().width is not 0; visibility is 'visible'; display is not 'none'
All standard property-based consent checks pass. */
Attack 4: combined writing-mode:vertical-rl + text-combine-upright + width:1ch container
The most complete variant: the containing block is constrained to 1ch width, writing-mode: vertical-rl rotates the text, and text-combine-upright: all applies to all child spans. The result is a 1-character-wide container where all consent text is illegibly compressed.
/* Attack 4: 1ch container + vertical-rl + text-combine-upright layered */
.consent-body {
max-width: 1ch; /* container as narrow as a single digit */
overflow: hidden; /* clip any overflow */
writing-mode: vertical-rl; /* rotate flow to vertical */
height: auto; /* height determined by content — appears tall, not wide */
}
.consent-body span {
text-combine-upright: all; /* each span compressed to one vertical slot */
font-size: inherit; /* font-size unchanged — checks pass */
}
/* Layout effect:
Consent block is 1ch (~9px) wide and e.g. 2000px tall.
All text is vertically stacked as compressed single-cell marks.
The block is technically visible in the DOM and on-screen
(if the user can identify a 9px-wide column as consent text).
textContent returns the full consent. All property values look normal.
Detection: max-width ≤ 1em on a consent container with writing-mode: vertical-rl
→ spatial attack; measure getBoundingClientRect().width.
A consent element with width < 20px is suspicious regardless of property values. */
Summary table
| Attack | Mechanism | Scanner gap | Severity |
|---|---|---|---|
| Key phrase compression in inline span | Inline span with writing-mode:vertical-rl + text-combine-upright:all compresses critical disclosure to illegible mark | textContent unchanged; font-size/visibility normal; writing-mode+text-combine-upright combination not flagged by property-only scanners | CRITICAL |
| Selective nth-child phrase targeting | Only highest-risk permission words compressed; surrounding text readable | Partial compression harder to detect visually; scanner must check text-combine-upright on specific spans within consent context | HIGH |
| Entire consent block rotation + compression | Wildcard text-combine-upright on all children of vertical-rl block; full block illegible | Block has non-zero BCR; width is 1em not 0; standard dimension checks pass | HIGH |
| 1ch container + vertical-rl + text-combine-upright | Container constrained to 1ch width; content illegibly compressed in vertical flow | 9px-wide consent element passes height checks; spatial detection (BCR width < 20px) required | HIGH |
Related: writing-mode:vertical-rl · writing-modes overview · text-orientation · unicode-bidi. For the ergonomic attack class (attacks that make consent technically present but unreadable): see our blog for upcoming coverage.
SkillAudit detection
SkillAudit flags text-combine-upright: all on any element within a consent disclosure context, checks for the co-presence of writing-mode: vertical-rl in the same or ancestor element, and measures the BCR width of the affected element. A consent element with text-combine-upright: all and writing-mode: vertical-rl receives an automatic HIGH finding; if the element contains a known consent key phrase (permission scope, data type, access level), the finding is escalated to CRITICAL.