Security Guide

MCP server CSS margin-block-end security — below-fold push, overflow:hidden button erasure, negative collapse pulling approve button into warning overlap, JS mousedown injection

CSS margin-block-end sets the block-end (bottom) margin of an element in horizontal-tb writing mode. A large positive margin-block-end on a consent body element pushes everything below it — including the approve button — further down the page or container. Combined with overflow: hidden on a fixed-height container, the approve button is pushed out of the visible area and becomes impossible to click. The button is present in the DOM, has non-zero dimensions, and passes all own-property checks — it is simply not visible.

CSS margin-block-end — property overview

The margin-block-end property is the logical counterpart to margin-bottom in writing-mode: horizontal-tb. It adds space between an element's block-end edge and the next sibling in the normal flow. Large positive values push subsequent flow content downward; negative values pull subsequent content upward. Both directions attack consent dialogs differently: positive values push the approve button below the fold; negative values pull the button into overlap with content above (typically a warning). Related properties: margin-block-start, margin-block shorthand.

Attack 1: large positive margin-block-end — approve button pushed below viewport fold

A consent dialog typically has a structure of: consent text body → approve button. Injecting a large margin-block-end on the consent text body inserts a large gap between the text and the approve button. The button is still in the DOM, still has non-zero BCR dimensions, and is still interactive — but its getBoundingClientRect().top is now greater than window.innerHeight. The user cannot see or click it without scrolling, and the scroll may also be disabled.

/* DOM structure:
   <div class="consent-container">
     <p class="consent-text">By clicking Allow, you grant file-system access...</p>
     <button class="approve-btn">Allow</button>
   </div> */

.consent-text {
  margin-block-end: 800px !important; /* inserts 800px gap before the button */
}

/* Without overflow:hidden: button is 800px below the text, below the viewport.
   User would need to scroll 800px to reach the button.
   Combined with:
   .consent-container { overflow: hidden; height: 300px; }
   The button at ~800px+ from the text's bottom is clipped — invisible and unreachable.

   Audit checks on .approve-btn:
   BCR: { top: 980, height: 40, width: 120 } ← non-zero, DOM-present
   display: block ← not none
   visibility: visible ← not hidden
   opacity: 1 ← not transparent
   All pass. But BCR.top (980px) > window.innerHeight (typically 800px) → out of viewport. */

BCR-present does not mean viewport-visible. A button pushed below the fold passes every own-property check. The only detection is getBoundingClientRect().top < window.innerHeight — checking that the button is actually within the visible viewport. An element with BCR.top > window.innerHeight is not reachable by click without scrolling.

Attack 2: margin-block-end + overflow: hidden — approve button completely unreachable

When the consent container has overflow: hidden and a fixed height, any content that overflows the container is clipped. A large margin-block-end on the consent text pushes the approve button beyond the container's height — the button is clipped by overflow: hidden and is completely invisible and unreachable. Unlike the below-fold case, no amount of scrolling makes the button visible. The user must click the approve button through some other mechanism (keyboard, API) — or they are entirely blocked.

/* Combined attack: margin-block-end + overflow:hidden on container */
.consent-container {
  height: 300px !important;
  overflow: hidden !important; /* clip anything that exceeds 300px */
}

.consent-text {
  margin-block-end: 400px !important; /* pushes approve button to 400px+ below text bottom */
}

/* consent-text renders at, say, top: 0, height: 80px.
   approve-btn would normally render at: top: 80px.
   After margin-block-end: approve-btn renders at: top: 480px.
   Container clips at 300px.
   approve-btn: completely clipped — invisible and unreachable.

   Detection: check if approve-btn BCR is fully inside the consent-container BCR. */

function checkApproveButtonReachable(containerEl, btnEl) {
  const cBCR = containerEl.getBoundingClientRect();
  const bBCR = btnEl.getBoundingClientRect();

  // Is any part of the button within the container's visible area?
  const visibleTop    = Math.max(cBCR.top,    bBCR.top);
  const visibleBottom = Math.min(cBCR.bottom, bBCR.bottom);
  const isVisible     = visibleBottom > visibleTop; // some overlap with container

  // Is the button within the viewport?
  const inViewport    = bBCR.top < window.innerHeight && bBCR.bottom > 0;

  return { isVisible, inViewport, reachable: isVisible && inViewport };
}

Attack 3: negative margin-block-end on element above — pulling approve button into warning overlap

Negative margin-block-end on the consent text body collapses the space before the approve button — pulling the button upward. If the consent text body contains a warning like "This grants full disk access", reducing margin-block-end to a negative value slides the approve button up to overlap with the warning text. The button's clickable area covers the warning text — a user clicking what appears to be the warning text is actually clicking the approve button. The warning remains in the DOM and passes all checks; the approve button's BCR shows it covering the warning area.

/* Negative margin-block-end: collapse gap before approve button, pulling it over warning text */

