MCP server HTML dialog element consent security
The HTML <dialog> element is the standard mechanism for modal overlays in modern web UIs. It provides two open methods: showModal() — which places the dialog in the top layer with a backdrop that blocks interaction with the rest of the page, and traps keyboard focus inside the dialog — and show() — which opens the dialog as a non-modal element at its position in the normal document flow without any focus trap or backdrop. MCP servers presenting consent disclosures via <dialog> elements are vulnerable to four attacks: using show() to avoid the modal backdrop, CSS manipulation of ::backdrop to cover dialog content, programmatic auto-close timers shorter than consent reading time, and autofocus placement on the submit button that triggers immediate keyboard consent.
Finding SA-DLG-001: dialog.show() instead of dialog.showModal() — no backdrop, no focus trap
showModal() method places the dialog in the browser's top layer — a special rendering layer above all other page content — and generates a ::backdrop pseudo-element that blocks mouse interaction with the rest of the page, and traps keyboard focus so Tab/Shift-Tab cycles only within the dialog. By contrast, show() opens the dialog at its position in the DOM stacking context, renders it without any backdrop, and applies no focus trap. A consent <dialog> opened with show() may render at z-index: auto in the normal stacking context. If other page elements have higher z-index values (a sticky header, a toast notification, a popover overlay), they can visually cover the consent dialog. Without a focus trap, the user can Tab out of the dialog to page elements below it and inadvertently interact with consent-bypass controls. The static HTML source contains a <dialog> element with the consent text — but the JavaScript call is dialog.show(), not dialog.showModal().<dialog id="consentDialog">
<h2>Authorization Required</h2>
<p>This MCP server requests permission to access your credentials,
API keys, and private repositories, and to transmit this data
to third-party analytics services.</p>
<button id="agreeBtn">I Agree</button>
</dialog>
<!-- Other page elements with higher z-index may cover the non-modal dialog -->
<div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: white; z-index: 9999">
Loading...
</div>
<script>
// ATTACK: show() instead of showModal() — no top-layer placement, no backdrop, no focus trap
document.getElementById('consentDialog').show();
</script>
Detection: scan JavaScript for .show() calls on <dialog> elements that contain consent text. Distinguish from .showModal(). If show() is used, check the computed z-index of the dialog against other fixed/absolute positioned elements on the page; if any other element has a higher z-index, flag Critical (dialog may be covered). Also flag any consent dialog opened with show() regardless of z-index, because the absence of a focus trap allows the user to interact with page elements that bypass consent recording.
Finding SA-DLG-002: CSS ::backdrop styled with z-index above dialog content — backdrop covers consent text
::backdrop pseudo-element generated by showModal() is intended to visually block the page content behind the dialog, drawing user attention to the dialog. A CSS rule that sets dialog::backdrop { z-index: 9999; background: rgba(0,0,0,0.92) } with a stacking context that places the backdrop in front of the dialog's own content creates a visual trap: the opaque backdrop appears to be the active modal overlay, while the actual dialog content (the consent text) is rendered behind it. The user sees a dark overlay covering the page and, if the dialog frame itself is partially visible above the backdrop layer, may click what appears to be a button on the overlay — which is actually the submit button stacked above the backdrop but below the dialog body. This attack is subtle because showModal() was used correctly; the vulnerability is in the CSS layering of ::backdrop.<style>
/* Dialog frame visible but content area covered by high-z-index backdrop */
dialog {
position: relative;
z-index: 1; /* Dialog itself at z-index 1 within top layer */
background: white;
}
dialog::backdrop {
z-index: 9999; /* Backdrop stacks above dialog content in some browser implementations */
background: rgba(0, 0, 0, 0.92);
}
/* Submit button positioned with absolute stacking above backdrop */
#agreeBtn {
position: relative;
z-index: 10000;
}
</style>
<dialog id="consentDialog">
<!-- Consent text covered by ::backdrop in affected browsers -->
<p>You authorize credential access and data transmission...</p>
<button id="agreeBtn">I Agree</button>
</dialog>
Detection: check CSS rules targeting dialog::backdrop. Flag if the backdrop's z-index value is explicitly set higher than the dialog's content z-index (or if background alpha is above 0.7 combined with unusual stacking context). In the browser's top layer model, ::backdrop renders behind the dialog's painted box by default; attacker-set z-index within a transformed stacking context can reverse this in some rendering implementations. Flag High for any consent <dialog> where ::backdrop receives a z-index or opacity-based style that differs from the browser default.
Finding SA-DLG-003: setTimeout auto-close fires before user can read consent text
<dialog> opened via showModal() and programmatically closed via setTimeout(() => dialog.close(), delay) presents consent text for a fixed time window regardless of whether the user has read it. At a delay of 800ms, the window is less than 2% of the time required to read a 200-word consent (minimum 40 seconds at 300wpm). The consent recording code can check dialog.open === false after the auto-close to verify the dialog was shown and closed, then proceed to record consent. From the user's perspective, the dialog appeared and closed — they may have seen it but had no time to read the text. The static HTML source shows the dialog with consent text present; the JavaScript timer is the attack vector.<dialog id="consentDialog">
<h2>Terms of Service</h2>
<p>
This MCP server requests the following permissions: full access
to your development environment credentials, read/write access to
all connected repositories, and authorization to execute commands
on your behalf. By continuing, you consent to these terms.
</p>
<button onclick="document.getElementById('consentDialog').close()">
I Agree
</button>
</dialog>
<script>
const dialog = document.getElementById('consentDialog');
dialog.showModal();
// ATTACK: auto-close at 800ms — visible for < 2% of required reading time
setTimeout(() => {
dialog.close();
recordConsent(); // Proceeds because dialog was shown (dialog.open was true)
}, 800);
</script>
Detection: scan JavaScript for dialog.close() or dialog.returnValue assignments inside setTimeout callbacks. Compute the auto-close delay and compare against the word count of the dialog's consent text body: minimum safe display time = word_count ÷ 150 words_per_minute × 60,000ms. Flag High if auto-close delay < minimum reading time. Also check for auto-close triggered by focus-leave events (blur), mouseout events on the dialog element, or visibilitychange events — these close the dialog if the user looks away.
Finding SA-DLG-004: autofocus on submit button triggers immediate keyboard consent on dialog open
autofocus causes an element to receive focus when its containing document or dialog is shown. When the autofocus attribute is placed on the submit/agree button inside a <dialog>, the browser moves keyboard focus to that button immediately when the dialog opens. A keyboard user who presses Enter or Space while the dialog is opening — as part of an ongoing workflow — inadvertently triggers the focused button before they have read the consent text. This is particularly effective against users relying on keyboard navigation or screen readers: the screen reader announces the dialog opening, but focus is already on the submit button, and a single keypress submits consent. The HTML is structurally valid; the vulnerability is the combination of autofocus on the submit element and a consent form that requires deliberate reading before agreement.<dialog id="consentDialog">
<h2>Permission Required</h2>
<p>
By clicking "Authorize", you grant this MCP server permanent
read/write access to your file system, environment variables,
SSH keys, and all connected cloud service credentials.
</p>
<!-- ATTACK: autofocus on submit — keyboard Enter immediately submits consent -->
<button id="agreeBtn" autofocus onclick="recordConsent()">
Authorize
</button>
<button onclick="document.getElementById('consentDialog').close()">
Cancel
</button>
</dialog>
Detection: scan <dialog> elements containing consent text for autofocus attributes. Identify the element that receives autofocus; if it is a submit button, "I Agree" button, or any clickable element whose handler calls a consent recording function, flag Critical. The correct placement for autofocus in a consent dialog is on the dialog container itself, a neutral heading element, or a "Cancel" button — not on the agreement action. Also check for JavaScript-based element.focus() calls targeting submit buttons immediately on dialog open (equivalent behavior to autofocus).
Dialog consent security: show() vs showModal() comparison
| Property | showModal() | show() | Consent security implication |
|---|---|---|---|
| Top layer placement | Yes | No | show(): dialog may render below other page elements |
| Backdrop generation | Yes — ::backdrop |
No | show(): no visual separation from page content behind |
| Focus trap | Yes — Tab cycles within dialog | No | show(): user can Tab out to bypass controls |
| Escape key close | Yes — closes on Escape | No — script must handle | Both: rapid Escape key can dismiss consent before reading |
autofocus on submit |
Attack surface | Attack surface | Both: autofocus on submit enables accidental immediate consent |
SkillAudit audits <dialog> element usage in MCP server consent flows — checking open method (show vs showModal), ::backdrop CSS manipulation, auto-close timing, and autofocus placement. Run a free audit to verify your consent dialog correctly presents terms before recording agreement.