MCP Security Reference

MCP server CSS writing-mode consent security

CSS writing-mode controls the direction in which inline content flows: horizontal left-to-right (normal), vertical top-to-bottom, or sideways. Applied to consent text, non-horizontal writing modes produce text that is visually unreadable for horizontal-language users while keeping the DOM text node entirely intact. Beyond the obvious rotate(90deg) transform, writing-mode attacks are harder to detect because they alter the text's logical box model, affect line-height behaviour, and interact with text-orientation to produce variants that pass single-character detection checks.

Attack findings

HIGHSA-CSS-WMC-001 — writing-mode:vertical-lr on a narrow-height container stacks consent text as a top-to-bottom column; natural horizontal reading is impossible; displayed within a fixed 40px wide consent area
HIGHSA-CSS-WMC-002 — writing-mode:sideways-rl produces physically rotated text rendered upside-down; each character is individually legible upside-down but requires physically inverting the screen to read as prose
HIGHSA-CSS-WMC-003 — writing-mode:vertical-rl combined with text-orientation:upright produces individual upright (un-rotated) Latin characters stacked vertically; single-character detection sees valid ASCII; word-level reading is impossible
MEDIUMSA-CSS-WMC-004 — JS mouseenter on install button transitions writing-mode from horizontal-tb to vertical-lr; consent appears readable at page load and becomes unreadable only when user moves to click

Background: CSS writing-mode values

The CSS Writing Modes Level 4 specification defines five values for writing-mode: horizontal-tb (default — left to right, top to bottom), vertical-rl (top to bottom, right to left column advance), vertical-lr (top to bottom, left to right column advance), sideways-rl (characters rotated 90° clockwise, column advance right to left), and sideways-lr (characters rotated 90° counter-clockwise, column advance left to right). The property was designed for CJK typography and Arabic script vertical layout. When applied to horizontal-script consent text in a European language, any non-horizontal-tb value produces unreadable output.

Interaction with text-orientation: The text-orientation property further modifies how characters are rendered within a vertical writing mode. With text-orientation:upright, Latin characters are rotated 0° (upright) instead of the default 90° — producing a stacked column of individual upright letters rather than a sideways string. Both forms are unreadable as prose at any reasonable column width.

Attack 1 — writing-mode:vertical-lr top-to-bottom column text (SA-CSS-WMC-001)

With writing-mode:vertical-lr, the inline direction becomes top-to-bottom and the block direction advances left to right. For a consent paragraph, this means each character is stacked vertically: "B" then "y" then " " then "c" then "l"... The text reads as a narrow column from top to bottom. For a fluent reader of horizontal Latin script, the reading path is entirely broken — eyes must track vertically across single characters. The attack is most effective when the container is given a fixed 40–60px width (enough for one character column) so there is no horizontal spillover to accidentally form readable fragments.

/* Attack: vertical-lr stacks consent characters top-to-bottom */
.consent-dialog .consent-text {
  writing-mode: vertical-lr;
  width: 40px;          /* one character column width */
  overflow: hidden;      /* clip any spillover */
  height: 200px;         /* container height — users see top 200px of the column */
}
/* DOM: "By clicking Install you authorize this server to read your files."
   Rendered: single column of characters — B, y, (space), c, l, i, c, k... top to bottom
   textContent: unaffected — full string returned */

SA-CSS-WMC-001 (High). Detection: getComputedStyle(el).writingMode returns a string. Flag any value other than 'horizontal-tb' on an element whose textContent contains consent keywords ('authorize', 'grant', 'access', 'permission', 'agree'). The writing-mode value for a consent element should always be horizontal-tb for any horizontal-script language.

/* Detection */
const CONSENT_KEYWORDS = ['authorize', 'grant', 'access', 'permission', 'agree', 'install'];
function checkWritingModeConsent(el) {
  const text = el.textContent.toLowerCase();
  const isConsent = CONSENT_KEYWORDS.some(k => text.includes(k));
  if (!isConsent) return null;
  const wm = getComputedStyle(el).writingMode;
  if (wm && wm !== 'horizontal-tb') {
    return { vuln: 'SA-CSS-WMC-001', detail: `writingMode:${wm}` };
  }
  return null;
}

Attack 2 — writing-mode:sideways-rl produces upside-down text (SA-CSS-WMC-002)

writing-mode:sideways-rl renders characters rotated 90° clockwise and advances columns right-to-left. For Latin script, this produces text that runs from bottom to top with each character physically rotated — not mirrored, but rotated — so that reading the consent requires tilting one's head or screen 90° to the right. Unlike a CSS transform:rotate(90deg) which rotates the element's rendering box, sideways-rl is a layout-level change that causes the element to participate in normal flow vertically. Screen readers and accessibility tools read the DOM text; visual auditors see correct characters in the accessibility tree. Only the rendered visual output is rotated.

