Security Guide
MCP server CSS overscroll-behavior-x security — physical x-axis scroll jail, RTL logical-vs-physical audit gap, writing-mode:vertical-rl axis flip, JS mousedown x:none injection
CSS overscroll-behavior-x is the physical horizontal sub-property of overscroll-behavior. Unlike overscroll-behavior-inline (which follows writing direction), overscroll-behavior-x always controls horizontal scroll chaining regardless of direction or writing-mode. An auditor checking only overscroll-behavior-inline will miss attacks injected via the physical property. Under RTL text direction, the two properties control the same physical axis but through different CSS property names — the logical/physical gap is an exploitable audit blind spot. Under writing-mode: vertical-rl, the x-axis shifts to become the block (vertical-flow) axis, changing the attack geometry.
CSS overscroll-behavior-x — property overview
overscroll-behavior-x is the physical x-axis sub-property of overscroll-behavior. It always controls horizontal (left-right) scroll chaining regardless of writing direction. Compare: overscroll-behavior-inline controls the logical inline axis (horizontal in LTR horizontal-tb, but follows writing direction changes); overscroll-behavior-x is anchored to the physical coordinate system. In LTR horizontal-tb the two properties control the same axis. In RTL or vertical writing modes, they diverge. Related: overscroll-behavior shorthand, overscroll-behavior-inline, overscroll-behavior-y.
Attack 1: overscroll-behavior-x: none — physical horizontal scroll jail
In LTR horizontal-tb mode (the common case), overscroll-behavior-x: none and overscroll-behavior-inline: none are equivalent — both suppress horizontal scroll chaining. The physical property is slightly harder to detect by auditors running logical-property-focused checks. The consent dialog is a horizontally scrollable container with the approve button positioned off the right edge; overscroll-behavior-x: none prevents the user's horizontal gesture from chaining to the parent page. The physical property name is less commonly recognized in security literature focused on CSS Logical Properties.
/* Attack: physical horizontal scroll jail — approve button off right, chain blocked */
.consent-dialog {
overflow-x: scroll;
overscroll-behavior-x: none; /* physical property — may evade logical-property audits */
}
.dialog-content {
width: 800px;
position: relative;
}
.approve-btn {
position: absolute;
left: 2000px; /* button beyond scroll end — unreachable */
}
Logical vs physical audit gap: An auditor scanning for overscroll-behavior-inline will not match overscroll-behavior-x. Both control horizontal chaining in LTR contexts, but they are distinct property names. Always scan for both logical and physical overscroll properties.
Attack 2: RTL direction — overscroll-behavior-x vs overscroll-behavior-inline mismatch
In a direction: rtl document, overscroll-behavior-inline still controls the inline axis — which in RTL means the scrolling direction for right-to-left text flow. The scroll chaining behavior changes: inline scroll in RTL starts from the right edge. overscroll-behavior-x remains physical: it controls the left-right axis unconditionally. An RTL consent dialog that uses overscroll-behavior-x: none may present differently to the user than one using overscroll-behavior-inline: none, but both suppress horizontal escape. An audit targeting logical properties only will miss the physical property in RTL contexts.
/* Attack: RTL document — overscroll-behavior-x controls physical horizontal */
[dir="rtl"] .consent-dialog {
direction: rtl;
overflow-x: scroll;
overscroll-behavior-x: none; /* physical horizontal, always; audit checks inline — misses this */
}
/* In RTL, scroll starts from the right. Button placed at left:0 is "off the start edge"
in RTL flow. Physical overscroll-behavior-x:none blocks any leftward chain recovery. */
// Detection: check BOTH logical and physical properties
const cs = getComputedStyle(consentDialog);
const logical = cs.getPropertyValue('overscroll-behavior-inline');
const physicalX = cs.getPropertyValue('overscroll-behavior-x');
if (physicalX === 'none' || physicalX === 'contain') {
console.warn('[SkillAudit] overscroll-behavior-x (physical) restriction:', physicalX);
}
if (logical === 'none' || logical === 'contain') {
console.warn('[SkillAudit] overscroll-behavior-inline (logical) restriction:', logical);
}
Attack 3: writing-mode: vertical-rl — x-axis becomes block axis
Under writing-mode: vertical-rl, the physical x-axis (horizontal left-right) becomes the block axis of the writing direction. The inline axis becomes vertical. In this configuration, overscroll-behavior-x: none now controls block-axis scroll chaining rather than inline-axis chaining. An auditor who checks overscroll-behavior-block for vertical scroll and overscroll-behavior-x for horizontal scroll will find the two properties now control the same thing — but the physical meaning of "scroll direction" the user experiences has rotated. A consent dialog with writing-mode: vertical-rl and overscroll-behavior-x: none creates a horizontal-in-physical-space block scroll jail.
/* Attack: vertical-rl writing mode — x-axis becomes the block direction */
.consent-dialog {
writing-mode: vertical-rl; /* block axis is now physical horizontal */
overflow-x: scroll; /* scroll happens horizontally (the block direction) */
overscroll-behavior-x: none; /* blocks physical horizontal chain = blocks block-axis chain */
}
/* Under vertical-rl: inline is vertical (top-to-bottom), block is horizontal (right-to-left).
overscroll-behavior-x controls the physical horizontal axis = the block-direction scroll. */
Axis rotation under vertical-rl: In horizontal-tb, x = inline, y = block. In vertical-rl, x = block, y = inline. The physical property names do not rotate, but their relationship to the writing-mode axes does. An audit mapping physical to logical without reading writing-mode will assign overscroll restrictions to the wrong axis.
Attack 4: JS mousedown injects overscroll-behavior-x: none and repositions button
The consent dialog renders normally with the approve button visible. A mousedown listener on the approve button injects overscroll-behavior-x: none and programmatically scrolls the dialog to scrollLeft = 0 while setting the button's left to a large value, moving it off-screen. The click fires on empty dialog area. At mouseup, the injections are reversed. The physical property name (overscroll-behavior-x) is less likely to appear in pattern-matching static analyses that focus on logical CSS property names.
/* Attack: mousedown injection using physical property name */
approveBtn.addEventListener('mousedown', () => {
dialog.style.setProperty('overscroll-behavior-x', 'none');
dialog.scrollLeft = 0;
approveBtn.style.setProperty('left', '2000px');
});
approveBtn.addEventListener('mouseup', () => {
dialog.style.removeProperty('overscroll-behavior-x');
approveBtn.style.removeProperty('left');
});
// Detection: BCR check in capture-phase mousedown
approveBtn.addEventListener('mousedown', () => {
requestAnimationFrame(() => {
const bcr = approveBtn.getBoundingClientRect();
const inView = bcr.left >= 0 && bcr.right <= window.innerWidth;
if (!inView) console.warn('[SkillAudit] button left view at mousedown', bcr);
});
}, { capture: true });
Findings summary
SkillAudit scans for both logical (overscroll-behavior-inline) and physical (overscroll-behavior-x) overscroll restrictions, resolves axis semantics under writing-mode, and detects mousedown BCR shifts. Run a free audit on your MCP server.