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. */
}
AttackPrerequisiteWhat it enablesSeverity
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 speedCSS 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 UIPermission 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 unaffectedHIGH
text-orientation:upright in vertical-rl — each Latin character displayed as standalone upright glyph descending vertically, word not recognized as a unitCSS injection setting writing-mode:vertical-rl + text-orientation:upright on permission scope elements; all browsers since Chrome 48 / Firefox 41 / Safari 10.1Permission 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 spellHIGH
writing-mode:sideways-lr on security notice — text rotated 90° CCW, readable only when tilting head leftCSS 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 itHIGH
text-orientation + font-size interaction — block-axis size mismatch causes disclosure content to overflow and clip in fixed-height containersCSS 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 regionFixed-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 userMEDIUM

Defences

SkillAudit findings for this attack surface

HIGHwriting-mode:vertical-rl on permission scope list rotates all Latin labels 90° CW — permission words not recognized at horizontal scan speed: MCP server injects writing-mode:vertical-rl on the permission list container; all Latin permission scope labels (READ, WRITE, EXECUTE) are displayed as 90°-CW-rotated sideways columns; horizontal readers do not register sideways text as permission labels during consent flow scan; DOM and accessibility tree are fully intact
HIGHtext-orientation:upright in vertical-rl on EXECUTE scope badge — word rendered as R/E/A/D/... descending column, word recognition fails: MCP server sets text-orientation:upright on permission scope badges within a vertical writing mode; each character is upright but the sequence descends vertically; "EXECUTE" appears as seven individual upright characters descending, not as a horizontal word; users read individual initials, not the permission scope word
HIGHwriting-mode:sideways-lr on security disclosure section rotates entire notice 90° CCW — users perceive a decorative vertical column, not readable text: MCP server injects writing-mode:sideways-lr on the security notice container; all text is rotated 90° counter-clockwise; readable only with head tilt left; in fast-paced consent flow, users skip the vertical design-element and proceed without reading the disclosure
MEDIUMtext-orientation:upright + font-size:2em in fixed-height container clips permission scope words after first 2 characters — disclosure content exceeds container boundary: MCP server injects inflated font-size in upright vertical orientation; block-axis dimension per character is 2× expected; fixed-height consent container with overflow:hidden clips each permission word after the first 2 visible characters; "EXECUTE" shows only "EX" before being clipped

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.

← Blog  |  Security Checklist