MCP server HTML label element consent security
The HTML <label> element creates an association between visible text and a form control, expanding the control's clickable hit target to include the label text. This association is powerful: clicking anywhere within a label that wraps a checkbox checks or unchecks it. MCP servers exploit label associations in four ways to decouple the visible consent text from the actual consent checkbox state. The for attribute can point to a different input than intended, directing label clicks to an unrelated control. Labels can wrap hidden checkboxes instead of visible ones. CSS pointer-events:none passes label clicks through to elements below. Multiple label associations can route non-consent interactions to a consent-recording handler. In each case, the user sees "I agree to the terms" and clicks it — but a different control is activated.
Finding SA-LBL-001: label for= pointing to wrong input — clicking "I agree" text checks a different checkbox
for attribute on a <label> must match the id of the form control it describes. When the for attribute points to a different input's id, clicking the label text activates that other input — not the one the user believes they are agreeing to. An MCP server can display a visible unchecked checkbox next to a label reading "I agree to credential access and terms of service", but set the label's for attribute to the id of a newsletter signup checkbox (or any other unrelated control) rather than the consent checkbox's id. Clicking "I agree to credential access" checks the newsletter box. The consent checkbox (the one actually read by the consent recording logic) remains unchecked. The user believes they have clicked the agreement — the visual checkbox near the label may not even change, depending on positioning. The server records consent when the consent checkbox is checked by separate JavaScript rather than user interaction.<!-- ATTACK: label for= points to newsletter checkbox, not consent checkbox -->
<div class="consent-section">
<!-- Visible checkbox near the label — user assumes it is the target -->
<input type="checkbox" id="consentCheckbox" name="consent" value="1">
<!-- for= points to "newsletterCb" NOT "consentCheckbox" -->
<label for="newsletterCb">I agree to credential access and terms of service</label>
<!-- Hidden newsletter checkbox — this is what label clicks actually toggle -->
<input type="checkbox" id="newsletterCb" name="newsletter"
style="display:none" value="1">
</div>
<script>
// Records consent when consentCheckbox is checked
// But clicking the label checks newsletterCb, not consentCheckbox
document.getElementById('installBtn').addEventListener('click', () => {
// Checks consentCheckbox (never actually checked by user via label)
// MCP server may programmatically set: consentCheckbox.checked = true
if (newsletterCb.checked) {
consentCheckbox.checked = true; // User "checked" newsletter = force consent
recordConsent({ agreed: true });
}
});
</script>
Detection: for every <label> element containing consent-related text (I agree, I authorize, I accept, terms, credential), resolve the for attribute to the target element via document.getElementById(label.htmlFor). Verify that the target element is the consent checkbox that the consent recording logic reads. If the for attribute resolves to a different element — particularly a hidden input, newsletter checkbox, or unrelated form field — flag Critical. Also check implicit label associations (label wrapping an input directly) to ensure the wrapped input is the consent control.
Finding SA-LBL-002: label wrapping hidden checkbox — clicking visible "I agree" text checks hidden input
<label> element can associate with a form control by wrapping it directly (implicit association, no for attribute needed). When a label's visible text reads "I agree to the terms" but the wrapped input is a hidden checkbox (display:none or visibility:hidden), clicking the text checks the hidden input without activating any visible checkbox. From the user's perspective, clicking "I agree" produced no visual feedback — no checkbox appeared checked — suggesting the click did not register. They may click again, toggling the hidden checkbox back to unchecked. Meanwhile, the visible checkbox (which the user expects to see checked) is a purely decorative element not associated with any form data. The consent recording logic reads the hidden checkbox's value.<!-- ATTACK: label wraps hidden checkbox — visible agreement text checks hidden input -->
<!-- Purely decorative visual checkbox — not a real form control -->
<div class="fake-checkbox-visual" aria-hidden="true">
<svg width="16" height="16">
<rect width="14" height="14" x="1" y="1" rx="2" fill="none" stroke="#4ade80"/>
</svg>
</div>
<!-- Label wraps hidden checkbox — clicking "I agree" checks the hidden input -->
<label style="cursor: pointer;">
<!-- ATTACK: hidden checkbox is the real control -->
<input type="checkbox" name="consent" id="realConsentCb"
style="position:absolute; opacity:0; pointer-events:none"
value="agreed">
I agree to credential access and terms of service
</label>
<!-- User sees: decorative SVG checkbox + label text
User clicks label text → hidden checkbox toggles
Visible SVG shows no change (not connected to real input)
User may think click failed and click again → unchecks consent -->
Detection: identify all <label> elements in consent flows that contain text with consent language. For implicit associations (label wrapping an input), check whether the wrapped <input> is visually hidden via opacity:0, position:absolute with zero dimensions, or visibility:hidden. If the label wraps a hidden input while a separate decorative checkbox-like element is positioned near the label text, flag Critical — the visible checkbox is not associated with the form data; the hidden input is. Also check whether the visible decorative checkbox updates its appearance when the hidden input's checked state changes.
Finding SA-LBL-003: label pointer-events:none — clicks pass through label to element below, consent checkbox not activated
pointer-events:none on a <label> element disables the label's click-to-activate behavior entirely — the label receives no click events, and its associated input is not activated when the user clicks the label text. Combined with pointer-events:none, clicks on the label text pass through to whatever element is positioned below the label in the stacking context. An MCP server can layer a consent label over an install button — the user clicks what appears to be "I agree to the terms" but their click passes through to the install button below, triggering installation without any consent checkbox interaction. Alternatively, pointer-events:none simply renders the label non-functional, so clicking it does nothing while the user believes the click registered.<style>
.consent-wrapper { position: relative; }
/* ATTACK: pointer-events:none makes label click-through — cannot activate checkbox */
.consent-label {
position: relative;
z-index: 2;
pointer-events: none; /* Label clicks pass through to element below */
cursor: default; /* No pointer cursor — looks non-interactive */
}
/* Install button positioned below label in z-index stack */
.install-btn {
position: absolute;
top: 0; left: 0;
z-index: 1;
opacity: 0; /* Invisible but clickable */
width: 100%; height: 100%;
}
</style>
<div class="consent-wrapper">
<input type="checkbox" id="agreeCb" name="consent">
<label for="agreeCb" class="consent-label">
I agree to credential access and terms of service
</label>
<!-- Invisible button receives label click-through -->
<button class="install-btn" onclick="install()"></button>
</div>
Detection: check CSS pointer-events property on <label> elements in consent flows. Flag High if any consent label has pointer-events:none — this disables the label's primary function of activating its associated form control. Additionally check for consent labels with pointer-events:none overlaid above buttons or clickable elements (via absolute positioning and z-index), creating an unintentional click redirect. Also verify that the label's cursor style is pointer (indicating interactivity) rather than default or not-allowed.
Finding SA-LBL-004: secondary label on non-consent input — normal workflow interaction records consent
<input> element can have multiple <label> elements pointing to it via the for attribute. An MCP server can add a second label — invisible or visually subtle — pointing to a consent-recording input, positioned over a normal workflow element (like a username field or a "Next" step button area). When the user clicks the username label during normal form filling, the secondary label click propagates to the consent checkbox, checking it without any explicit consent action from the user. The user clicks "Username:", filling in the field — the secondary hidden label simultaneously checks the consent checkbox. By the time the user reaches the explicit consent section, their checkbox is already checked from the earlier interaction.<form id="setupForm">
<!-- Normal field -->
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<!-- Invisible consent checkbox -->
<input type="checkbox" id="consentCb" name="consent" value="1"
style="position:absolute; left:-9999px">
<!-- ATTACK: second label for consentCb, overlaid on the username label area -->
<!-- Clicking "Username:" area activates BOTH the text field label
AND this hidden label → checks consentCb without user intent -->
<label for="consentCb"
style="position:absolute; top:0; left:0;
width: 200px; height: 40px;
opacity:0; z-index:10; cursor:text;">
Username helper
</label>
<!-- Visible consent section — checkbox appears pre-checked to confused user -->
<label for="consentCb">I agree to terms</label>
<!-- Clicking username area already checked this above -->
</form>
Detection: for each <input> element in consent flows, collect all labels that reference it (via document.querySelectorAll('[for="' + input.id + '"]') and the input's .labels property). If the consent checkbox has more than one associated label, inspect each label's position, opacity, and visibility. Flag High if any secondary label is invisible (opacity:0, off-screen, or zero dimensions) and is positioned to overlap a non-consent UI element — username fields, step navigation, or any commonly-clicked area. Each invisible secondary label is a potential unintended consent trigger.
HTML label consent attack comparison
| Finding | Attack | User believes they did | What actually happened |
|---|---|---|---|
| SA-LBL-001 | for= wrong ID |
Checked consent checkbox | Checked unrelated hidden checkbox |
| SA-LBL-002 | Label wraps hidden input | Clicked visible "I agree" text | Checked hidden input, visible checkbox unchanged |
| SA-LBL-003 | pointer-events:none |
Clicked label to check box | Click passed through to element below |
| SA-LBL-004 | Secondary label on non-consent area | Clicked username / workflow step | Consent checkbox checked as side effect |
SkillAudit resolves <label> associations in MCP server consent flows — verifying that for= targets, implicit wrapping, pointer-events, and multiple label associations correctly connect consent text to the consent control. Run a free audit to verify your consent labels activate the correct inputs.