← Blog

MCP server CSS writing-mode and direction consent security

Four SA-CSS-WMD consent attack patterns exploiting CSS writing-mode and direction properties to render consent text in physically rotated or character-reversed layouts that humans cannot process as normal consent. The text is visually present with correct contrast and non-zero pixel dimensions, but spatial orientation makes it practically unreadable. getComputedStyle checks for color, opacity, and visibility all pass; the attack lives entirely in text flow direction.

By SkillAudit  |  30 Oct 2026

CSS writing-mode controls whether text flows horizontally or vertically and in which direction. The property was designed for CJK (Chinese/Japanese/Korean) typographic systems where vertical text is conventional. In a consent dialog context, an attacker can use writing-mode: vertical-rl to rotate English consent text 90° clockwise, so that it reads top-to-bottom instead of left-to-right. The text is physically present, has good WCAG contrast, and the element has positive dimensions — but a typical user reading a Latin-script dialog will not process 90°-rotated text as consent before clicking the Accept button below it.

SA-CSS-WMD-001: writing-mode:vertical-rl — 90° clockwise rotation

writing-mode: vertical-rl rotates the text block 90° clockwise: text flows from top to bottom, with successive lines flowing from right to left. For an English sentence like “By clicking Allow you grant this MCP server file system access,” the result is a narrow vertical column where each character is physically rotated, reading down the column. The element occupies a tall, narrow box instead of a wide, short one. Layout dimensions change, which may push UI elements around the dialog.

.consent-text {
  writing-mode: vertical-rl;
  /* text now flows top-to-bottom, lines right-to-left */
  /* each character is upright but the reading direction is vertical */
}

/* Resulting layout:
   Normal: [By clicking Allow you grant this MCP server access.]
           (400px wide, 20px tall)

   vertical-rl: B     ← characters stacked vertically
                y         (each character occupies ~14px in the block direction)

                c     total height ≈ 40 chars × 14px = 560px
                l     element width ≈ 1 line × 14px = ~20px wide
                ...

   getComputedStyle checks:
   - color: #111111 → passes
   - opacity: 1 → passes
   - visibility: visible → passes
   - writing-mode: vertical-rl → DETECTABLE if checked

   Detection: check getComputedStyle(el).writingMode for any non-'horizontal-tb' value
   on consent-critical text elements.
*/

Rotated text passes all color/visibility checks: writing-mode: vertical-rl does not affect color, opacity, visibility, or any property that first-generation audit engines check. The text has correct contrast. The element has positive layout dimensions. Only checking writing-mode and text-orientation properties — or computing the text's reading direction relative to the UI's primary language direction — will detect this attack.

CRITICAL
SA-CSS-WMD-001 — writing-mode: vertical-rl on consent text: rotates English text 90° clockwise to vertical top-to-bottom layout; correct WCAG contrast; passes color/opacity/visibility checks. Detection: getComputedStyle(el).writingMode !== 'horizontal-tb' on consent element → CRITICAL.

SA-CSS-WMD-002: writing-mode:sideways-lr — 90° counterclockwise rotation

writing-mode: sideways-lr rotates text 90° counterclockwise: characters are displayed on their side with the top pointing left. This is the mirror of vertical-rl in terms of rotation direction. For consent text, the practical effect is the same: English text cannot be read without rotating one's head 90° counterclockwise. The difference from vertical-rl is in the direction of the rotation, which a human might perceive as even less natural than the clockwise direction.

.consent-text {
  writing-mode: sideways-lr;
  /* text flows bottom-to-top, characters rotated 90° counterclockwise */
  /* the top of each character points to the left */
}

/* writing-mode values that rotate text:
   - horizontal-tb: normal horizontal left-to-right (default)
   - vertical-rl:   90° clockwise rotation
   - vertical-lr:   90° clockwise, lines flow left-to-right (rare)
   - sideways-rl:   90° clockwise (same visual as vertical-rl for LTR text)
   - sideways-lr:   90° counterclockwise

   All non-horizontal-tb values should be flagged for consent text.

   Browser support note:
   - horizontal-tb, vertical-rl, vertical-lr: widely supported
   - sideways-rl, sideways-lr: supported in Firefox, Chrome, Edge since ~2020
   Scanner should flag all non-horizontal values regardless of current support. */
CRITICAL
SA-CSS-WMD-002 — writing-mode: sideways-lr on consent text: rotates English text 90° counterclockwise; unreadable without head tilt; same contrast profile as normal text. Detection: any non-horizontal-tb writing-mode on consent-critical elements → CRITICAL.

SA-CSS-WMD-003: direction:rtl + unicode-bidi:bidi-override — character order reversal

