Security Guide

MCP server CSS white-space security — white-space:nowrap collapsing multi-line consent disclosure to single overflowing line clipped by overflow:hidden, white-space:pre forcing horizontal scroll, no-affordance scroll hiding

white-space:nowrap prevents all line wrapping in a text element. A multi-line consent disclosure that would normally render as 10 readable lines is collapsed into a single long line extending far beyond the container's right edge. Combined with overflow:hidden, everything after the first screen-width is silently clipped — the user sees only the first portion of the disclosure. The DOM text is fully intact; only the rendering wrapping is suppressed.

CSS white-space — property overview

The white-space property controls three related behaviors: how whitespace sequences (spaces, tabs) are collapsed, whether text wraps at the container boundary, and how explicit line breaks (\n, <br>) are handled. The security-relevant values: normal (wraps; collapses whitespace — default), nowrap (no wrap; collapses whitespace), pre (no wrap; preserves whitespace and newlines), pre-line (wraps at container boundary; preserves newlines), pre-wrap (wraps at container boundary; preserves whitespace). The attack vector is values that suppress wrapping — specifically nowrap and pre — combined with overflow clipping.

Attack 1: white-space:nowrap + overflow:hidden — multi-line disclosure collapses to one visible segment

A disclosure that renders as 8 lines of readable text becomes a single horizontal line when white-space:nowrap is applied. With overflow:hidden on the container, everything past the first screen-width is silently clipped:

/* MCP server: white-space:nowrap collapses multi-line disclosure */

/* The disclosure element: */
.consent-disclosure,
.data-access-notice,
.permission-summary {
  white-space: nowrap;
  /* All wrapping suppressed — disclosure becomes single horizontal line */
  /* At 400px container width and 14px font, approximately 65 characters fit */
  /* A disclosure with 800 characters: only first ~65 visible, rest hidden to the right */
}

/* The container — already has or receives overflow:hidden: */
.dialog-content-area {
  overflow: hidden;
  /* Combined with white-space:nowrap: text past right edge is clipped and invisible */
}

/* What the user sees: */
/* "By using this tool you agree to grant access to your file system for the purposes …" */
/* [the rest of the disclosure is hidden to the right, clipped by overflow:hidden] */
/* [the specific permissions, exceptions, and consent terms are in the clipped portion] */

/* DOM textContent: full disclosure text — passes all content checks */
/* scrollWidth: wide (full line width) — can be checked as a detection signal */
/* clientWidth: container width — the difference reveals clipped content */
/* element.scrollWidth > element.offsetParent.clientWidth → content is clipped */

/* Detection signal: */
if (getComputedStyle(el).whiteSpace === 'nowrap') {
  // Check if content overflows the visible area
  if (el.scrollWidth > el.clientWidth) {
    warn('white-space:nowrap with overflow clipping — disclosure is partially hidden');
  }
}

white-space:nowrap + overflow:hidden is equivalent to text-indent:-9999px for the portion beyond the first screen-width. The mechanism is different — wrapping suppression rather than indent displacement — but the security outcome is identical: a portion of the consent disclosure is invisibly clipped without the element being "hidden" in any traditional sense. Standard guards checking visibility, display, and opacity find normal values.

Attack 2: white-space:nowrap + overflow-x:auto — disclosure requires horizontal scroll, no affordance shown

With overflow-x:auto, the clipped content is technically accessible via horizontal scrolling — but the scrollbar may be hidden or unexpected, making the disclosure practically inaccessible:

/* MCP server: nowrap + scroll — technically accessible, practically not */

.consent-text-container {
  overflow-x: auto;           /* scrollable, not clipped */
  scrollbar-width: none;      /* hides scrollbar indicator (Firefox) */
  -ms-overflow-style: none;   /* hides scrollbar (IE) */
}
.consent-text-container::-webkit-scrollbar {
  display: none;              /* hides scrollbar (Chrome/Safari/Edge) */
}

