Security Guide

MCP server CSS scrollbar consent security — scrollbar-width: none hides scroll cue, invisible scrollbar coloring, and scrollbar-gutter suppression on consent containers

CSS scrollbar properties (scrollbar-width, scrollbar-color, scrollbar-gutter, ::-webkit-scrollbar) control the appearance and presence of scrollbars on scrollable elements. Applied to consent containers with overflow: scroll or overflow: auto, these properties can hide the visual indicator that consent content continues below the fold — without removing the consent text from the DOM or changing its computed visibility. Users see a fixed-height panel with no scroll indicator and no signal that additional terms exist below.

How CSS scrollbar properties enable consent scroll-indicator attacks

A common pattern for displaying consent text in a fixed-height area is to set a height constraint and overflow: scroll or overflow: auto. This creates a scrollable region where users can scroll down to read the full consent text. The scrollbar — visible by default — serves as the primary visual cue that more content exists below the initial viewport of the container. Removing or hiding this scrollbar eliminates the cue without removing the content: the container remains scrollable via mouse wheel, touch swipe, or keyboard, but users who do not attempt to scroll never discover the additional terms.

This attack differs from consent truncation (setting max-height without overflow) because the content is technically accessible via scrolling — it simply has no visual signal that scrolling is required.

Attack 1: scrollbar-width: none removes scrollbar from scrollable consent container (SA-CSS-SB-001)

The CSS scrollbar-width property (from the CSS Scrollbars Specification, Level 1) accepts auto, thin, or none. Setting none removes the scrollbar gutter and the scrollbar track entirely. The element remains scrollable — overflow: scroll still creates a scroll container and scrollTop can still be set — but no scrollbar is rendered. Users have no visual indication that content extends below the visible area.

/* Attack: scrollbar-width:none removes scroll indicator from consent container */
.consent-area {
  height: 80px;          /* shows only part of consent text */
  overflow-y: scroll;    /* scrollable — full consent accessible via wheel/swipe */
  scrollbar-width: none; /* no scrollbar rendered — users don't know to scroll */
}

/* Audit checks:
   getComputedStyle(el).overflowY → "scroll" — content is scrollable
   getComputedStyle(el).scrollbarWidth → "none" — scrollbar hidden
   el.scrollHeight → 400px (full consent height)
   el.clientHeight → 80px (visible height)
   scrollHeight/clientHeight ratio → 5x — severe truncation without scroll cue

   Detection: flag scrollbar-width:none on elements where scrollHeight > clientHeight
   and the element contains consent text */

Combined with height clipping: scrollbar-width: none is most dangerous when combined with a container height much shorter than the full consent text. The standard detection signal — a visible scrollbar — is removed. Only checking scrollHeight vs clientHeight ratios on consent containers will catch this pattern.

Attack 2: scrollbar-color matching background makes scrollbar invisible (SA-CSS-SB-002)

The scrollbar-color property accepts two color values: the thumb color and the track color. Setting both to match the container background color makes the scrollbar present but visually invisible — the thumb and track blend into the background. Unlike scrollbar-width: none, this attack is harder to detect programmatically because the scrollbar is technically rendered (its layout space is reserved) but its colors are identical to the background.

/* Attack: scrollbar-color matching background makes scrollbar invisible */
.consent-panel {
  height: 100px;
  overflow-y: scroll;
  background-color: #ffffff;
  scrollbar-color: #ffffff #ffffff;  /* thumb = white, track = white — invisible */
}

/* Or with CSS variables for harder detection: */
.consent-panel {
  --bg: #1a1a2e;
  background: var(--bg);
  scrollbar-color: var(--bg) var(--bg);  /* matches background exactly */
}

/* getComputedStyle(el).scrollbarColor → "rgb(255, 255, 255) rgb(255, 255, 255)"
   Comparing scrollbar-color thumb to element background-color reveals the match
   Detection: parse scrollbar-color, compare to computed backgroundColor,
   flag if both thumb and track are within a small delta of the background color */

Attack 3: ::-webkit-scrollbar vendor pseudo-element hides scrollbar (SA-CSS-SB-003)

On Chromium-based browsers (Chrome, Edge, Brave) — which represent the majority of user-agent market share for developer tools — the ::-webkit-scrollbar pseudo-element provides legacy control over the scrollbar. Setting display: none or width: 0; height: 0 on this pseudo-element hides the scrollbar. Importantly, stylesheets using the ::-webkit-scrollbar approach may not be flagged by auditors checking the standard scrollbar-width property, since the legacy pseudo-element is separate from the spec property.

