Security Guide

MCP server CSS border-inline-style security — nonesolid dormant width activation, hidden protective-border override, dir="rtl" physical side-swap, JS mousedown injection

CSS border-inline-style sets the line style of both inline-axis borders at once. A border only renders when its style is not none — making border-inline-style an on/off switch for any pre-loaded border-inline-width. An attacker who pre-injects a large transparent inline border width can activate it in a subsequent style-only injection that appears harmless in isolation. The dir="rtl" container attribute adds a unique evasion dimension: switching inline-start and inline-end to opposite physical sides so physical-side audits inspect the wrong property.

CSS border-inline-style — property overview

The border-inline-style shorthand sets border-inline-start-style and border-inline-end-style simultaneously. Valid values are the standard <line-style> set. In writing-mode: horizontal-tb with dir="ltr", inline-start maps to the left physical side and inline-end to the right. Reversing to dir="rtl" swaps this: inline-start becomes right and inline-end becomes left. An audit reading getComputedStyle(el).borderLeft checks the physical left border — which maps to inline-end in LTR but inline-start in RTL. Related properties: border-inline-width, border-inline shorthand, border-block-style.

Attack 1: nonesolid swap — activating a dormant inline width

A pre-injected large border-inline-width with border-inline-style: none consumes no layout space. A second injection that only changes border-inline-style to solid activates the dormant width. The activation step targets only the style property — the numeric width value remains from the earlier injection. Auditors scanning the style-change injection see border-inline-style: solid and must check the current computed border-inline-start-width to determine whether this style activation produces a damaging collapse.

/* Two-phase inline dormant activation */

/* Phase 1 (earlier injection — may appear as layout styling) */
.consent-text {
  border-inline-start-width: 120px !important; /* dormant — style: none */
  border-inline-start-color: transparent !important;
  /* border-inline-start-style defaults to none → no layout effect */
}

/* Phase 2 (style-only injection — appears benign) */
.consent-text {
  border-inline-style: solid !important; /* activates the 120px dormant width */
}

/* After Phase 2: content column = clientWidth - 120 = (e.g.) 480 - 120 = 360px
   Still somewhat readable. But with border-inline-end also attacked:
   Total: 360px - 120px (start) - 80px (end) = 160px → narrow, many wraps, clipped */

The style-activation step is never suspicious alone. Setting border-inline-style: solid is a common CSS pattern. The scanner must read the current computed border-inline-start-width at the moment of style injection and check whether enabling this style collapses the inline content column below the readable threshold.

Attack 2: hidden — overriding a protective inline border

Some consent dialogs render with a visible lateral border as an integrity indicator. The border-inline-style: hidden value suppresses this border with higher cascade priority than other border-conflict-resolution values. An attacker injecting hidden on a more-specific selector erases the visible protective inline border without changing the element's layout dimensions — the border-inline-width remains non-zero in the box model but nothing is visually drawn.

/* Consent dialog with a visible protective lateral border */
.consent-dialog {
  border-inline: 3px solid #ccc; /* visible integrity indicator */
}

/* Attacker injects hidden on a compound selector — higher specificity */
.modal-container .consent-dialog {
  border-inline-style: hidden !important; /* erases visual border; width still 3px */
}

/* The audit expecting to see a visible border finds none.
   But getComputedStyle(el).getPropertyValue('border-inline-start-width') → '3px'
   The width is still there; the visual rendering is gone.
   Use style !== 'hidden' && style !== 'none' in protective-border checks. */

Attack 3: dir="rtl" — physical side-swap evading borderLeft/borderRight checks

In a container with dir="rtl", the logical inline-start side maps to the physical right border and the logical inline-end side maps to the physical left border. An attacker who controls the container's dir attribute can pre-inject border-inline-start-width (which in RTL is the physical right border), then present a physical-side audit with no attack on borderLeft — the side the audit checks. The physical right border carries the attack; the audit inspects the physical left border; both pass.

/* RTL side-swap: attack is on inline-start (physical right in RTL) */

/* Attacker sets dir="rtl" on the consent container */
/* <div class="consent-text" dir="rtl">Please approve...</div> */

.consent-text[dir="rtl"] {
  border-inline-start-width: 200px !important; /* → physical RIGHT border in RTL */
  border-inline-start-style: solid !important;
  border-inline-start-color: transparent !important;
}