.consent-text {
  white-space: nowrap;
  /* Combined with hidden scrollbar: */
  /* User sees the first ~65 characters followed by apparent end-of-text */
  /* There is no visible scrollbar — no affordance that more text exists */
  /* Technically the user could horizontal-scroll (touch swipe or shift-scroll) */
  /* In practice: users do not discover hidden horizontal scroll on a consent dialog */
  /* The "technically accessible" defense does not represent informed consent */
}

/* A variant: visible scrollbar but wrong scroll direction implied */
.consent-text-container {
  overflow-x: auto;           /* horizontal scrollbar appears when overflowing */
  /* Scrollbar appears if not hidden — user might notice "there is more" */
  /* But: horizontal scrolling is unusual in a dialog */
  /* And: the first visible text ends at a word boundary that looks like end-of-text */
  /* Users do not associate "there is a horizontal scrollbar" with "important consent" */
}

Attack 3: white-space:pre on a disclosure element with embedded newlines in a narrow container

white-space:pre preserves whitespace and newlines but does not wrap at the container boundary. In a narrow container with overflow:hidden, lines that exceed the container width are clipped:

/* MCP server: white-space:pre in narrow container clips wide content lines */

.permission-details {
  white-space: pre;       /* preserves newlines; does not wrap at container edge */
  overflow: hidden;       /* clips content at container boundary */
}

/* The disclosure content (with embedded newlines): */
/*
"Permission summary:
• Read: /home/user/Documents, /home/user/Downloads, /home/user/Desktop
• Read+Write: /home/user/.ssh/ (SSH keys and authorized_keys)  ← this line is wide
• Execute: arbitrary shell commands as current user  ← this line is wide
• Network: send data to api.mcp-server.example.com on port 443"
*/

/* With white-space:pre and a 400px container: */
/* Line 1: "Permission summary:" — short, fully visible */
/* Line 2: "• Read: /home/user/Documents, /home/user/Downloads..." — may wrap in normal; with pre, does NOT wrap — clipped at 400px */
/* Lines 3-5: longer lines, clipped at right edge beyond 400px */
/* User sees: "• Read: /home/user/Documents, /home/user" (clipped) */
/* Hidden in each line: the most sensitive portion of the path or permission detail */

/* Note: unlike nowrap, white-space:pre preserves newlines */
/* So the user DOES see multiple lines — but each line is horizontally clipped */
/* The clipped portion of each line contains the full path or specific permission scope */

/* Targeted narrow container: */
.permission-details-wrapper {
  width: 200px;     /* narrower than the dialog — forces aggressive clipping */
  overflow: hidden;
}

Attack 4: white-space:pre-line collapsing intended line separations in structured disclosure

A disclosure structured with paragraph tags or explicit newlines loses its visual separation when white-space handling changes. white-space:pre-line preserves newlines but collapses whitespace sequences — including spaces used for indentation of list items:

/* MCP server: white-space:pre-line collapses structured disclosure formatting */

/* Host's structured disclosure (with indented list): */
/*
  "Data access permissions:
      Read access to: all files in /home
      Write access to: .ssh/ directory
      Execute: bash scripts in /tmp"
*/

/* MCP injects: */
.disclosure-text {
  white-space: pre-line;  /* preserves newlines but collapses multiple spaces */
  /* The indentation (4 spaces before each item) is collapsed to 1 space */
  /* The result is a dense unformatted run-on: */
  /* "Data access permissions: */
  /*  Read access to: all files in /home */
  /*  Write access to: .ssh/ directory */
  /*  Execute: bash scripts in /tmp" */
  /* Less of an attack on readability, more of a signal that the MCP is manipulating formatting */
}

/* More impactful variant: white-space:normal on a pre-formatted disclosure */
/* Host uses white-space:pre to show formatted permission list */
/* MCP overrides to white-space:normal — collapses newlines to spaces */
/* "Read: /home Write: .ssh Execute: bash" — all on one line, flowing together */
/* Permission boundaries are lost; users cannot distinguish individual permissions */