/* Attack: sideways-rl rotates consent text 90° clockwise (upside-down to read) */
.consent-text {
  writing-mode: sideways-rl;
  /* no other changes needed — layout handles the rotation */
}
/* Rendered: consent text displayed running bottom-to-top, rotated 90° right
   User must physically tilt head or device to read
   Accessibility tree: unaffected — textContent is correct
   getComputedStyle().writingMode: 'sideways-rl' */

SA-CSS-WMC-002 (High). sideways-rl and sideways-lr are the most aggressive writing-mode attacks because the characters are rotated at the glyph level. They also affect the element's layout box dimensions — the element becomes tall and narrow instead of short and wide — which can cause it to overflow its container and become partially or fully hidden depending on overflow settings.

Attack 3 — vertical-rl + text-orientation:upright stacks characters without rotating (SA-CSS-WMC-003)

The text-orientation:upright value, when combined with a vertical writing mode, renders each character upright (0° rotation) rather than rotated 90°. The result is a column of individual Latin letters, each correctly oriented but stacked vertically. The word "authorize" becomes a vertical column: A, u, t, h, o, r, i, z, e — nine separate upright characters stacked top to bottom. A consent checker that validates individual character encoding and correctness (no Unicode substitutions, no invisible characters) passes this attack because each character is the original, correct, unmodified glyph. The consent is illegible as prose only because the spatial arrangement of characters does not match horizontal reading patterns. This is the subtlest of the four attacks because both the DOM and the rendered glyphs are technically accurate.

/* Attack: vertical stacking of upright characters — correct glyphs, illegible as prose */
.consent-text {
  writing-mode: vertical-rl;
  text-orientation: upright;
  /* result: A, u, t, h, o, r, i, z, e stacked vertically — each char correctly rendered */
  /* no Unicode manipulation — character-level checks pass */
  /* prose reading is impossible — characters must be read as vertical list */
}
/* Accessibility tree: correct characters in correct order
   Visual rendering: vertical stack of unrotated Latin letters
   Single-character auditors: PASS
   Prose-reading humans: FAIL */

SA-CSS-WMC-003 (High). Detection requires checking both writingMode and textOrientation together. Flag the combination of any vertical writing-mode (vertical-rl, vertical-lr) with text-orientation:upright on a consent element. The upright combination is the most likely to evade character-level auditors.

/* Detection — vertical writing mode + upright orientation combination */
function checkWritingModeUpright(el) {
  const cs = getComputedStyle(el);
  const wm = cs.writingMode;
  const to = cs.textOrientation;
  if ((wm === 'vertical-rl' || wm === 'vertical-lr') && to === 'upright') {
    return { vuln: 'SA-CSS-WMC-003', detail: `writingMode:${wm}, textOrientation:${to}` };
  }
  return null;
}

Attack 4 — JS mouseenter triggers writing-mode transition at install hover (SA-CSS-WMC-004)

At page load, the consent element has the default writing-mode:horizontal-tb. The consent is readable. When the user's mouse enters the install button, a JavaScript handler sets writing-mode:vertical-lr on the consent element. CSS does not interpolate writing-mode (it is not an animatable property) — the change is instantaneous. The user's gaze is now on the install button, not the consent text that has just become unreadable. The mutation is reversed on mouseleave so a post-install inspection of the page source shows nothing anomalous. Static CSS analysis passes because the rule is never in the stylesheet — it exists only in the inline style injected by JS during the interaction window.

/* Attack: writing-mode injection at install hover */
installBtn.addEventListener('mouseenter', () => {
  consentEl.style.writingMode = 'vertical-lr';
});
installBtn.addEventListener('mouseleave', () => {
  consentEl.style.writingMode = '';  /* revert — DOM looks clean after interaction */
});

/* Detection: MutationObserver on consent element style */
new MutationObserver(() => {
  const wm = getComputedStyle(consentEl).writingMode;
  if (wm !== 'horizontal-tb') {
    flagTampering('SA-CSS-WMC-004');
    installBtn.disabled = true;
  }
}).observe(consentEl, { attributes: true, attributeFilter: ['style'] });

SkillAudit detection: SkillAudit checks the computed writingMode and textOrientation on all consent-bearing elements. It flags non-horizontal-tb writing modes, the vertical-rl+upright combination, and both sideways-rl and sideways-lr values. During dynamic analysis it installs a MutationObserver on consent elements to catch hover-triggered writing-mode injection. Run a free audit →

Detection summary

Attack IDProperties involvedKey detection signal
SA-CSS-WMC-001writing-mode:vertical-lr + narrow widthwritingMode !== 'horizontal-tb' on consent element
SA-CSS-WMC-002writing-mode:sideways-rlwritingMode === 'sideways-rl' or 'sideways-lr'
SA-CSS-WMC-003writing-mode:vertical-rl + text-orientation:uprightvertical writing-mode AND textOrientation === 'upright'
SA-CSS-WMC-004JS mouseenter sets writing-mode:vertical-lrMutationObserver style change → writingMode check during mouse interaction