direction: rtl sets the base text direction to right-to-left. Combined with unicode-bidi: bidi-override, this forces all characters to be rendered in reverse order within each line, overriding the Unicode Bidirectional Algorithm. The string “By clicking Allow you grant access.” becomes “.ssecca tnarg uoy wollaA gnikkilc yB” — each character individually present but in mirror-reversed sequence. A user reading the dialog sees unfamiliar character patterns and may not recognize the reversed English as a consent statement.

.consent-text {
  direction: rtl;
  unicode-bidi: bidi-override;
  /* Forces right-to-left rendering with override of Unicode bidi algorithm */
  /* Characters render in reverse order: "Allow" → "wollaA" */
}

/* Example transformation:
   Original: "By clicking Allow you grant this MCP server file system access."
   Rendered: ".ssecca metsys elif revres PCM siht tnarg uoy wollaA gnikkilc yB"

   Each character is visually the same (no rotation of individual glyphs).
   The sequence is reversed.
   A native English speaker encountering this text will not parse it as consent.

   Contrast and opacity: unchanged. WCAG passes.
   Font size: unchanged.
   Visibility: visible.

   Detection:
   - getComputedStyle(el).direction === 'rtl' (detectable)
   - getComputedStyle(el).unicodeBidi === 'bidi-override' (detectable)
   Both together on a consent element → SA-CSS-WMD-003 CRITICAL
   direction:rtl alone without bidi-override is less severe (word order reversal,
   characters within words remain LTR for LTR scripts). */

direction:rtl without bidi-override: direction: rtl alone reverses the word order within the line (last word appears first) but does not reverse individual characters within each word for LTR scripts. Combined with unicode-bidi: bidi-override, individual characters are also reversed. The full combination is a more severe attack. Scanners should flag direction: rtl on its own (word order confusion) and flag the full combination (character reversal) at higher severity.

CRITICAL
SA-CSS-WMD-003 — direction: rtl + unicode-bidi: bidi-override: full character-order reversal; “Allow access” → “ssecca wollaA”; English speakers cannot parse reversed text as consent. Detection: direction === 'rtl' + unicodeBidi === 'bidi-override' on consent element → CRITICAL.

SA-CSS-WMD-004: writing-mode:vertical-rl + transform:rotate(180deg) — upside-down vertical

Combining writing-mode: vertical-rl with transform: rotate(180deg) applies two rotations: vertical-rl rotates the text block 90° clockwise (top-to-bottom reading), and rotate(180deg) applies an additional 180° rotation to the entire element. The net result is text that reads bottom-to-top (vertical) with characters upside-down (rotated 180° relative to their normal orientation). This double transformation is harder for a user to mentally decode than a single 90° rotation. CSS transform detection must account for compounded transformations from both writing-mode and transform.

.consent-text {
  writing-mode: vertical-rl; /* 90° CW rotation */
  transform: rotate(180deg);  /* additional 180° rotation */
  /* Net: 90° + 180° = 270° → text reads bottom-to-top, characters upside-down */
}

/* Combined rotation analysis:
   writing-mode:vertical-rl alone: text reads top-to-bottom (90° CW)
   + transform:rotate(180deg): element rotated 180° CW on top of that
   Net rotation of text content relative to horizontal: 90° + 180° = 270°
   = same as 90° counterclockwise

   Characters are individually upside-down (180° rotated).
   Text column reads from bottom to top.

   Detection:
   Step 1: detect writing-mode !== 'horizontal-tb' → flag rotation from writing-mode
   Step 2: detect transform matrix rotation → decompose DOMMatrix
   Step 3: combine both rotations → net text rotation angle
   If net rotation ≠ 0° and ≠ 360° → flag as orientation attack

   Note: CSS transform on its own (without writing-mode) was covered in SA-CSS-TRV.
   This pattern combines two rotation mechanisms. */
HIGH
SA-CSS-WMD-004 — writing-mode: vertical-rl + transform: rotate(180deg): net 270° rotation (text reads bottom-to-top, chars upside-down); compounded rotations harder to reverse-read than single rotation. Detection: combine writing-mode rotation angle and CSS transform matrix decomposition; flag if net rotation ≠ 0° mod 360°.

Summary of SA-CSS-WMD findings

IDSeverityPropertiesEffect on consent textDetection method
SA-CSS-WMD-001 CRITICAL writing-mode: vertical-rl 90° CW rotation, reads top-to-bottom writingMode !== 'horizontal-tb'
SA-CSS-WMD-002 CRITICAL writing-mode: sideways-lr 90° CCW rotation, reads bottom-to-top writingMode !== 'horizontal-tb'
SA-CSS-WMD-003 CRITICAL direction: rtl + unicode-bidi: bidi-override Full character-order reversal within each line direction=rtl + unicodeBidi=bidi-override
SA-CSS-WMD-004 HIGH writing-mode: vertical-rl + transform: rotate(180deg) Net 270° rotation, chars upside-down vertical Combined writing-mode + transform angle

Related pages