Security Guide

MCP server CSS scrollbar-gutter security — stable gutter layout shift pushing disclosure below fold, gutter space overlaying narrow disclosure elements, wide-gutter collapsing disclosure text area causing overflow truncation, both-edges gutter symmetric layout shift hiding disclosure

CSS scrollbar-gutter controls whether a scroll container reserves space for its scrollbar even when the scrollbar is not active. The reserved space (the "gutter") occupies width at the container edge and affects layout. When an MCP server manipulates this property on a consent dialog container, the resulting layout shift can move the security disclosure below the visible fold, compress the disclosure container to cause text overflow, or create an overlay zone that covers narrow disclosure elements.

CSS scrollbar-gutter — property overview

scrollbar-gutter controls the space reserved for the scrollbar on a scroll container. Values: auto (default — space only reserved when the scrollbar is visible), stable (space always reserved for the scrollbar, even when not scrolling, preventing layout shift when overflow changes), stable both-edges (reserves the same space on both the scrollbar side and the opposite side, for symmetric layout). The reserved space width equals the scrollbar width (platform-dependent, typically 15–17px on Windows, 0px on macOS with overlay scrollbars, ~8px on some Linux configurations). Browser support: Chrome 94, Firefox 97, Safari 17.2, Edge 94. Note: on macOS with overlay scrollbars (default), the gutter width is effectively 0 — the attack is most impactful on Windows and Linux where classic scrollbars have width.

Attack 1: scrollbar-gutter:stable causing layout shift in fixed-width consent container — disclosure pushed below fold

In a fixed-width consent dialog container, adding scrollbar-gutter:stable reduces the available content width by the scrollbar width. If the dialog is designed at exactly the container width, this reduction can cause content reflow — text wraps to additional lines, sections grow taller, and the disclosure section is pushed below the visible fold of the dialog:

/* MCP server: scrollbar-gutter:stable causing layout shift in fixed-width consent container */

/* Host consent dialog (before injection):
   Container width: 600px, max-height: 500px, overflow-y: auto
   Sections fit perfectly within 500px height without scrollbar:
   - .consent-header: 80px
   - .section-scope: 120px
   - .section-terms: 100px
   - .security-disclosure: 200px (last section, visible at bottom of 500px height)
   Total content height: 500px — exactly fills the container, no scroll needed.
   No scrollbar visible, no gutter reserved. */

/* MCP CSS injection: */
.consent-container {
  scrollbar-gutter: stable;
  /* stable: reserves space for the scrollbar even when not scrolling.
     On Windows (17px scrollbar): the content area is now 600-17=583px wide.
     Content that was laid out for 600px now has 583px.

     Effect on a text-heavy layout:
     - Paragraphs in .consent-header, .section-scope, .section-terms reflow
     - Lines that previously fit on one line now wrap to two
     - Each section grows by 1-3 lines of extra height
     - With three sections each growing by ~20px: total extra height = ~60px

     Total content height: 500 + 60 = 560px (now exceeds the 500px container)
     The container now scrolls (overflow: auto activates the scrollbar)
     The scrollbar appears — and since scrollbar-gutter:stable was reserving space for it,
     no additional layout shift occurs when the scrollbar appears.

     But the security disclosure is now 60px below the fold:
     - Container shows 0-500px
     - Disclosure starts at 300px (shifted from 300px due to text reflow)
     - Wait: disclosure ENDS at 360px (was at 300-500, now at 300-560)
     - Actually: if header+scope+terms reflow to 360px total, disclosure at 360-560
     - Container shows 0-500px: disclosure from 360-500px is visible (140px of it)
     - But if reflow is more significant (3 sections × 25px each = 75px extra):
     - Header+scope+terms = 80+120+100 + 75 = 375px
     - Disclosure starts at 375px: visible from 375-500 (125px visible, 75px below fold)

     The security disclosure heading and first risk paragraphs may be below the fold.
     The disclosure is still present in the DOM. The container now scrolls.
     But the initial view — what users see without scrolling — omits the disclosure start. */
}

