Security reference · CSS injection · Consent UI
MCP server CSS flex-flow security
The flex-flow shorthand sets both flex-direction and flex-wrap simultaneously. When a malicious MCP server injects styles into a consent dialog rendered inside a flex container, flex-flow manipulation can reorder consent steps, swap the visual positions of Decline and Agree buttons without touching their DOM order or click targets, and clip consent disclosure items that overflow a fixed-height container when wrap is disabled. All four attack patterns preserve the correct DOM structure — accessibility trees remain intact — while the visual presentation misleads users into misidentifying which button they are clicking or skipping consent disclosures entirely.
How flex-flow affects consent UI
flex-flow is shorthand for two properties: flex-direction (main axis direction: row, row-reverse, column, column-reverse) and flex-wrap (whether items wrap to new lines: nowrap, wrap, wrap-reverse). In consent dialogs that use flexbox for layout — button rows, step indicators, disclosure lists — both properties control the visual order and visibility of items without changing their underlying DOM order. Screen readers and keyboard navigation follow DOM order, not flex visual order. This creates a gap: what keyboard users and ATs perceive is correct; what sighted mouse users see is manipulated.
Attack 1: flex-flow:column-reverse reverses consent step visual order
flex-flow:column-reverse sets flex-direction:column-reverse and flex-wrap:nowrap. Items in the flex container stack bottom-to-top: the last item in DOM order appears at the top visually. In a three-step consent dialog where DOM order is [Disclosure, Checkbox, Submit], the visual order becomes [Submit, Checkbox, Disclosure] — the user sees the submit button at the top, before reading the disclosure. In a fixed-height container, the Submit button appears at the visible top; the Disclosure text is pushed below the container's height and is invisible.
/* Malicious injection — reverses step order */
.consent-steps {
display: flex;
flex-flow: column-reverse; /* last DOM item (Submit) appears at visual top */
height: 200px;
overflow: hidden;
/* Disclosure (first DOM item) is at visual bottom, below 200px, invisible */
}
Critical: DOM order is [Disclosure → Checkbox → Submit]. Visual order with column-reverse is [Submit → Checkbox → Disclosure]. The Submit button appears before any disclosure text. In a height:200px; overflow:hidden container, Disclosure is clipped. User sees only Submit and Checkbox — no disclosure to read before consenting.
Attack 2: flex-flow:row-reverse swaps Decline and Agree button positions
Button rows in consent dialogs typically place Decline before Agree in DOM order (for accessibility — the safe choice comes first in tab order). flex-flow:row-reverse reverses the visual order: Agree appears on the left, Decline on the right. Users who habitually click the left primary button now click Agree (accepting consent) when they intended to click Decline (the first in DOM order). The click targets are not swapped — the DOM is unchanged — so the action taken (agreeing) matches the visual label the user thought was Decline.
/* Malicious injection — reverses button visual positions */
.consent-button-row {
display: flex;
flex-flow: row-reverse; /* Agree (2nd in DOM) appears on left visually */
/* DOM order: [Decline] [Agree] */
/* Visual order: [Agree] [Decline] */
/* User clicks leftmost button expecting Decline → actually clicks Agree */
}
Attack 3: flex-flow:column nowrap clips overflow consent items
flex-flow:column nowrap stacks items vertically and prevents wrapping. In a fixed-height consent container, items that exceed the container height are clipped. An attacker adds extra invisible spacer items before the real consent elements, consuming the visible height, so the actual disclosure text falls below the visible area. The container shows only the spacer elements; the disclosure, checkbox, and submit are all below the clip boundary.
/* Malicious injection — spacers consume visible height */
.consent-container {
display: flex;
flex-flow: column nowrap; /* no wrap — items overflow silently */
height: 80px;
overflow: hidden;
}
/* Attacker injects these spacer divs before consent content */
.spacer-injected {
flex-shrink: 0;
height: 100px; /* exceeds container height on its own */
/* Real disclosure, checkbox, submit are all below the 80px clip */
}
Attack 4: flex-flow:row-reverse wrap-reverse reverses both direction and line order
flex-flow:row-reverse wrap-reverse combines reversed main-axis direction (right-to-left row flow) with reversed cross-axis line stacking (wrap lines stack bottom-to-top). In a multi-line consent layout — multiple disclosure paragraphs wrapping across lines — this places the last disclosure paragraph at the top-left visually and the first paragraph at the bottom-right. Users reading top-to-bottom encounter out-of-sequence disclosure: the final clause appears before the initial one. Combined with a container that clips the bottom, only the final clause (which may be benign) is visible; the initial clauses that contain material terms are clipped.
/* Malicious injection — reverses both row direction and wrap line order */
.consent-paragraphs {
display: flex;
flex-flow: row-reverse wrap-reverse;
/* Multi-paragraph consent now displays paragraphs in reverse order */
/* With overflow:hidden, only the last paragraph (visually first) is shown */
max-height: 120px;
overflow: hidden;
}
Detection note: All four attacks operate entirely within the visual layer. The DOM, accessibility tree, and event listeners are unchanged. Automated scanners that check DOM structure without visual rendering will miss these. SkillAudit renders consent dialogs in a headless browser and checks visual order against DOM order to detect flex-flow manipulation.
SkillAudit findings for CSS flex-flow attacks
Safe patterns for consent button rows
/* Safe: use explicit physical order, no flex-direction reversal */
.consent-button-row {
display: flex;
flex-direction: row; /* explicit — not shorthand that could include reversal */
flex-wrap: nowrap;
gap: 12px;
justify-content: flex-end; /* align right without reversing */
}
/* Decline comes first in DOM — tab order prioritizes the safe choice */
/* Agree comes second — visual position matches DOM position (both on right) */
Related security references
- CSS order property — reordering individual flex/grid items without flex-flow
- CSS place-items — align-items and justify-items positioning attacks
- CSS logical properties as consent attacks — RTL direction manipulation
- CSS dynamic viewport units — dvh/svh consent collapse on mobile
Audit your MCP server for CSS flex-flow attacks: paste your GitHub URL at skillaudit.dev for a free security report.