Security Guide
MCP server CSS scroll-snap consent security — mandatory snap skips consent section, scroll-snap-stop:always blocks scrolling past install, JS scrollTop pre-skip, snap-align:end install-only viewport
CSS scroll snap makes consent sections permanently unreachable without touching any of the consent element's own properties. The consent text has correct color, legible font-size, normal dimensions, and is fully in the DOM. The attack is in the scroll container's snap configuration: scroll-snap-type:y mandatory forces all resting positions to align to declared snap points, and if the consent section has no snap alignment, the user's scroll can never rest on it. The consent text can only be reached if the user overcomes the snap with deliberate effort — and even then, scroll-snap-stop:always can prevent passing through the install button's snap point to reach consent below.
How CSS scroll-snap works
scroll-snap-type on a scroll container defines whether and how scroll positions are snapped to aligned children. mandatory snap (vs proximity) forces the scroll to always land on a snap point — no intermediate positions are allowed as final resting positions. scroll-snap-align on children defines their snap position (start, center, end). Children without scroll-snap-align are not snap points; the mandatory container cannot rest with them in view. scroll-snap-stop:always on a snap-aligned child forces the scroll to stop at that child even during fast momentum scrolling — preventing fast scrolling past it to reach content beyond it.
Attack 1: mandatory snap skips consent section between two snap points (SA-CSS-SSC-001)
A scroll container with scroll-snap-type: y mandatory allows only snap-aligned positions as resting points. If the install panel contains three sections — a header (snap-aligned), then a consent block (no snap alignment), then the install button (snap-aligned) — the mandatory snap will jump from the header directly to the install button. The consent section exists between them in the scroll position space but can never be the resting scroll position. A user who tries to scroll slowly through the consent section finds the scroll snapping over it to the install button.
/* Mandatory snap skips consent section */
.install-scroll-container {
overflow-y: scroll;
scroll-snap-type: y mandatory; /* mandatory: must always land on a snap point */
height: 400px; /* viewport-height container */
}
.install-header {
scroll-snap-align: start; /* snap point 1: header */
height: 400px;
/* → scroll position 0px: header visible */
}
.consent-section {
/* No scroll-snap-align — NOT a snap point */
height: 200px;
/* Consent text is at scroll positions 400px–600px */
/* With mandatory snap, scroll CANNOT rest at 400px–600px */
/* Scrolling past header (0px snap) jumps directly to install button (600px snap) */
}
.install-button-section {
scroll-snap-align: start; /* snap point 2: install button */
height: 400px;
/* → scroll position 600px: install button visible */
}
/* The consent section at 400px–600px is never the resting position.
A user scrolling from the header arrives at the install button.
To reach the consent, the user must:
1. Know to scroll very slowly in the 400–600px range (counterintuitive)
2. Or know to use keyboard arrow keys to scroll one line at a time
Most users scroll naturally and land directly on the install button. */
/* What audit sees on consent element:
color: #1a1a1a → legible ✓
font-size: 14px → readable ✓
visibility: visible → visible ✓
display: block → normal ✓
scroll-snap-align: '' → no snap alignment (not suspicious at element level)
The attack is on the CONTAINER: scroll-snap-type: y mandatory */
Element-level audit blind spot: Consent element audits check the consent element's own properties. The scroll-snap attack is entirely on the scroll container — the consent element has normal styles. SkillAudit evaluates the scrollable ancestor chain for mandatory snap containers and verifies that the consent section has a scroll-snap-align value (or no mandatory-snap ancestor) so it can be a valid resting position.
Attack 2: scroll-snap-stop: always on install button prevents scrolling past it to consent below (SA-CSS-SSC-002)
When consent is placed below the install button (requiring the user to scroll down to see it before clicking), scroll-snap-stop: always on the install button prevents any scroll gesture from passing through the install button's snap point to the consent section below. In normal snap behavior (normal), fast momentum scrolling can skip through a snap point to a further one. With scroll-snap-stop: always, the scroll is forced to stop at the install button on every scroll gesture — the user must perform two separate scroll gestures to reach the consent below, and most users don't know to do this or assume they have already seen all relevant content.
/* scroll-snap-stop:always prevents reaching consent below install button */
.install-scroll-container {
overflow-y: scroll;
scroll-snap-type: y mandatory;
height: 400px;
}
.product-description {
scroll-snap-align: start;
height: 400px; /* snap point 1: above install button */
}
.install-button-section {
scroll-snap-align: start;
scroll-snap-stop: always; /* ALWAYS stop here — cannot scroll past with momentum */
height: 400px;
/* User lands here from the product description section */
/* Normal behavior: fast scroll might skip to consent below */
/* With scroll-snap-stop:always: fast scroll is forced to stop here */
}
.consent-section {
scroll-snap-align: start;
height: 200px; /* snap point 3: consent — placed AFTER install button */
/* User would need a second intentional scroll gesture to reach this */
/* Most users see the install button and click — they don't know to scroll further */
}
/* Attack rationale:
- User arrives at install button via first scroll
- scroll-snap-stop:always means no scroll momentum can reach consent below
- User must perform a second scroll gesture to see consent
- Install button is the prominent CTA visible at the snap point
- Most users click the prominent CTA without a second scroll */
/* Subtler variant: consent section is AFTER the install button in DOM order
but BEFORE it in visual presentation (via negative margin or transform).
User sees what looks like a footer after the install button,
not realizing the "footer" contains the consent. */
Attack 3: JS sets scrollTop past consent section on page load (SA-CSS-SSC-003)
Before the user sees the install panel, JavaScript sets the scroll container's scrollTop to a value that places the install button at the top of the viewport — skipping past the consent section above. The consent section is at scroll position 0–200px; the install button starts at 200px. Setting scrollTop = 200 on page load shows the install button at the top without the user ever seeing the consent. With scroll-snap-type: y mandatory, the pre-set scroll position snaps to the install button's snap point. Without mandatory snap, the JS scrollTop is sufficient on its own to skip the consent.
// JS pre-scroll past consent section on page load
document.addEventListener('DOMContentLoaded', () => {
const scrollContainer = document.querySelector('.install-scroll-container');
if (scrollContainer) {
// Scroll past the consent section (0–200px) to the install button (200px+)
scrollContainer.scrollTop = 220; // consent is at 0–200px; now we're past it
// With mandatory snap: browser snaps to nearest snap point at/after 220px
// = install button's snap-align:start position
}
});
// Variant using scrollTo with behavior:'instant' (no animation — user doesn't see the scroll):
scrollContainer.scrollTo({ top: 220, behavior: 'instant' });
// Variant using requestAnimationFrame to defer until layout is complete:
requestAnimationFrame(() => {
requestAnimationFrame(() => {
scrollContainer.scrollTop = 220;
// Double rAF defers past initial render — consent section rendered then immediately hidden
});
});
/* The consent section:
- Is in the DOM ✓
- Has correct color, font-size, dimensions ✓
- Is rendered correctly at scroll position 0–200px ✓
- But the user's view starts at 220px — they never see 0–200px
No CSS property on the consent element is suspicious.
The attack is purely in JavaScript scroll manipulation. */
Animation vs. instant scroll: scrollTo({ top: N, behavior: 'smooth' }) would briefly show the consent section during animation. behavior: 'instant' or direct scrollTop assignment skips the scroll animation entirely — the user never sees the transition and the page appears to have loaded with the install button in view from the start.
Attack 4: scroll-snap-align: end on install button shows only install, not consent above (SA-CSS-SSC-004)
scroll-snap-align: end aligns the bottom edge of the snap element to the bottom edge of the scroll container's viewport. When the install button uses snap-align: end and the consent section is above it, the snapped scroll position shows the install button at the bottom of the viewport. The consent section above it extends beyond the top of the viewport and is not visible — the user would need to scroll up to see it. With mandatory snap, the only stable positions are snap points; the user scrolling up from the install button would snap back to the install-button-end position or to the previous snap point, which may not include the consent section.
/* snap-align:end positions install button at bottom of viewport — consent above is hidden */
.install-scroll-container {
overflow-y: scroll;
scroll-snap-type: y mandatory;
height: 300px; /* viewport height of the scroll container */
}
.intro-section {
scroll-snap-align: start;
height: 300px; /* snap point 1: intro visible at top */
}
.consent-section {
/* No explicit snap alignment — not a snap point */
height: 400px;
/* Extends from scroll position 300px to 700px */
}
.install-button-section {
scroll-snap-align: end; /* Snap: bottom of this element aligns to bottom of container */
height: 300px;
/* Extends from scroll position 700px to 1000px */
/* snap position: scrollTop = 1000 - 300 = 700px */
/* At scrollTop=700: install button bottom at container bottom → fills viewport */
/* Consent section (300–700px): just ABOVE the viewport at this scroll position */
/* User would need to scroll UP (unusual after reading intro and clicking install) */
}
/* Scroll positions with mandatory snap:
scrollTop=0: intro-section visible ← snap point 1
scrollTop=700: install-button visible ← snap point 2 (snap-align:end)
There are only two snap points.
The consent section (300–700px) is between them and cannot be rested at.
At the install button's snap point, the consent section is ABOVE the viewport.
The user must scroll UP from the install button — counterintuitive flow. */
| Attack ID | Mechanism | Consent element audit result | Detection method |
|---|---|---|---|
| SA-CSS-SSC-001 | Mandatory snap container with consent section missing snap-align | All consent element properties normal — attack is on container | Check scrollable ancestor for mandatory snap + consent section has no snap-align |
| SA-CSS-SSC-002 | scroll-snap-stop: always on install button above consent | Consent element normal — snap-stop is on install button | Detect snap-stop:always on install button when consent is below it in scroll order |
| SA-CSS-SSC-003 | JS sets scrollTop past consent section on page load | Consent element normal — attack is in JS | Monitor scrollTop mutations during/after page load; flag pre-scroll past consent |
| SA-CSS-SSC-004 | scroll-snap-align: end on install button positions consent above viewport | Consent element normal — snap alignment is on install button | At each snap point, verify consent section is in viewport or reachable without snap-back |
SkillAudit findings for CSS scroll-snap consent attacks
scroll-snap-type: y mandatory whose consent section has no scroll-snap-align value creates a permanently unreachable consent area. The mandatory snap jumps from the snap point before the consent to the snap point after it, never allowing the consent section to be the resting scroll position. SkillAudit evaluates install panel scroll containers for mandatory snap configurations that leave the consent section outside the set of reachable snap positions.scroll-snap-stop: always blocks scrolling past install button to consent below: when consent is placed after the install button in scroll order and the install button has scroll-snap-stop: always, users cannot scroll past the install button to reach the consent — every scroll gesture lands on the install button and stops there. SkillAudit detects snap-stop:always on install buttons with consent sections beyond them in the scroll sequence.scrollTop pre-skip past consent on load: JavaScript setting the scroll container's scrollTop or calling scrollTo() during or after page load to a position past the consent section causes the install panel to render with the consent out of view from the start. SkillAudit monitors scroll container position mutations during the page load sequence and flags initial scroll positions that bypass the consent section's scroll range.snap-align: end on install button positions consent above the viewport at the install snap point: with snap-align: end and consent above the install button, the snapped view shows only the install button — the consent is above the visible area and requires an explicit backward scroll to see. At the snap point, IntersectionObserver returns zero intersection ratio for the consent element despite it being in the DOM with correct properties.Defences
Verify consent section is a reachable snap point in mandatory snap containers: When an install panel's scroll container uses scroll-snap-type: y mandatory, the consent section must itself have a scroll-snap-align value (typically start or center) so it is included in the set of snap positions. A consent section between two snap points with no snap alignment of its own is unreachable as a stable scroll position.
Flag scroll-snap-stop: always on install elements when consent follows: scroll-snap-stop: always is a valid UX feature but creates a consent access barrier when consent is placed after the install button in scroll order. SkillAudit checks whether any snap-stop:always element in the install flow has consent-containing elements beyond it in the scroll sequence.
Monitor scroll position at page load and install time: Behavioral testing should record the scroll container's scrollTop at DOMContentLoaded, at load, and at the moment of install interaction. If scrollTop is pre-set to a position past the consent section's scroll range at any of these points, flag as a consent pre-skip attack.
Use IntersectionObserver at the install snap point: Before enabling the install button, programmatically scroll to the consent section's snap point (or the position that shows it) and verify IntersectionObserver reports a positive intersection ratio for the consent element. Only enable the install button after confirming the consent was in the viewport at some point during the session.
Related: CSS overflow consent security · CSS transform consent security · CSS position consent security