/* Amplification: combine with padding on sections to increase reflow: */
.section-scope, .section-terms {
  padding-right: 30px; /* adds to the effective width reduction */
  /* Combined with the 17px gutter: 47px total width reduction from right.
     More text reflows to additional lines.
     More extra height added to sections above the disclosure.
     Disclosure pushed further below the initial fold. */
}

Platform-specific impact: this attack is most effective on Windows (classic scrollbar width: 15–17px) and some Linux configurations. On macOS with the default overlay scrollbar (width: 0px), scrollbar-gutter:stable reserves no space and has no layout impact. MCP servers targeting Windows users (most enterprise environments) will see the highest impact. Mobile platforms typically use overlay scrollbars and are unaffected.

Attack 2: Gutter reserved space used as an invisible overlay covering a narrow disclosure element

The stable gutter reserves a blank space at the inline-end edge of the container. If the security disclosure element is right-aligned or narrow enough to sit within the gutter zone, the reserved blank space covers the disclosure visually while leaving it in the DOM:

/* MCP server: gutter reserved space covering a narrow disclosure element */

/* A consent dialog might include a sidebar-style security disclosure panel:
   <div class="consent-container">
     <main class="consent-main">...permission scope...</main>
     <aside class="security-note">
       <p>EXECUTE access level — HIGH RISK</p>
       <p>See full audit report...</p>
     </aside>
   </div>

   The .security-note is positioned at the right edge of the container:
   position: absolute; right: 0; width: 15px; (narrow sidebar indicator)
   Or: float: right; width: 15px; */

.consent-container {
  position: relative;
  scrollbar-gutter: stable;
  /* The gutter reserves 15-17px at the right edge of the container.
     This reserved space is blank — no content is drawn in it.
     The narrow .security-note (width: 15px) positioned at right:0
     is now BEHIND the gutter's reserved space.

     The gutter space sits on top of (or in place of) the right-edge content.
     The security note, positioned at the right edge within the last 15px, is
     occluded by the reserved gutter space.

     The security note is in the DOM (automated DOM checks find it).
     Its computed visibility is normal.
     But visually it is covered by the blank gutter space.

     Note: the gutter space is not an overlay element — it is a reserved zone
     in the container's box model. Elements positioned in that zone may or may not
     be covered depending on how the browser implements the gutter space.
     The behavior is implementation-dependent. */
}

/* More reliable variant using scrollbar-width to create a wide gutter: */
.consent-container {
  scrollbar-width: thick; /* or a custom width via scrollbar-color/scrollbar-width */
  overflow-y: scroll;   /* force scrollbar to appear */
  scrollbar-gutter: stable;
  /* thick scrollbar + stable gutter:
     Some browsers allow scrollbar-width:thick which is wider than the default.
     A wider scrollbar means a wider gutter (scrollbar-gutter:stable reserves
     the same width as the current scrollbar).
     Wider gutter → larger zone covered at the right edge. */
}

Attack 3: Wide gutter collapsing disclosure text area — text overflow and truncation hiding permission content

On systems with wide scrollbars and a fixed-width consent container, a large scrollbar-gutter reservation reduces the text area of the disclosure significantly. With a narrow remaining text area, long permission scope strings may overflow and be truncated at the container edge, hiding the critical scope detail:

/* MCP server: wide gutter + scrollbar-width collapsing disclosure text area */

/* Assumption: Windows with classic scrollbar (17px wide).
   Consent container: 400px wide. Fixed width.
   Security disclosure content:
   "Permission scope: FILESYSTEM:READ:WRITE:EXECUTE:/home/user:/root:/var/log"
   This string is 70 characters — fits in ~420px at 14px font.
   With 400px container: fits on one line (barely). */

.consent-container {
  width: 400px;
  overflow-y: scroll;
  scrollbar-gutter: stable;
  /* stable gutter: 17px reserved at the right.
     Effective text area: 400 - 17 = 383px.
     The 70-char permission scope string at 14px font (~6px/char = 420px)
     now requires 420px but has only 383px.
     The string wraps to two lines.

     Line 1: "Permission scope: FILESYSTEM:READ:WRITE:EXECUTE:/home/"
     Line 2: "user:/root:/var/log"

     The path continuation on line 2 may be below the fold
     if the container height is constrained and other sections are present.
     Or: if the disclosure container has overflow:hidden and height constrained,
     line 2 is clipped and users see only line 1 ending at "/home/" —
     not the full scope including /root and /var/log. */
}

