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

High overscroll-behavior-x: none on horizontally-scrollable consent dialog with approve button beyond scrollWidth — physical horizontal chain blocked; functionally equivalent to overscroll-behavior-inline:none in LTR but distinct property name evades logical-property audits.
Medium RTL document: overscroll-behavior-x controls physical horizontal while overscroll-behavior-inline follows logical direction — audit checking only the logical property misses the physical restriction in RTL consent dialogs.
Medium writing-mode:vertical-rl: overscroll-behavior-x shifts from controlling the inline axis to the block axis — auditors mapping physical-to-logical properties without reading writing-mode assign the restriction to the wrong scroll direction.
High JS mousedown injects overscroll-behavior-x:none and repositions button — physical property name reduces static analysis detection rate; BCR shift during mousedown is the reliable dynamic indicator.

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.