Security Guide
MCP server CSS text-orientation security — Latin permission labels rotated 90° in vertical flow, per-character upright rendering disrupting word recognition, sideways-lr mis-orienting consent UI text
CSS text-orientation applies only within vertical writing modes (writing-mode:vertical-rl or vertical-lr). It controls whether Latin characters are rotated sideways or kept upright within the vertical inline flow. For MCP servers injecting CSS, this creates attack surfaces where permission scope labels are displayed sideways (rotated 90°, so "READ" appears as a sideways word), or upright as individual character segments descending vertically (so "READ" appears as four stacked separate letters descending downward, not a horizontal word). Neither form is recognized by horizontal readers at a glance.
CSS text-orientation — property overview
text-orientation is only meaningful within a vertical writing mode. It has three values: mixed (default) rotates Latin characters 90° clockwise within the vertical inline flow — so a horizontal Latin word is displayed as a sideways column; upright renders each Latin character as an upright glyph in the vertical flow — each character is right-side up but descends downward, so the word reads as individual characters top-to-bottom; sideways is equivalent to mixed but forces all characters (including CJK) to rotate sideways. The property affects only the character orientation, not the reading direction of the overall text flow — that is set by writing-mode.
Attack 1: text-orientation:mixed in vertical flow — Latin permission labels rotate 90° clockwise
The default value mixed in a vertical writing mode rotates Latin characters 90° clockwise. This means a horizontal English permission label like "EXECUTE" or "READ FILESYSTEM" is displayed sideways — the text runs top-to-bottom but each letter is individually rotated 90° clockwise, so you need to tilt your head 90° to the right to read it:
/* MCP server injects vertical writing mode with mixed orientation */
.consent-permission-list,
.scope-disclosure,
.permission-container {
writing-mode: vertical-rl;
/* text-orientation: mixed; — this is the default */
}
/* Effect: each Latin character in permission labels is rotated 90° CW.
"READ" in vertical-rl with mixed: four characters, each rotated 90° CW,
stacked vertically. The word reads sideways when tilting head 90° right.
A user scanning a consent UI horizontally does not read sideways text.
They see a vertical column of sideways characters — not a permission label.
DOM text: "READ" — correct.
Computed accessible name: "READ" — correct.
Visual rendering for horizontal reader: unreadable at normal scan speed. */
/* More effective: narrow container forces single vertical column: */
.scope-badge {
writing-mode: vertical-rl;
width: 20px;
overflow: hidden;
/* Permission badge now 20px wide, text flows downward, rotated sideways.
In a horizontal layout, this badge is just a 20px wide column of sideways text.
Users see a narrow vertical design element — not a permission scope label. */
}
Rotated text at normal scan speed is not read. Human peripheral vision pattern-matches upright horizontal text shapes. Sideways text requires deliberate head or eye rotation. In a consent flow where the user is scanning at 200–400ms per item, sideways text is registered as a graphical element, not as a word.
Attack 2: text-orientation:upright — each Latin character displayed individually in vertical descent
With text-orientation:upright in a vertical writing mode, each Latin character is kept upright (not rotated) but the flow direction is vertical — each character occupies its own position descending downward, one character per line-advance. This means "READ" appears as R (top), E, A, D (bottom) — each character is right-side-up but they are stacked vertically, not horizontal. The word shape is destroyed:
/* text-orientation:upright in vertical-rl:
Each Latin character is upright (not rotated) but descends one per line-advance.
"READ" → R (top) / E / A / D (bottom) — a vertical column of four upright characters.
Each character looks like a normal letter, but the sequence reads top-to-bottom.
A Western reader reads left-to-right — they do not read a vertical column as a word.
They may read it as "R, E, A, D" (individual letters) or not read it at all. */
.permission-scope-label {
writing-mode: vertical-rl;
text-orientation: upright;
}
/* "EXECUTE" → E / X / E / C / U / T / E descending.
The word shape is 7 characters tall, 1 character wide.
Width: approximately the width of the widest character in the word.
A user scanning the consent dialog sees a narrow vertical column of individual chars.
The word "EXECUTE" is not recognized as a permission scope label. */
/* Even shorter words become unrecognizable: */
/* "DO" → D / O — two stacked characters. Does not read as "DO" horizontally.
"OK" → O / K — looks like two stacked circle-and-angle design elements.
"DENY" → D / E / N / Y — four stacked single chars, reads as initials not a word. */
Attack 3: writing-mode:sideways-lr — all text rotated 90° counter-clockwise
The sideways-lr value causes text to flow bottom-to-top (the block direction is left-to-right but the inline direction is bottom-to-top within each column). All characters, including Latin, are rotated 90° counter-clockwise. A consent warning reads correctly only when viewed from the right side (tilting head 90° left). This is different from vertical-rl + mixed which is 90° CW — sideways-lr is 90° CCW:
/* sideways-lr: all characters rotated 90° counter-clockwise.
Text reads bottom-to-top within each vertical column.
To read normally: tilt head 90° left (lean left).
This is the opposite rotation from vertical-rl + mixed. */
.security-notice,
.warning-banner,
.critical-disclosure {
writing-mode: sideways-lr;
}
/* The entire security disclosure section is now bottom-to-top text.
Users see a vertical column of counter-clockwise-rotated text.
They need to rotate their head left to read it.
In a fast-paced consent flow, they do not do this.
The visual appearance is a decorative vertical design element. */
/* Mixing orientations: one section vertical-rl (90° CW), one sideways-lr (90° CCW).
Different sections of the consent UI are rotated in opposite directions.
The visual result is incoherent — decorative vertical columns with no readable text. */
Attack 4: text-orientation + font-size interaction displacing consent UI elements
In vertical writing modes with text-orientation:upright, Latin characters are upright and the browser allocates the character's advance width as the line height (block-axis dimension). When font-size is manipulated alongside text-orientation, the block-axis size of each character differs from the surrounding element expectations, causing layout displacement:
/* font-size interaction with upright orientation in vertical flow */
.consent-container {
writing-mode: vertical-rl;
}
.permission-item {
text-orientation: upright;
font-size: 2em; /* inflate size — each upright char takes 2x block-axis space */
/* In a fixed-height consent container, inflated upright text overflows.
With overflow:hidden, only the top N characters of each permission word visible.
"EXECUTE" may show only E/X before being clipped by the container boundary. */
}
.disclosure-text {
text-orientation: mixed;
font-size: 0.3em; /* shrink sideways-rotated text to near-unreadable size */
/* Sideways text at 0.3em (4.8px) is illegible — characters too small to read.
Combined with sideways rotation, the text is doubly unreadable:
both sideways and too small. */
}
| Attack | Prerequisite | What it enables | Severity |
|---|---|---|---|
| writing-mode:vertical-rl + text-orientation:mixed on permission label — Latin text rotated 90° CW within vertical flow, word not readable at normal horizontal scan speed | CSS injection setting writing-mode:vertical-rl on the permission list container; text-orientation:mixed is the default, no additional property needed; effective on any Latin-script permission label in the consent UI | Permission scope labels (READ, WRITE, EXECUTE) are displayed as 90°-clockwise-rotated text in a vertical column — word shape unrecognized at horizontal scan speed; users see a vertical design element and do not register the permission scope; DOM and accessibility tree are unaffected | HIGH |
| text-orientation:upright in vertical-rl — each Latin character displayed as standalone upright glyph descending vertically, word not recognized as a unit | CSS injection setting writing-mode:vertical-rl + text-orientation:upright on permission scope elements; all browsers since Chrome 48 / Firefox 41 / Safari 10.1 | Permission scope word is rendered as a vertical sequence of individual upright characters — R/E/A/D descending; word recognition fails (Western readers read left-to-right, not top-to-bottom); users may read the individual characters without recognizing the permission scope word they spell | HIGH |
| writing-mode:sideways-lr on security notice — text rotated 90° CCW, readable only when tilting head left | CSS injection of writing-mode:sideways-lr on the security disclosure or warning banner container; supported in Chrome 79+, Firefox 89+, Safari 14.1+ | Entire security notice is rotated 90° counter-clockwise; requires head tilt left to read; in fast consent flow, text is perceived as a decorative vertical column; users do not read it | HIGH |
| text-orientation + font-size interaction — block-axis size mismatch causes disclosure content to overflow and clip in fixed-height containers | CSS injection setting both text-orientation and font-size on children within a fixed-height consent container with overflow:hidden; the block-axis size inflation pushes disclosures beyond the visible region | Fixed-height consent container clips security disclosures when font-size is inflated in upright orientation; only partial content visible (first N characters of each permission word before overflow clip); full disclosure not visible to sighted user | MEDIUM |
Defences
- Freeze
writing-modeandtext-orientationon consent UI elements with!important. Addwriting-mode: horizontal-tb !important; text-orientation: mixed !important;to all security-critical containers and labels. This prevents injected vertical writing mode from applying to permission scope labels and security notices. - Computed style check: flag any non-horizontal writing mode in the consent container. Run
getComputedStyle(el).writingModeon all elements in the consent flow. Any value other thanhorizontal-tbon an element containing permission keywords or financial amounts is flagged HIGH. - CSP
style-srcnonce. Prevents injection of any<style>block settingwriting-modeortext-orientation. - SkillAudit flags:
writing-modeother thanhorizontal-tbon any element in the consent UI;text-orientation:uprightwithwriting-mode:vertical-*on permission scope elements;sideways-lron any element containing disclosure text.
SkillAudit findings for this attack surface
Related: CSS writing-modes security covers the parent writing-mode property and its four attack surfaces. CSS direction security covers RTL text reversal and BiDi attacks. CSS letter-spacing security covers inter-character spacing attacks. CSS injection overview covers the broader attack model.