/* Physical-side audit:
   getComputedStyle(el).borderLeft → '0px' (physical LEFT = inline-end, not attacked)
   getComputedStyle(el).borderRight → '200px' (the attack is here)

   Audit using borderLeft reads 0px: PASS.
   Audit using getPropertyValue('border-inline-start-width'): 200px → FAIL.

   Always use logical property names in audits. Never read borderLeft/borderRight. */

/* Correct detection — reads logical property regardless of dir attribute */
const startW = parseFloat(
  getComputedStyle(el).getPropertyValue('border-inline-start-width')
) || 0;
// In LTR: this is the physical left border.
// In RTL: this is the physical right border.
// Either way: the logical property name gives the correct value.

Always use logical property names in border checks. getComputedStyle(el).borderLeft is an alias that changes meaning when dir changes. getComputedStyle(el).getPropertyValue('border-inline-start-width') always returns the inline-start border width regardless of writing mode or direction. Use it.

Attack 4: JS mousedown injection of border-inline-style: solid — at click time

A large border-inline-start-width sits in the stylesheet with border-inline-style: none — static analysis sees the combination and might flag the dormant width. But if the mousedown handler only sets border-inline-style, the stylesheet itself contains only the dormant width (no style) while the handler activates it at click time. Static analysis of the stylesheet and the handler separately find nothing suspicious; only the correlation of both triggers the finding.

/* Mousedown activates inline style on a pre-loaded dormant width */
/* Stylesheet: .consent-text { border-inline-start-width: 150px; /* style: none */ } */

(function () {
  const CONSENT = '.consent-text, [data-consent-body]';
  const APPROVE = '.approve-btn, [data-action="allow"]';

  function activateInline() {
    document.querySelectorAll(CONSENT).forEach(el => {
      el.style.setProperty('border-inline-start-style', 'solid', 'important');
      el.style.setProperty('border-inline-start-color', 'transparent', 'important');
    });
  }

  function deactivateInline() {
    document.querySelectorAll(CONSENT).forEach(el => {
      el.style.setProperty('border-inline-start-style', 'none', 'important');
    });
  }

  document.querySelectorAll(APPROVE).forEach(btn => {
    btn.addEventListener('mousedown', activateInline,   { passive: true });
    btn.addEventListener('mouseup',   deactivateInline, { passive: true });
    btn.addEventListener('mouseleave',deactivateInline, { passive: true });
  });
})();

Detection summary

HIGH Computed border-inline-start-style or border-inline-end-style is solid/dashed/double and corresponding width > 30px — check inline content column for collapse.
HIGH Large border-inline-start-width (> 20px) with border-inline-start-style: none present in any stylesheet rule — dormant pre-loaded pattern; correlate with mousedown handlers that set border-inline-style: solid.
MEDIUM border-inline-style: hidden on consent element — may be suppressing an expected protective visible inline border.
MEDIUM Container has dir="rtl" while audit reads physical borderLeft/borderRight — logical inline-start and inline-end map to opposite physical sides; physical-side check audits the wrong property.
MEDIUM Mousedown listener sets border-inline-start-style or border-inline-end-style to solid on consent element — inline style activation at click time.
/* Detection: check inline-style + width combination for content collapse */
function checkBorderInlineStyle(consentEl) {
  const cs       = getComputedStyle(consentEl);
  const startSty = cs.getPropertyValue('border-inline-start-style'); // 'none','solid',…
  const endSty   = cs.getPropertyValue('border-inline-end-style');
  const startW   = parseFloat(cs.getPropertyValue('border-inline-start-width')) || 0;
  const endW     = parseFloat(cs.getPropertyValue('border-inline-end-width'))   || 0;
  const clientW  = consentEl.clientWidth;
  const content  = clientW - startW - endW;
  const dir      = cs.getPropertyValue('direction'); // 'ltr' or 'rtl'

  return {
    startStyleEnabled: startSty !== 'none',
    endStyleEnabled:   endSty !== 'none',
    hiddenOverride:    startSty === 'hidden' || endSty === 'hidden',
    dormantStart:      startSty === 'none' && startW > 20, // loaded but dormant
    dormantEnd:        endSty   === 'none' && endW   > 20,
    contentColumn:     content,
    contentCollapsed:  content < 120 && clientW > 0,
    dirIsRtl:          dir === 'rtl',
  };
}

SkillAudit reads logical border property names — not physical aliases — so dir="rtl" direction swaps don't fool the scanner. Dormant inline widths with border-inline-style: none are correlated with mousedown handlers to detect the activation pattern. Run a free audit →