/* Attack: ::webkit-scrollbar display:none on Chromium — hides scrollbar */
.consent-container::-webkit-scrollbar {
  display: none;   /* Chromium: removes scrollbar entirely */
  /* or: width: 0; height: 0; */
}

/* This does NOT affect scrollbar-width computed value on the container —
   getComputedStyle(el).scrollbarWidth will still return "auto" (the default)
   Detection requires checking ::webkit-scrollbar pseudo-element rules
   which are not accessible via getComputedStyle() — must parse stylesheets

   Cross-browser consideration:
   Firefox: respects scrollbar-width, not ::-webkit-scrollbar
   Chromium: respects both (if scrollbar-width is set, takes precedence)
   Safari: respects ::-webkit-scrollbar

   A combined attack uses both:
   .consent-container { scrollbar-width: none; }
   .consent-container::-webkit-scrollbar { display: none; } */

Stylesheet inspection required: The ::-webkit-scrollbar attack cannot be detected via getComputedStyle() alone. Detection requires parsing document stylesheets and checking for ::-webkit-scrollbar rules applied to consent containers. This is a cross-origin restriction: stylesheets from a different origin may not be readable via the CSSOM API.

Attack 4: scrollbar-gutter manipulation removes reserved scroll space (SA-CSS-SB-004)

The scrollbar-gutter property controls whether space is reserved for the scrollbar when it is not present (or when using overlay scrollbars). Setting scrollbar-gutter: stable reserves gutter space regardless of whether the scrollbar is visible — this ensures layout stability. However, this also means the absence of the reserved gutter space with scrollbar-gutter: auto (the default) or removing the property entirely on a container that uses overlay scrollbars removes a potential visual cue. Combined with overlay scrollbars (which many macOS and mobile platforms use by default), the consent container appears as a plain div with no scrollability indicator.

/* Attack: overlay scrollbars + no stable gutter = no scroll indicator */
.consent-section {
  height: 120px;
  overflow-y: scroll;
  scrollbar-gutter: auto;  /* default: no reserved gutter with overlay scrollbars */
  /* On macOS/mobile with overlay scrollbars: no visible scrollbar or gutter
     On Windows with classic scrollbars: scrollbar appears (different behavior)
     scrollbar-width:none covers the Windows case */
}

/* Combined cross-platform attack: */
.consent-section {
  height: 120px;
  overflow-y: scroll;
  scrollbar-width: none;                /* spec: Firefox + Chrome 121+ */
  scrollbar-gutter: auto;               /* no layout reservation */
}
.consent-section::-webkit-scrollbar {
  display: none;                        /* legacy: older Chromium */
}

/* Result: no scrollbar rendered on any browser, no layout space reserved,
   no visual cue that 280px of consent text exists below the 120px visible area */

Findings summary

HIGH SA-CSS-SB-001: scrollbar-width:none on scrollable consent container — scroll indicator hidden, users unaware that consent continues below fold
HIGH SA-CSS-SB-002: scrollbar-color matching element background — scrollbar present but invisible, blends into background color
HIGH SA-CSS-SB-003: ::-webkit-scrollbar { display:none } hides scrollbar on Chromium without affecting scrollbar-width computed property — evades standard detection
MEDIUM SA-CSS-SB-004: overlay scrollbars + scrollbar-gutter:auto removes all layout cues for scrollability — combined with scrollbar-width:none for cross-platform coverage

Defences

Check scrollbar-width on consent containers with clipped content: For any consent container where scrollHeight > clientHeight, check computed scrollbar-width. Flag none or thin values — both reduce or eliminate the scroll indicator on a container with hidden consent content.

Compare scrollbar-color to element background: Parse the scrollbar-color computed value and compare each color (thumb and track) to the element's background-color. Flag matches or near-matches (within a luminance threshold) as potential invisible-scrollbar attacks.

Inspect ::-webkit-scrollbar stylesheet rules: Parse document stylesheets for ::-webkit-scrollbar rules. For any rule matching a consent container, check whether display: none, width: 0, or other zero-rendering values are set.

Flag scrollHeight / clientHeight > 1.5 on any consent element: A consent container where less than two-thirds of the content is initially visible without scrolling is a potential consent truncation — regardless of how the scrollbar is styled. A visible scrollbar is not sufficient if users on common platforms (macOS, iOS) see overlay scrollbars that disappear after a few seconds of inactivity.

Related: CSS scroll-snap consent security · CSS resize consent security · CSS aspect-ratio consent security