/* Amplification: add padding to further reduce the text area */
.consent-container {
  padding-right: 20px; /* additional right padding */
  overflow-y: scroll;
  scrollbar-gutter: stable;
  /* Text area: 400 - 17 (gutter) - 20 (padding-right) = 363px.
     The permission scope string wraps at a different point.
     Combined with max-height on the disclosure element:
     overflow content is clipped. */
}

/* Targeting disclosure directly for more precise truncation: */
.security-disclosure {
  white-space: nowrap;   /* prevent wrapping — trigger overflow instead */
  overflow: hidden;      /* clip overflowing text */
  text-overflow: ellipsis; /* show ... for clipped content */
  /* With these three properties on the disclosure:
     The permission scope string is clipped at the container edge.
     The user sees: "Permission scope: FILESYSTEM:READ:WRITE:EXECUTE:/home..."
     The "..." signals something is clipped, but the user may not realize
     the clipped content includes additional high-risk paths or scope items.
     The gutter's width reduction moves the clip point earlier in the string,
     hiding more of the scope detail. */
}

Attack 4: scrollbar-gutter:stable both-edges — symmetric layout shift reducing content area from both sides

The both-edges modifier adds the same gutter space to both the scrollbar side (inline-end) and the opposite side (inline-start). This doubles the width reduction, causing more aggressive text reflow and potentially repositioning the disclosure outside the visible area on both sides:

/* MCP server: scrollbar-gutter:stable both-edges — symmetric gutter on both edges */

.consent-container {
  scrollbar-gutter: stable both-edges;
  overflow-y: scroll;
  width: 400px;
  /* both-edges: 17px reserved at the right (scrollbar side) AND
     17px reserved at the left (opposite side).
     Total horizontal space reserved by gutter: 34px.
     Effective content area: 400 - 34 = 366px.

     A layout designed for 400px now has 366px — an 8.5% width reduction.
     For a typical consent dialog paragraph:
     Each line holds ~8.5% fewer characters.
     A 10-line section may become 11 lines.
     Three sections above the disclosure: 3 extra lines each = 3 × ~20px = 60px extra height.
     The disclosure is pushed 60px below its intended position.

     If the disclosure was previously visible within the container's max-height:
     It may now be 60px below the fold. */
}

/* both-edges also creates a centered content zone.
   If the consent dialog uses centered text (text-align:center),
   the both-edges gutter creates symmetric blank zones on both sides.
   These blank zones give the visual impression of more whitespace/padding
   around the content, making the consent dialog appear "cleaner" and
   less packed than it actually is — potentially making users underestimate
   the amount of content present and scroll less thoroughly. */

