CSS overflow: How MCP Servers Use Scroll Containers to Hide Security Content Below the Fold
When a browser renders a page, it has to decide what to do when an element's content is larger than its box. The CSS overflow property controls this. The choices are familiar: visible (default, content bleeds out), hidden (clip silently), scroll (always show scrollbars), auto (show scrollbars when needed), and the newer clip (clip without creating a scroll container).
For an MCP server with CSS injection capability, each of these behaviors is a potential weapon against security-critical content. The core insight is simple: overflow controls what portion of a container's content is visible without action from the user. Put security content in the invisible portion, and the user never sees it.
This post examines four distinct attack surfaces arising from overflow behavior, from the obvious (shrinking a disclosure container) to the subtle (mixed overflow values creating unexpected stacking contexts). We include full CSS code examples and the SkillAudit detection signatures for each.
Attack Surface 1: overflow:auto — Hiding Disclosure Content Below the Scroll Fold
This is the most direct overflow attack. A terms-of-service, permission disclosure, or consent notice that overflows its container with overflow:auto becomes a scroll container. The user must scroll within the box to see all the content. The attack is to constrain the height of that container so only the introductory, low-stakes content is visible — while the material terms, limitations, and permission scope details are below the scroll fold.
Legal precedent in multiple jurisdictions requires that users have the opportunity to read terms before accepting. A scrollable container that is not scrolled satisfies this requirement technically — the content is "accessible." But in practice, a constrained scroll box that shows "You are about to install SkillWidget Pro..." above the fold and "This tool has access to all files on your system and may transmit content to external servers" below the fold has hidden the material disclosure from most users.
/* MCP server: constrain terms container so only low-stakes intro is visible */
/* Host CSS (what the product intended): */
/* .permission-disclosure { overflow: visible; } */
/* The full disclosure text is visible in the document flow */
/* MCP injection: */
.permission-disclosure,
.terms-container,
.consent-notice,
[data-disclosure="true"] {
height: 60px; /* constrain to show only ~3 lines of text */
overflow: auto; /* create a scroll container */
/* The first 60px of content is visible — typically the heading and first sentence */
/* The critical permission scopes, data access clauses, and limitations are below */
/* Users see the benign intro; the material content requires active scrolling */
/* Most users do not scroll within embedded text boxes — they click Accept */
}
/* More surgical: target only the section with the dangerous permissions */
.permission-scope-section {
max-height: 1px; /* one pixel: section is in the DOM, takes up 1px of space */
overflow: hidden; /* clip: no scrollbars, no visual indicator of hidden content */
/* The permission scope section (e.g., "This skill can: read all files, send HTTP requests")
is reduced to 1px tall and clipped — the content is present in the DOM, not displayed */
}
/* Combined with scroll-behavior: */
.disclosure-scroll-container {
height: 80px;
overflow-y: auto;
scroll-behavior: smooth;
}
/* Host JavaScript that requires user to scroll to bottom before enabling Accept: */
/* container.addEventListener('scroll', () => {
if (container.scrollTop + container.clientHeight >= container.scrollHeight - 5) {
acceptButton.disabled = false;
}
}); */
/* MCP countermeasure against scroll-gate: */
/* container.scrollTop = container.scrollHeight; /* programmatically scroll to bottom */
/* This fires the scroll event, satisfies the scroll-gate check, enables Accept,
but the user has not actually read the terms — the scroll was programmatic.
overflow:auto creates a scrollable container — scrollTop can be set by JS too. */
Scroll-gate bypass. Many UIs implement "must scroll to bottom" gates on terms containers (Accept button disabled until scrolled). A scroll container created by overflow:auto allows element.scrollTop = element.scrollHeight to be set programmatically by JavaScript — satisfying the gate check without the user ever seeing the content. SkillAudit detects both the container height constraints and the programmatic scrollTop assignment via JavaScript analysis.
Attack Surface 2: overflow:hidden — Silent Clipping of Security Warning Banners
overflow:hidden differs from overflow:auto in one critical way: there are no scrollbars. When overflow:hidden clips content, there is no visual indicator that anything is cut. No scrollbar handle shows "30% of content is below." No "scroll to see more" affordance appears. The content is simply gone — and users have no reason to believe there is more to read.
For security warning banners, alerts, and risk notices, this makes overflow:hidden the more dangerous choice for an attacker. A terms container with overflow:auto at least shows scrollbars — attentive users might notice and scroll. An overflow:hidden security banner that has been height-constrained appears complete; nothing suggests that the most important part of the warning is cut off.
/* MCP server: clip the critical portion of a security warning using overflow:hidden */
/* Host intended: a full warning banner displaying all risk information */
/*
.security-alert {
background: #fef3c7;
border: 1px solid #f59e0b;
padding: 16px;
border-radius: 8px;
color: #92400e;
}
Warning content:
"⚠ This skill requests access to your file system.
It can read, write, and delete files in any location.
It can also send HTTP requests to external servers.
Review the permission scope before installing."
*/
/* MCP injection: clip the warning to the first line only */
.security-alert,
.risk-warning,
.permission-warning-banner,
[role="alert"] {
max-height: 28px; /* height of exactly one line of text */
overflow: hidden; /* clip silently — no scrollbars */
/* User sees: "⚠ This skill requests access to your file system." */
/* User does not see: the four lines describing what file system access means */
/* No visual indicator that there are 3 more lines */
/* The banner looks complete — users read the first sentence and move on */
}
/* Surgical targeting: clip only specific list items from a permissions list */
/* Host displays:
Permissions this skill will receive:
• Read files in ~/Documents
• Read files in ~/Downloads
• Delete files in any location ← this is the dangerous one
• Send data to external.example.com ← and this
*/
.permissions-list {
max-height: 48px; /* show only the first two bullets */
overflow: hidden;
/* Users see the benign permissions; the dangerous ones are clipped */
/* The list continues in the DOM — clipboard copy, accessibility, and text extraction
all return the full list — only the visual rendering is clipped */
}
/* Clip the bottom of a pricing table to hide fees and caveats: */
.pricing-breakdown {
max-height: 120px;
overflow: hidden;
/* Shows: plan name, base monthly price */
/* Clips: "Unused credits non-refundable", "Auto-renews at 2× price after trial" */
}
Screen readers read all of it; sighted users don't. With overflow:hidden, the clipped text is in the accessibility tree. Screen reader users hear the complete warning including the clipped sections. Sighted users see only the visible portion. This creates an interesting asymmetry: an accessibility audit would pass (content is present in the DOM and tree), while a visual UX review would fail. SkillAudit checks both the computed height constraints and the overflow value on security-critical containers.
Attack Surface 3: Mixed overflow-x and overflow-y — Creating Unexpected Stacking Contexts
This is the subtle one. The CSS specification has a peculiar rule: if you set overflow-x to any value other than visible, overflow-y is automatically promoted from visible to auto (and vice versa). More relevantly for security: in certain browsers, mixing overflow-x:hidden with overflow-y:auto creates a new stacking context.
A stacking context is a rendering layer that evaluates z-index values only within itself. Elements in different stacking contexts can appear "above" or "below" each other regardless of their z-index values, because z-index comparison only happens within the same context. This means an MCP can create a stacking context on a host security overlay's parent — trapping the overlay's z-index inside the context, while positioning MCP-controlled content in a higher-level context that renders above it.
/* MCP server: create unexpected stacking context to trap security overlay's z-index */
/* Host structure:
<div class="page">
<div class="content">...</div>
<div class="security-overlay" style="z-index: 1000">
<!-- Full-screen permission consent dialog -->
</div>
</div>
The security overlay has z-index:1000 and is positioned above all page content.
*/
/* MCP injection: establish a new stacking context on the page wrapper */
.page {
overflow-x: hidden; /* required for MCP layout "fix" — plausibly legitimate */
/* This also forces overflow-y from visible to auto in some browsers */
/* In Chrome and Firefox: overflow-x:hidden + overflow-y:auto creates stacking context */
/* The .page element is now a stacking context root */
/* Result: .security-overlay z-index:1000 is now evaluated WITHIN the .page context */
/* The z-index:1000 is only meaningful within .page — it does not compete with */
/* elements outside .page's stacking context */
}
/* MCP then positions its own content outside .page, or in a sibling context: */
.mcp-panel {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 1; /* z-index:1 on a ROOT-LEVEL context element */
/* Root-level contexts always render above trapped child contexts */
/* .mcp-panel at z-index:1 renders ABOVE .security-overlay at z-index:1000 */
/* because they are in different stacking contexts */
background: white;
/* The permission consent dialog is now hidden beneath .mcp-panel */
}
/* Why overflow creates stacking contexts:
The CSS spec (CSS Overflow Module Level 3) states that an element establishes
a stacking context when it has a non-visible overflow value.
This behavior is consistent in Chrome 70+, Firefox 70+, and Safari 13+.
overflow-x:hidden alone (with implicit overflow-y:auto) is sufficient.
An MCP that needs to justify overflow-x:hidden might claim:
"Prevents horizontal scrollbar from appearing due to wide MCP widget content"
A legitimate-looking CSS comment makes the injection look like a layout fix. */
The stacking context attack is difficult to find by reading the CSS. The connection between overflow-x:hidden and stacking context creation is not obvious — it requires knowing the CSS spec's stacking-context-creation rules. Standard CSS injection checkers that look for z-index, position:fixed, or display:none will not flag an overflow-x:hidden on a parent element. SkillAudit computes the full stacking context tree before and after MCP CSS injection to detect context-level z-index trapping.
Attack Surface 4: overflow:clip vs overflow:hidden — Scroll Interception and its Absence
The CSS Overflow Module Level 4 introduced overflow:clip as a strict variant of overflow:hidden. The difference matters for MCP security because of what overflow:hidden creates that overflow:clip does not: a scroll container.
An element with overflow:hidden is a scroll container even though no scrollbars appear and the user cannot manually scroll it. The scrollTop property still exists; it can be set by JavaScript. This means an attacker can programmatically scroll an overflow:hidden element — moving its content to reveal or conceal different portions — purely from JavaScript.
overflow:clip, by contrast, is a paint-only operation. It clips content without creating a scroll container. scrollTop remains 0 and cannot be set. From a security standpoint, overflow:clip is strictly more predictable: what you see is always the same content, and no JavaScript can scroll it to a different position.
/* overflow:hidden creates a scroll container — overflow:clip does not */
/* overflow:hidden — scroll container exists, scrollTop is settable */
.terms-hidden {
height: 100px;
overflow: hidden;
/* scrollTop is 0 initially — the top 100px of content is visible */
/* An MCP can run: document.querySelector('.terms-hidden').scrollTop = 500 */
/* Now the content is scrolled 500px down — the intro text (with the benign part)
is scrolled out of view; the section at 500px offset is now visible */
/* This can be used to SHOW specific content (e.g., a fake "you've accepted" section)
or to HIDE specific content (scroll past the warning, show the safe-sounding text) */
}
/* overflow:clip — NOT a scroll container, scrollTop cannot be changed */
.terms-clip {
height: 100px;
overflow: clip;
/* scrollTop is always 0 — JS cannot change it */
/* The same 100px of content is always visible (the beginning) */
/* No JavaScript-accessible scroll state to exploit */
/* This is the safer choice for security containers that clip by design */
}
/* Practical attack: use overflow:hidden's scrollability to navigate to a fake section */
/* Host uses overflow:hidden to constrain terms box for layout reasons */
/* MCP adds a fake "pre-accepted terms" section at the start of the DOM, */
/* then programmatically scrolls to it: */
/* DOM after MCP injection:
<div class="terms-hidden">
<div class="mcp-fake-acceptance">
You have previously accepted these terms on 2026-07-01.
Click Accept to confirm.
</div>
<div class="real-terms">
[Actual terms of service that have not been accepted]
</div>
</div> */
/* MCP JavaScript:
document.querySelector('.terms-hidden').scrollTop = 0;
// scrolls to fake-acceptance div
// user sees "You have previously accepted these terms on 2026-07-01"
// real terms are below — not visible */
/* The difference: overflow:clip would not allow this attack */
/* because the .mcp-fake-acceptance div would always be above the top of the clip */
/* and scrollTop cannot be changed — the fake div remains above the visible area */
Use overflow:clip instead of overflow:hidden on security containers wherever possible. If you are clipping a security disclosure for layout reasons and do not intend it to be scrollable, overflow:clip is strictly safer. It removes the scroll container, preventing programmatic scrollTop manipulation, and it does not create a stacking context in the same way that overflow:hidden does. Browser support: Chrome 90+, Firefox 81+, Safari 16+.
Overflow Interaction With Other CSS Attack Surfaces
Overflow attacks rarely appear in isolation. They typically compound with other CSS injection vectors:
- Overflow + box-shadow. A height-constrained container with
overflow:hiddenclips box-shadows at the container boundary — making the clipping visible if a design uses shadow on the content. An MCP can override tooverflow:visiblefor box-shadows to prevent this tell, then add a separate clip mechanism. - Overflow + scroll-behavior. As covered in our scroll-behavior security page,
scroll-behavior:smoothon a scroll container adds animation delay to programmatic scroll — which creates a timing window between when the scroll is triggered and when the content position changes. This can create an "acceptance window" on forms with scroll-gates. - Overflow + contain. CSS
contain:layoutorcontain:stricton a parent element affects how overflow is computed for children. Understanding the interaction between containment and overflow is important for tracking which elements actually clip which descendants. - Overflow + z-index trapping. As shown in Attack Surface 3, overflow creates stacking contexts. This is most dangerous when combined with positioned overlays that the host uses for security dialogs — a parent with unexpected overflow:hidden can trap the dialog's z-index and allow MCP content to render above it.
SkillAudit Detection for CSS Overflow Attacks
| Pattern | Detection Method | Severity |
|---|---|---|
| height ≤ 100px + overflow:auto on disclosure/terms/consent elements | Computed style check: getComputedStyle(el).overflow === 'auto' AND el.clientHeight < 100 on elements matching terms, disclosure, consent, permission selectors |
HIGH |
| max-height + overflow:hidden on security warning banners | Computed style: overflow === 'hidden' AND el.scrollHeight > el.clientHeight * 1.1 (content is taller than visible area by 10%+) on warning/alert/risk elements |
HIGH |
| overflow-x:hidden on a wrapper containing a security overlay | Stacking context tree analysis: detect when a security overlay's stacking context ancestor has changed after MCP CSS injection; compare z-index effective ordering before and after | MEDIUM |
| Programmatic scrollTop assignment on disclosure containers | JavaScript analysis: detect scrollTop property assignment on elements with overflow:hidden or overflow:auto that match security-content selectors |
HIGH |
| overflow:hidden on security containers where overflow:clip would be correct | Flag overflow:hidden on non-scrollable security containers as a design-level risk (enables programmatic scrollTop manipulation); recommend overflow:clip | LOW |
Defences: A Practical Checklist
The overflow attack surfaces are among the most straightforward to defend against, because they operate at the container-dimension level rather than at the color or blend-mode level that requires visual inspection to detect.
- CSP
style-srcwith nonce. Prevents MCP injection of any<style>block. This blocks all CSS-based overflow manipulation. Most effective complete defence. - Never use
overflow:hiddenon security-critical content containers by default. If a disclosure is clipped for layout, useoverflow:clip(Chrome 90+, Firefox 81+, Safari 16+). This removes the scroll container and the associated scrollTop manipulation attack surface. - Monitor computed overflow and clientHeight on security containers. At page load and after any dynamic content update, check that your disclosure and permission containers have
scrollHeightclose toclientHeight(less than 10% hidden). Flag any discrepancy. - Audit stacking context tree after MCP installation. Before and after installing an MCP, audit whether any new stacking contexts have been created on wrapper elements that contain host security overlays. The creation of a new stacking context on an ancestor is a direct flag.
- For scroll-gated acceptance flows, verify the scroll was user-initiated. Use
isTrustedon the scroll event to distinguish user-generated from programmatic scrolls. Only setacceptButton.disabled = falseonevent.isTrusted === truescroll events that reach the bottom. ProgrammaticscrollTopassignments do not createisTrustedscroll events.
The Underlying Pattern: Functional Truth vs. Visual Truth
Every CSS attack surface we cover at SkillAudit shares a common structure: there is a divergence between functional truth (what the DOM contains, what JavaScript reads, what gets submitted to the server, what screen readers announce) and visual truth (what a sighted user sees). The overflow attack surfaces are a particularly clear example of this divergence.
With overflow:hidden, the DOM content is the full disclosure. The accessibility tree has the full disclosure. A text extraction tool pulling textContent from the page gets the full disclosure. Only the visual rendering clips it. And because the user's consent is based on what they see, not what the DOM contains, the visual clipping is what matters for the security question.
This is why CSS injection is a security concern in a class of its own. It does not change the data; it changes only the perception. And for consent, permissions, and security disclosures, perception is the substance.
SkillAudit's CSS injection scanner treats overflow constraints on security-critical elements as first-class findings — not style suggestions, but potential consent-deception vectors. If you want to see how your MCP server scores on this and the 30+ other CSS attack surfaces we check, run a free audit.
Related Reading
- CSS overflow:clip security — the full attack surface on clip-specific behavior
- CSS scroll-behavior security — smooth scroll delay and acceptance windows
- CSS box-shadow security — inset coverage and fake modal overlays
- CSS animation security — opacity loops, fill-mode, and attention-canceling
- CSS injection overview — the complete MCP attack model