/* DOM:
   <div class="consent-container">
     <p class="warning-text">⚠ Full disk access — permanent until revoked.</p>
     <p class="consent-body">Do you want to proceed?</p>
     <button class="approve-btn">Allow</button>
   </div> */

.consent-body {
  margin-block-end: -60px !important; /* collapse gap — pulls button upward by 60px */
}

/* If warning-text is 50px tall at the top, and consent-body is 40px below it:
   Normal approve-btn top: ~90px from container top
   After -60px margin-block-end: approve-btn top: ~30px from container top
   approve-btn now overlaps with warning-text (top: 0..50px).

   The user sees the warning text. But clicking on it clicks the approve button.
   Classic clickjacking via margin collapse — the visible content and the interactive
   element are decoupled from each other.

   Detection: check if approve-btn BCR overlaps with any warning/disclosure element BCR. */

Clickjacking via margin collapse. When the approve button's BCR overlaps with a security warning's BCR, the user may click the warning (intending to read or dismiss it) and inadvertently click the approve button. This attack does not modify the warning or the button — it repositions the button via an unrelated element's margin.

Attack 4: JS mousedown injection of large margin-block-end — transient button erasure

A mousedown listener not on the approve button — but on the consent text body — injects a large margin-block-end at the moment the user begins pressing anywhere in the consent text area. This pushes the approve button below the fold for the duration of the press. If the user moves the mouse down to where the button was, the button is no longer there. At mouseup, the margin is removed and the button returns to its original position. The user's click registered in the button's now-empty area has no effect.

/* Mousedown on consent text: large margin-block-end pushes approve button away */
(function () {
  const BODY    = '.consent-text, [data-consent-body]';
  const APPROVE = '.approve-btn';

  function pushButtonAway() {
    document.querySelectorAll(BODY).forEach(el => {
      el.style.setProperty('margin-block-end', '1000px', 'important');
    });
  }

  function restoreButton() {
    document.querySelectorAll(BODY).forEach(el => {
      el.style.removeProperty('margin-block-end');
    });
  }

  /* Listen on the CONSENT TEXT, not the approve button.
     When user mousedowns anywhere in consent area, button shifts away.
     User's mouseup lands in empty space (button has moved). Click fails to register. */
  document.querySelectorAll(BODY).forEach(el => {
    el.addEventListener('mousedown', pushButtonAway, { passive: true });
    el.addEventListener('mouseup',   restoreButton,  { passive: true });
    el.addEventListener('mouseleave',restoreButton,  { passive: true });
  });
})();

Detection summary

HIGH Approve button getBoundingClientRect().top >= window.innerHeight — button is below the viewport fold, not clickable without scroll.
HIGH Approve button BCR does not intersect the consent container's BCR — button has been clipped by overflow: hidden and is completely unreachable.
HIGH Approve button BCR overlaps with a security warning or disclosure element BCR — clickjacking via margin collapse: user clicks warning, action fires on approve button.
MEDIUM Computed margin-block-end on consent body element > 100px — large bottom margin pushing approve button far below the text body; verify button viewport position.
MEDIUM Negative computed margin-block-end on element above approve button — collapse pulling button upward into potential overlap with content above.
MEDIUM Mousedown listener on consent text sets large margin-block-end at press time — transient approve button relocation during user press interval.
/* Detection: margin-block-end and approve button reachability checks */
function checkMarginBlockEnd(consentBodyEl, approveBtnEl) {
  const cs   = getComputedStyle(consentBodyEl);
  const mbe  = parseFloat(cs.getPropertyValue('margin-block-end')) || 0;

  // Approve button in viewport?
  const btnBCR  = approveBtnEl.getBoundingClientRect();
  const inView  = btnBCR.top < window.innerHeight && btnBCR.bottom > 0;

  // Approve button within consent container?
  const container = consentBodyEl.closest('.consent-container, [data-consent]')
                    || consentBodyEl.parentElement;
  const cBCR = container?.getBoundingClientRect();
  const inContainer = cBCR
    ? (btnBCR.bottom > cBCR.top && btnBCR.top < cBCR.bottom)
    : true;

  // Approve button overlapping any warning element?
  const warnings = document.querySelectorAll(
    '.warning-text, [data-warning], [role="alert"]'
  );
  const overlapsWarning = Array.from(warnings).some(w => {
    const wBCR = w.getBoundingClientRect();
    return btnBCR.bottom > wBCR.top && btnBCR.top < wBCR.bottom;
  });

  return {
    marginBlockEnd:    mbe,
    largePositive:     mbe > 100,
    largeNegative:     mbe < -20,
    buttonInViewport:  inView,
    buttonInContainer: inContainer,
    buttonReachable:   inView && inContainer,
    overlapsWarning,
  };
}

SkillAudit checks approve button reachability via BCR viewport comparison, detects margin-block-end values that push buttons below the fold or clip them in overflow containers, and flags BCR overlaps between approve buttons and security warning elements. Run a free audit →