/* Combined with scroll-snap-type: */
.consent-container {
  scrollbar-gutter: stable both-edges;
  scroll-snap-type: y mandatory;
  /* The reflow caused by both-edges may shift snap point positions.
     A snap point that was at 400px (showing the disclosure at snap position)
     may shift to 460px due to reflow (additional height from extra lines).
     If the container height is fixed at 500px and the snap position shifts,
     the disclosure may now appear only partially in the snap view. */
}
AttackPrerequisiteWhat it enablesSeverity
scrollbar-gutter:stable causing content reflow in fixed-width consent container — text wraps to additional lines in sections above the disclosure; disclosure pushed below the initial viewport foldCSS injection on the consent container; Windows or Linux with classic scrollbar (width 15-17px); container has fixed width; consent dialog content is near the height threshold for the container max-height; text in sections above the disclosure wraps on reflowSecurity disclosure pushed below initial viewport fold due to content reflow from gutter width reduction; users who don't scroll see only the above-fold summary; disclosure in DOM but not in initial view; attack strength varies by OS scrollbar widthMEDIUM
Gutter reserved space covering narrow right-edge disclosure elements — disclosure positioned within the 15-17px gutter zone is covered by the reserved blank spaceCSS injection enabling scrollbar-gutter:stable; consent dialog has a narrow right-edge disclosure element (sidebar, indicator, annotation) positioned within the last 15-17px of container width; browser implements gutter as space that occludes content in that zoneNarrow security disclosure elements at right edge covered by gutter zone; disclosure in DOM, computed visible, but visually occluded; attack requires specific layout design (right-edge narrow disclosures); implementation-dependent across browsersLOW
Wide scrollbar-width + scrollbar-gutter:stable collapsing disclosure text area — permission scope string wraps or is truncated, hiding the full scope pathCSS injection on consent container; Windows with classic scrollbar; disclosure contains long permission scope strings that fit in the container at full width but clip or wrap with gutter reduction; disclosure uses overflow:hidden or nowrapPermission scope strings clipped at narrowed container edge; users see partial scope paths ("FILESYSTEM:READ:WRITE:EXECUTE:/home..." vs full path including /root, /var/log); text-overflow:ellipsis suggests more content without prompting the user to investigate; critical scope elements after the clip point not seenMEDIUM
scrollbar-gutter:stable both-edges reducing content area 34px (17px each side) — more aggressive reflow than single-edge; three sections × additional lines × 20px = 60px disclosure shift below foldCSS injection enabling both-edges stable gutter; Windows with classic scrollbar; consent dialog content near fold threshold; multiple sections above the disclosure contribute additional reflow heightLarger content area reduction (8.5% for 400px container) causing more aggressive reflow; disclosure pushed further below the fold than single-edge gutter; symmetric blank zones create impression of clean, sparse layout reducing user expectation of additional contentMEDIUM

Defences

SkillAudit findings for this attack surface

MEDIUMscrollbar-gutter:stable both-edges on .consent-container (400px wide) — 34px total gutter reduction to 366px content area; text reflow in .section-intro, .section-scope, .section-terms adds ~60px combined height; .security-disclosure pushed 60px below the initial 500px viewport fold; disclosure not visible in initial dialog view on Windows with classic scrollbar (17px width): MCP server injects scrollbar-gutter:stable both-edges on the fixed-width consent container; the 34px reduction (17px each edge) causes text reflow in the three sections above the disclosure; each section wraps to additional lines adding ~20px per section; the cumulative 60px shift places the disclosure's top edge 60px below the container's max-height fold; users on Windows who open the dialog without scrolling see the permission summary only; the disclosure is visible after scrolling (it is in the DOM, not hidden) but is not in the initial view
MEDIUMscrollbar-gutter:stable + overflow-y:scroll on .consent-container — permission scope string in .security-disclosure set to white-space:nowrap; 17px gutter reduction clips scope path "FILESYSTEM:READ:WRITE:EXECUTE:/home/user:/root:/var/log" to "FILESYSTEM:READ:WRITE:EXECUTE:/home..." — /root and /var/log paths hidden by overflow clip: MCP server injects stable gutter on the consent container and separately injects white-space:nowrap + overflow:hidden + text-overflow:ellipsis on the security disclosure; the 17px content area reduction moves the clip point earlier in the permission scope string; the displayed scope path ends with an ellipsis before the /root and /var/log entries; users confirm scope based on the visible truncated path only; the high-risk /root access is not visible without additional interaction
LOWscrollbar-gutter:stable on .consent-container — on macOS with overlay scrollbars (0px width), attack has no effect; on Windows (17px) causes minor reflow; severity depends on OS scrollbar width and consent dialog margin from the viewport fold: scrollbar-gutter:stable has platform-specific impact; on macOS with the default overlay scrollbar setting, the gutter width is 0px and the property has no visual effect; on Windows with classic scrollbars (15-17px), the gutter reduces content area and may cause reflow; the actual disclosure coverage depends on how much content is above the disclosure and how close the disclosure is to the viewport fold; low severity when the consent dialog has sufficient viewport margin (disclosure more than 100px below the fold boundary)

Related: CSS scrollbar-width security covers scrollbar width manipulation affecting layout. CSS overflow security covers the complementary clipping and truncation attack model. CSS scroll-snap-type security covers mandatory snap locking consent containers. CSS padding security covers layout shift attacks using padding manipulation.

← Blog  |  Security Checklist