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. */
}
| Attack | Prerequisite | What it enables | Severity |
|---|---|---|---|
| vertical-align:super on trailing payment digit shifts it above baseline — "100" reads as "10⁰" = 1; perceived value dramatically lower than actual | CSS 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 targeting | The 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 value | HIGH |
| vertical-align:sub on warning banner inline text demotes it to subscript appearance — text visual prominence reduced, reads as fine print annotation | CSS 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 level | MEDIUM |
| 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 disabled | CSS injection targeting even/odd spans within permission notice text elements; requires per-word span structure in the host DOM | Permission 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 unit | HIGH |
| vertical-align:bottom in tall table cells with overflow:hidden pushes security status badges to cell bottom — hidden in cells taller than the visible region | CSS 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-height | Security 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
- CSP
style-srcwith nonce. Prevents injection of<style>blocks settingvertical-align. The complete solution. - Monitor computed
vertical-alignon security-critical inline elements. Any value other thanbaseline,middle, ortopon payment amount digits, permission labels, or audit status badges should be flagged.superandsubon digit spans have no legitimate use on financial displays. - Avoid per-character span structures in security-critical amount displays. Per-character or per-digit span wrappers provide targeting surface for
vertical-aligninjection. Render payment amounts as a single text node where possible, or use canvas rendering for final confirmation screens. - Set explicit
vertical-align:middlewith!importanton table status cells. Prevent bottom-alignment hiding by freezing the vertical alignment of status badge cells. - SkillAudit flags:
vertical-align:superorvertical-align:subon any element whose text content contains digits;vertical-alignalternating between super and sub on sibling inline elements within a paragraph;vertical-align:bottomon table cells in a permission or audit status table.
SkillAudit findings for this attack surface
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.