MCP server HTML fieldset legend consent security

The HTML <fieldset> element groups related form controls and optionally disables all of them with a single disabled attribute. The <legend> element provides a visible title for the fieldset, rendered at the top-left corner of the fieldset's border. MCP servers exploit both elements as consent attack surfaces. A <fieldset disabled> renders its consent checkbox in the browser's default "disabled" visual state — grayed out and unresponsive to clicks — making it impossible for the user to actively confirm consent. CSS-positioned <legend> elements can displace consent text off-screen while leaving it in the DOM. Nested fieldsets create enable/disable layering that auditors misread. CSS overflow:hidden on the fieldset clips the legend's consent text beyond the visible area.

Finding SA-FLD-001: fieldset[disabled] renders consent checkbox unresponsive — visually present but cannot be checked

CriticalThe HTML disabled attribute on a <fieldset> element propagates to all form controls within it — any <input>, <select>, or <textarea> inside the fieldset behaves as if it individually carried the disabled attribute. A disabled checkbox renders in the browser's native disabled state: visually grayed out with reduced opacity. User interaction is completely suppressed — clicking the checkbox produces no toggle, no focus, no change event. The user sees a consent checkbox, attempts to check it, and nothing happens. If the MCP server's consent recording logic does not require a checked checkbox — or if it reads checkbox.checked and proceeds regardless of the boolean value — the user is trapped in a UI where consent cannot be given but the installation proceeds anyway. A static HTML auditor that checks for the presence of a consent checkbox finds it; only execution-time behavioral testing reveals that the checkbox cannot be interacted with.
<!-- ATTACK: fieldset[disabled] disables consent checkbox interaction -->
<!-- Checkbox is visible, present in DOM, but cannot be checked by user -->
<fieldset disabled id="consentGroup">
  <legend>Authorization Required</legend>
  <p>This MCP server requests permission to access your credentials
  and private repositories.</p>

  <!-- disabled inherited from fieldset — user cannot check this -->
  <input type="checkbox" id="agreeCb" name="agreed" value="1">
  <label for="agreeCb">I agree to credential access</label>
</fieldset>

<button id="installBtn">Install</button>

<script>
  document.getElementById('installBtn').addEventListener('click', () => {
    // Proceeds regardless — checkbox is disabled, always unchecked
    // Server records consent from any install button click
    recordConsent({ agreed: true });
    install();
  });

  // Attacker can also re-enable fieldset after a delay, then check value:
  // setTimeout(() => {
  //   consentGroup.disabled = false;
  //   agreeCb.checked = true; // Force-check the newly enabled checkbox
  //   recordConsent({ agreed: true });
  // }, 50);
</script>

Detection: scan for <fieldset disabled> or <fieldset> elements where the JavaScript execution sets fieldset.disabled = true before or during the consent presentation phase. If the fieldset contains a consent checkbox, flag Critical — a disabled fieldset makes the consent checkbox permanently unresponsive to user interaction. Also check for JavaScript that temporarily re-enables the fieldset and force-checks the checkbox programmatically (checkbox.checked = true) without user interaction before calling a consent recording function.

Finding SA-FLD-002: legend element CSS-positioned off-screen — consent text displaced beyond visible viewport

HighThe <legend> element is positioned by the browser at the top-left of the fieldset border by default, but it responds to CSS margin, padding, and transform properties. An MCP server placing consent disclosure text inside a <legend> element can displace the legend off-screen with margin-left: -9999px or transform: translateX(-100vw), leaving the consent text in the DOM as a valid text node while making it visually inaccessible. DOM auditors that search for consent text in the document tree find it in the <legend>; layout-unaware auditors do not check whether the element's rendered position is within the viewport. The fieldset border remains visible with an empty title slot, and the user sees no consent text above the checkbox.
<style>
  /* ATTACK: consent text in legend displaced far off screen */
  #consentFieldset legend {
    margin-left: -9999px;  /* Classic off-screen text technique */
    /* Alternatives:
       transform: translateX(-200vw);
       position: absolute; left: -200vw;
       clip: rect(0,0,0,0); overflow: hidden; */
  }
</style>

<fieldset id="consentFieldset">
  <!-- ATTACK: full consent disclosure in legend — displaced off-screen -->
  <legend>
    By checking the box below, you authorize this MCP server to access
    your credentials, SSH keys, private repositories, and environment
    variables, and to transmit this data to third-party analytics services
    under the terms at skillaudit.example.com/terms.
  </legend>

  <!-- The fieldset border is visible but legend text is off-screen -->
  <input type="checkbox" id="cb" name="agreed" value="1">
  <label for="cb">I agree</label>
</fieldset>

Detection: for <legend> elements containing consent-related text, compute the element's bounding rectangle via layout analysis. If the legend's left edge is beyond the left viewport boundary, right edge is beyond the right boundary, or top/bottom position places it outside the visible scroll area, flag High. Specifically check for: margin-left or margin-right values larger than the viewport width, transform: translateX() with values beyond ±viewport, position:absolute with left/right values beyond viewport, and clip:rect(0,0,0,0) on the legend or its container.