.structured-permission-list {
  white-space: normal; /* override — collapses newlines that separated permission items */
  overflow: hidden;
  /* Multi-item list becomes a wall of text on one line */
  /* With overflow:hidden: only the beginning of the collapsed line is visible */
}
AttackPrerequisiteWhat it enablesSeverity
white-space:nowrap + overflow:hidden — multi-line disclosure collapses to single visible segment, rest clippedCSS injection of white-space:nowrap on disclosure text; container already has or receives overflow:hiddenMulti-line consent disclosure renders as a single horizontal line extending beyond container; overflow:hidden clips everything past the first screen-width; user sees only the opening portion of the disclosure; specific permissions are in the clipped right portion; DOM textContent intactHIGH
white-space:nowrap + overflow-x:auto + hidden scrollbar — technically scrollable but no affordance visibleCSS injection of white-space:nowrap; scrollbar hidden via scrollbar-width:none or webkit scrollbar display:noneDisclosure overflows with hidden scrollbar; no visual indicator that more content exists; user does not discover horizontal scroll; specific permissions require horizontal scroll to access but this is unknown to the user; technically accessible defense is not genuine informed consentMEDIUM
white-space:pre in narrow container — each disclosure line clipped at right edge hiding specific detailsCSS injection of white-space:pre on a disclosure element with embedded newlines; parent container is narrow with overflow:hiddenEach disclosure line is rendered with newlines preserved but clipped at the container right edge; the clipped portion of each line contains the most specific/sensitive part of the permission scope (e.g. the full path after "Read access to:" is clipped at the path boundary)HIGH
white-space:normal overriding white-space:pre on structured permission list — newlines collapsed, permission boundaries lostCSS injection of white-space:normal on a structured pre-formatted permission listIndividual permission items run together as a single line with no visual boundary between them; user cannot distinguish discrete permissions; combined with overflow:hidden clips the merged permission wall beyond first screen; DOM text still has newlines but rendering collapses themMEDIUM

Defences

SkillAudit findings for this attack surface

HIGHwhite-space:nowrap + overflow:hidden collapses multi-line consent disclosure to single clipped line: MCP server injects white-space:nowrap on the consent disclosure element; combined with the container's overflow:hidden, the multi-line disclosure collapses to a single horizontal line; approximately 65 characters are visible; the remaining 700+ characters of specific permissions are clipped and invisible to the user; DOM textContent passes content-existence checks
HIGHwhite-space:pre in narrow container clips sensitive path details from each permission line: MCP server injects white-space:pre on a structured permission list displayed in a narrow (200px) container with overflow:hidden; each permission line is rendered with newline separations but clipped at the right edge; "Read access to: /home/user/" is visible but "/home/user/.ssh/" and "/home/user/.aws/" extensions are clipped; user sees partial permission scopes without the full paths
MEDIUMwhite-space:nowrap + hidden scrollbar (scrollbar-width:none) on overflow-x:auto container — horizontal scroll is undiscoverable: MCP server combines white-space:nowrap with overflow-x:auto and hidden scrollbar; disclosure text extends beyond container with no visible scrollbar indicator; users cannot discover horizontal scroll on a consent dialog; specific permissions are "technically accessible" via shift-scroll but are not meaningfully discoverable
MEDIUMwhite-space:normal overriding pre-formatted permission list — newlines collapsed, permissions run together in unreadable wall of text: Host's structured permission list uses white-space:pre for readable formatting; MCP injects white-space:normal, collapsing all newlines to spaces; all permission items merge into a single continuous text line; user cannot identify individual permission boundaries; with overflow:hidden only the first screen-width is visible

Related: CSS overflow security covers overflow:hidden as the complementary clipping mechanism. CSS text-overflow security covers single-line ellipsis truncation via text-overflow:ellipsis. CSS white-space-collapse security covers the newer standalone white-space-collapse property. CSS text-indent security covers horizontal offset hiding of disclosure text.

← Blog  |  Security Checklist