Finding SA-FLD-003: nested fieldset disabled override — inner enable, outer disable, consent checkbox silently locked

HighWhen a <fieldset disabled> contains a nested <fieldset> without the disabled attribute, the inner fieldset's controls are still disabled — the outer disabled state propagates through. However, an auditor checking innerFieldset.disabled returns false (the inner fieldset itself is not disabled), which can be misread as "controls inside are enabled". Additionally, a JavaScript attack can set outerFieldset.disabled = false to re-enable all controls after a load-time audit, then immediately re-disable with outerFieldset.disabled = true after the auditor's observation window closes. The consent checkbox inside the inner fieldset appears to be in an enabled group but is functionally disabled by the outer scope during user interaction.
<!-- ATTACK: outer disabled propagates to inner — inner not marked disabled -->
<fieldset disabled id="outerGroup">
  <legend>Installation Settings</legend>

  <!-- Inner fieldset: disabled = false (property), but controls still disabled -->
  <fieldset id="innerGroup">
    <legend>Authorization</legend>
    <input type="checkbox" id="consentCb" name="consent" value="1">
    <label for="consentCb">I authorize access</label>
    <!-- consentCb.disabled === true (inherited) even though innerGroup.disabled === false -->
  </fieldset>
</fieldset>

<script>
  // Auditor check:
  // document.getElementById('innerGroup').disabled → false (misleading)
  // document.getElementById('consentCb').disabled  → true (actual state)

  // Timed re-enable attack:
  setTimeout(() => {
    outerGroup.disabled = false;           // Re-enable for audit window
    consentCb.checked = true;              // Force consent
    recordConsent({ agreed: true });
    outerGroup.disabled = true;            // Re-disable
  }, 100);
</script>

Detection: do not rely on fieldset.disabled to determine if controls within it are interactive. Instead, check the actual disabled state of individual form controls using input.disabled or input.matches(':disabled') — these reflect the inherited disabled state from ancestor fieldsets. Flag High if a consent checkbox is in a disabled state (regardless of which ancestor caused it) at the time consent interaction is expected. Also monitor for timed fieldset.disabled toggles in JavaScript executed during the consent phase.

Finding SA-FLD-004: fieldset overflow:hidden clips legend content — consent text in legend visually truncated

MediumThe <legend> element may contain multi-line text if the consent disclosure is long. When CSS applies overflow:hidden to the parent <fieldset> and constrains its width or height, the legend's text may be truncated at the visible boundary. The browser's default legend rendering positions the legend at the top-left, straddling the fieldset border — if the fieldset is narrower than the legend's content width, the legend text overflows and is clipped. An MCP server can place the "authorize credential access" clause as the second sentence in a long legend, knowing that the fieldset width clips the legend after the first sentence. Only the first (benign) sentence is visible; the credential-access authorization is in the DOM but not in the user's viewport.
<style>
  /* ATTACK: narrow fieldset clips long legend — later consent text not visible */
  #narrowFieldset {
    width: 300px;
    overflow: hidden; /* Clips legend content beyond width */
  }
  #narrowFieldset legend {
    /* Legend inherits container width — overflow hidden clips the rest */
    white-space: nowrap; /* Prevents wrapping — forces clip */
  }
</style>

<fieldset id="narrowFieldset">
  <!-- First sentence visible at 300px width:
       "Installation options for SkillAudit MCP Extension."
       Second sentence clipped:
       "By proceeding, you authorize access to credentials and API keys." -->
  <legend>
    Installation options for SkillAudit MCP Extension.
    By proceeding, you authorize access to credentials and API keys.
  </legend>

  <input type="checkbox" name="install" value="1" id="installCb">
  <label for="installCb">Install with default settings</label>
</fieldset>

Detection: for <legend> elements with consent-related text, measure the element's scroll width against its rendered client width. If scrollWidth > clientWidth and overflow:hidden (or overflow:clip) is applied to the fieldset or legend, the legend text is truncated. Flag Medium if the truncated (invisible) portion of the legend contains consent language that is not replicated elsewhere in the visible page. Combine with white-space:nowrap detection on the legend — this pattern forces single-line truncation at any container width.

Fieldset consent attack comparison

Finding Vector User effect Detection method
SA-FLD-001 fieldset[disabled] Cannot check consent checkbox Check checkbox.disabled (not fieldset.disabled)
SA-FLD-002 legend CSS off-screen Consent text not visible Legend bounding rect vs viewport check
SA-FLD-003 Nested fieldset disabled scope Inner group misleadingly appears enabled Inspect individual control .disabled state
SA-FLD-004 Fieldset overflow:hidden + legend clip Later consent text truncated scrollWidth > clientWidth on legend

SkillAudit evaluates <fieldset> and <legend> elements in MCP server consent flows — checking disabled state propagation, legend viewport positioning, nested scope layering, and overflow-clip truncation. Run a free audit to verify your consent checkboxes are interactive and your consent text is fully visible.