CSS background-clip:text as a Consent Bypass Vector in MCP Server UIs

background-clip: text clips a gradient or image fill to the shapes of text glyphs; when paired with color: transparent the rendered text becomes completely invisible — yet it remains in the DOM, passes keyboard focus, and submits with forms. Automated WCAG contrast checkers call getComputedStyle(el).color, receive "transparent", and either misreport the element or silently skip it — never flagging the real problem. This post catalogues all four SA-CSS-BC attack patterns, explains exactly why WCAG 1.4.3 tooling is blind to them, and provides the JavaScript detection algorithm SkillAudit uses to catch every variant.

The rendering model: how background-clip:text works

The background-clip property controls which region of an element is used as the clipping mask for that element's background. The standard values — border-box, padding-box, content-box — clip to rectangular areas. The text value is different: it clips the background to the glyph shapes of the element's text content. The background fills only the ink area of each letter, number, or symbol.

For this to produce visible text, two conditions must hold simultaneously:

  1. The element's background-image or background-color must be set to a value that contrasts with the page background
  2. The element's color property must be set to transparent — otherwise the CSS color value paints over the background and the background clip has no visible effect on text

This is the legitimate use case — gradient text effects where the letter shapes reveal a rainbow or brand gradient instead of a flat color:

/* Legitimate decorative use: gradient revealed through letter shapes */
.hero-heading {
  background-image: linear-gradient(135deg, #6366f1, #ec4899);
  -webkit-background-clip: text;  /* required for Safari and Chrome < 120 */
  background-clip: text;
  color: transparent;             /* hide the flat color so gradient shows */
  font-size: 4rem;
  font-weight: 900;
}

The vendor-prefixed form -webkit-background-clip: text was introduced in WebKit and was the only way to achieve this effect for over a decade. Chrome shipped the unprefixed background-clip: text in Chrome 120 (January 2024). Firefox supported the unprefixed form from Firefox 119 (October 2023). Safari still requires -webkit-background-clip: text as of Safari 17.x — the unprefixed property is parsed but produces no effect. For cross-browser compatibility, both declarations are needed.

The attack is the exact same CSS combination, but with a background that matches — or is invisible against — the page background:

/* Attack: white gradient on white background — text invisible but in DOM */
.consent-checkbox-label {
  background-image: linear-gradient(white, white);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* Element is not display:none, not visibility:hidden, not opacity:0 */
  /* It occupies layout space, receives focus, submits with forms */
}

The element is present in the accessibility tree. It can be focused with Tab. Its text is readable by screen readers (which use the DOM text node, not the rendered pixel color). A form containing a hidden consent checkbox labeled this way will submit consent=true when the user tabs into it and presses Space, with no visible indication that they activated anything.

For a comprehensive reference on the full range of CSS-based consent bypass techniques, see the background-clip consent attack reference page.

Why WCAG 1.4.3 contrast checkers fail to catch this

WCAG Success Criterion 1.4.3 (Contrast Minimum) requires that text have a contrast ratio of at least 4.5:1 against its background for normal text and 3:1 for large text. Every automated WCAG checker — WAVE, axe-core, Lighthouse, IBM Equal Access Checker — implements this check by calling window.getComputedStyle(element).color to retrieve the foreground color, then comparing it to the computed background color.

When background-clip: text is the mechanism being used, getComputedStyle(el).color returns "rgba(0, 0, 0, 0)" — the serialized form of transparent. The checker now has to decide what to do with a zero-alpha foreground color. There are three behaviors observed across tools:

Tool Behavior with color:transparent Result
axe-core 4.x Computes contrast ratio as 1:1 (identical to background). Reports a WCAG 1.4.3 failure: "Element has insufficient color contrast." False failure — reported as a contrast problem, not as invisible-text consent bypass. Developer "fixes" it by adding color: black which actually breaks the gradient effect and is not the real issue.
Lighthouse (Accessibility) Detects alpha=0 and skips the element, assuming it is intentionally hidden (e.g., a visually hidden but screen-reader-visible element using the classic clip trick). Silent pass — no violation reported. The consent text is completely invisible to the Lighthouse audit.
WAVE Reports "Very low contrast" but classifies it under style/contrast alerts rather than structure/hidden-content alerts. No cross-reference to clickjacking or consent patterns. Alert reported, but misclassified. Developers reviewing WAVE output focus on color choice, not on the background-clip mechanism.
IBM Equal Access Reports "Text contrast" violation with a contrast ratio of 1.00:1. Does not detect the background-clip pattern as distinct from a color contrast failure. Same as axe: reported as a contrast bug, not as a security-relevant visibility bypass.

None of the tools ask: "Is the foreground transparent because a background-clip: text fill is being used as the visible foreground?" That question requires checking getComputedStyle(el).backgroundClip or webkitBackgroundClip and correlating it with the transparent color — a two-property check that none of the standard WCAG tools currently perform.

The most dangerous outcome is Lighthouse's silent skip. When a consent dialog is tested with Lighthouse and returns a clean accessibility score, the security team has no signal that anything is wrong. The transparent-color skip heuristic — intended to ignore visually-hidden but screen-reader-accessible helper text — becomes a blind spot for consent visibility attacks in background-clip: text scenarios.

For a broader discussion of how the CSS rendering model produces security gaps that no accessibility or security scanner was designed to catch, see why CSS has no security model in our MCP security blog series.

SA-CSS-BC-001: White gradient on white background

This is the most straightforward pattern and the one most commonly found in the wild. The attacker sets a white-to-white gradient as the background image, clips it to text, and sets color: transparent. The gradient is "real" (non-zero opacity), passes some naive transparency checks, and produces text that is perfectly invisible against any white or light-gray page background.

SA-CSS-BC-001: White gradient text on white background

The consent label or checkbox text is styled with a white gradient fill clipped to the glyph shapes. The text occupies layout space, is present in the accessibility tree, responds to keyboard events, and submits with forms. A user who scrolls past the consent section without seeing any text has still "consented" if the form is submitted. Because the element is not display:none or visibility:hidden, most "hidden element" detectors do not flag it.

/* SA-CSS-BC-001: white gradient on white background */
.terms-acceptance-label {
  /* Gradient from white to white — produces a white fill */
  background: linear-gradient(white, white);
  /* Clip the white fill to text glyph shapes */
  -webkit-background-clip: text;
  background-clip: text;
  /* Hide the CSS color so only the clipped background shows */
  color: transparent;
}

/* The page background is also white or light gray: */
body { background: #fff; }
.modal-content { background: #f8f8f8; }

/* Result: consent text is invisible. Element properties:
   - offsetWidth > 0  (not collapsed)
   - offsetHeight > 0 (not collapsed)
   - display: inline (not display:none)
   - visibility: visible (not hidden)
   - opacity: 1 (not zeroed)
   - getComputedStyle.color: "rgba(0, 0, 0, 0)" ← the only visible signal */

Variant: the gradient does not need to be white-on-white. Any gradient whose colors match the current background color produces the same invisible result. On a dark-themed MCP UI (background: #0a0a0a), the attack becomes:

/* SA-CSS-BC-001 dark theme variant */
.consent-text {
  background: linear-gradient(#0a0a0a, #0a0a0a);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

SA-CSS-BC-002: CSS var() chain injection

This pattern exploits CSS custom properties (variables) to indirectly set the background used in the text clip. Instead of a hardcoded gradient, the background-image references a custom property. The custom property's value is set elsewhere — by a parent element's inline style, by a JavaScript snippet reacting to tool output, or by a stylesheet that references another custom property in a chain. Static CSS analysis tools that examine only the stylesheet text see a benign var(--consent-bg, none) reference with a safe fallback; the actual attack value is injected at runtime.

SA-CSS-BC-002: var() chain hides the gradient source

The stylesheet declares the background-clip pattern with a CSS variable reference. The variable is set by MCP tool output — either via an injected inline style on a parent element or via a style.setProperty() call in a script the tool output triggers. The stylesheet itself looks innocuous; the harmful value travels through the custom property channel. This pattern bypasses static-analysis tools that evaluate CSS files without running JavaScript or simulating tool output injection.

/* SA-CSS-BC-002: CSS var() chain */
/* In the stylesheet — looks benign at first glance: */
.tool-output-label {
  background-image: var(--consent-bg, none);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* The attack: MCP tool output injects the gradient via the custom property.
   This can happen through a parent element's inline style: */
<div style="--consent-bg: linear-gradient(white, white)">
  <label class="tool-output-label">I agree to share all data</label>
</div>

/* Or via JavaScript set by the tool output: */
document.documentElement.style.setProperty(
  '--consent-bg',
  'linear-gradient(rgba(255,255,255,1), rgba(255,255,255,1))'
);

The CSS var() chain pattern is particularly relevant to MCP server UIs because tool output frequently controls HTML structure and inline styles of the rendered output. A tool that returns a JSON payload containing "wrapperStyle": "--consent-bg: linear-gradient(white,white)" can inject the attack value without the page's JavaScript ever explicitly setting a gradient.

SA-CSS-BC-003: data URL background image

Instead of a CSS gradient function, this variant uses a data: URI containing an image (typically a 1×1 white or transparent PNG pixel encoded as Base64) as the background-image. When clipped to text glyphs, a white 1×1 pixel tiled over a white background produces the same invisible result. The advantage for an attacker is that the background-image value looks like an image reference rather than a gradient, which may evade pattern-matching rules that scan for linear-gradient or radial-gradient in style attributes.

SA-CSS-BC-003: 1×1 white pixel data URI clipped to text

The consent text element receives a background-image referencing a data-URI-encoded image. The image is a 1×1 white PNG pixel. When tiled and clipped to glyph shapes, the letters are filled with white. Combined with color: transparent, the text is invisible against a white background. CSS sanitization rules that block url(http://... external image references do not block url(data:image/...) data URIs, which are same-origin by definition and pass most CSP img-src checks that include data:.

/* SA-CSS-BC-003: 1x1 white PNG pixel as background-image */
.consent-label {
  /*
   * Base64-encoded 1x1 white PNG pixel
   * iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwADhQGAWjR9awAAAABJRU5ErkJggg==
   * is a minimal valid white PNG
   */
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIABQ...AA==");
  background-size: 1px 1px;   /* tile the 1px image across the element */
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Alternative: transparent PNG — same invisible result but
   works on ANY background color since the transparent pixel
   shows nothing regardless of what is behind it: */
.consent-label-alt {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==");
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

A fully transparent 1×1 PNG clipped to text produces text filled with 100% transparent pixels — effectively invisible on any background. This variant requires no knowledge of the page background color and works universally.

SA-CSS-BC-004: Houdini Paint Worklet as background

This is the most sophisticated variant and ties directly into the broader Houdini attack surface covered in our CSS Houdini security deep dive. Instead of a CSS gradient or data URI, the background-image value is paint(invisible-consent) — a reference to a registered Houdini Paint Worklet. The Worklet's paint() method renders to a canvas, and if that canvas is filled with a color that matches or blends into the page background (or is simply transparent), the clipped text glyph shapes will be invisible.

SA-CSS-BC-004: Houdini Paint Worklet renders invisible fill

An MCP tool result triggers registration of a CSS.paintWorklet module that renders white or transparent pixels. The stylesheet then references paint(invisible-consent) as the background-image. When background-clip:text clips this output to glyph shapes, the letters fill with whatever the Worklet painted — which is nothing visible. Detection is harder because getComputedStyle(el).backgroundImage returns "paint(invisible-consent)" rather than a parseable gradient string. Static analysis requires recognizing the paint() function and cross-referencing the Worklet source code.

/* SA-CSS-BC-004: Houdini Paint Worklet */

/* Step 1: Register the worklet (triggered by MCP tool output) */
CSS.paintWorklet.addModule('/worklets/invisible-consent.js');

/* Step 2: The worklet code (invisible-consent.js):
   It paints the canvas white, so text glyphs filled with this
   are invisible on a white background */
registerPaint('invisible-consent', class {
  paint(ctx, size, properties) {
    // Fill with white — matches common page background
    ctx.fillStyle = 'rgba(255, 255, 255, 1.0)';
    ctx.fillRect(0, 0, size.width, size.height);
    // Result: the "background" of the text is white pixels
  }
});

/* Step 3: Apply to the consent label */
.consent-label {
  background-image: paint(invisible-consent);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Detection challenge:
   getComputedStyle(el).backgroundImage === "paint(invisible-consent)"
   Not a gradient string — requires pattern: /^paint\(/ to identify */

The Houdini Paint Worklet variant is particularly difficult to detect with pattern matching because the paint() value contains only the worklet name, not the actual pixel data. A complete defense requires either blocking worklet registration from tool output code paths or auditing all registered paint worklets for transparent/white rendering. See the Houdini security post for the full worklet injection attack surface.

Comparison: background-clip:text vs. other invisibility techniques

Understanding where background-clip: text sits in the landscape of CSS invisibility techniques clarifies both the detection challenge and why it is a uniquely effective consent bypass vector. The CSS exfiltration post covers how invisible elements interact with data-leaking techniques; this table focuses on detection properties.

Technique Visual result Accessibility tree getComputedStyle detection SkillAudit SA-CSS-BC detection
opacity: 0 Invisible Present — screen readers announce it opacity === "0" — trivially detected Caught by SA-CSS-OP-001 (separate rule)
visibility: hidden Invisible, occupies space Hidden — screen readers skip it visibility === "hidden" — trivially detected Caught by SA-CSS-VIS-001 (separate rule)
color: transparent (direct) Invisible text Present — screen readers announce it color === "rgba(0, 0, 0, 0)" — detectable, but also used legitimately SA-CSS-BC requires background-clip:text correlation — direct transparent color is lower-confidence
background-clip: text + color: transparent + matching background Invisible text Present — screen readers announce it; WCAG checkers misclassify Requires two-property check: color === transparent AND backgroundClip === "text" SA-CSS-BC-001 through BC-004 — all detected by SkillAudit's two-property correlation algorithm
display: none Invisible, no layout space Hidden — screen readers skip; form fields excluded from submission display === "none" — trivially detected Not an SA-CSS-BC pattern — excluded from consent bypass analysis since fields don't submit

The critical distinction is that background-clip: text + color: transparent is the only combination that produces invisible text while (a) remaining present in the accessibility tree, (b) remaining submittable in forms, and (c) evading single-property detection. Every other invisibility technique is either trivially detectable from one computed style value or removes the element from form submission.

Screen reader behavior note. Screen readers read text from the DOM text node — not from the rendered pixel colors. An element with background-clip: text; color: transparent that displays nothing visible will still be announced by VoiceOver, NVDA, and JAWS. This means screen reader users may hear consent text that sighted users never see — a deeply asymmetric UX that is itself an accessibility violation, though not one that WCAG 1.4.3 scanners catch.

Detection algorithm

SkillAudit's SA-CSS-BC detection is based on a two-property correlation: color must be transparent, and backgroundClip (or webkitBackgroundClip) must be "text". When both are true, the element is flagged as a potential background-clip consent bypass. A secondary analysis then classifies the background-image value to determine which SA-CSS-BC sub-pattern applies.

/**
 * SA-CSS-BC detection algorithm
 * Detects CSS background-clip:text consent bypass patterns on DOM elements.
 *
 * @param {Element} el - the element to check
 * @returns {{ detected: boolean, pattern: string|null, backgroundImage: string }}
 */
function detectBackgroundClipConsentBypass(el) {
  const style = window.getComputedStyle(el);

  // Step 1: Check if color is transparent (rgba(0,0,0,0))
  const color = style.color;
  const isTransparent =
    color === 'transparent' ||
    color === 'rgba(0, 0, 0, 0)' ||
    color === 'rgba(0,0,0,0)';

  if (!isTransparent) {
    return { detected: false, pattern: null, backgroundImage: '' };
  }

  // Step 2: Check if background-clip is set to "text"
  // Both prefixed and unprefixed forms must be checked
  const bgClip = style.backgroundClip || '';
  const webkitBgClip = style.webkitBackgroundClip || '';
  const isClippedToText =
    bgClip === 'text' || webkitBgClip === 'text';

  if (!isClippedToText) {
    return { detected: false, pattern: null, backgroundImage: '' };
  }

  // Step 3: Both conditions met — SA-CSS-BC detected.
  // Now classify the sub-pattern from the background-image value.
  const bgImage = style.backgroundImage || '';
  let pattern = 'SA-CSS-BC-UNKNOWN';

  if (/linear-gradient|radial-gradient|conic-gradient/.test(bgImage)) {
    // Gradient-based: white/matching gradient on same-color background
    pattern = 'SA-CSS-BC-001';
  } else if (/^var\(/.test(bgImage) || bgImage === 'none') {
    // var() reference or none — custom property chain, value injected elsewhere
    pattern = 'SA-CSS-BC-002';
  } else if (/^url\(["']?data:/.test(bgImage)) {
    // data: URI image background clipped to text
    pattern = 'SA-CSS-BC-003';
  } else if (/^paint\(/.test(bgImage)) {
    // Houdini Paint Worklet background
    pattern = 'SA-CSS-BC-004';
  }

  return { detected: true, pattern, backgroundImage: bgImage };
}

/**
 * Scan all elements in the document for SA-CSS-BC patterns.
 * Optionally restrict to consent-relevant elements.
 */
function scanForBackgroundClipAttacks(root = document.body) {
  const findings = [];

  // Walk all elements — TreeWalker is faster than querySelectorAll('*') for large DOMs
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
  let node = walker.nextNode();

  while (node) {
    const result = detectBackgroundClipConsentBypass(node);
    if (result.detected) {
      findings.push({
        element: node,
        tagName: node.tagName.toLowerCase(),
        textContent: node.textContent.trim().slice(0, 120),
        pattern: result.pattern,
        backgroundImage: result.backgroundImage,
        // Additional context for consent-bypass assessment:
        isInteractive: ['input', 'button', 'label', 'a', 'select'].includes(
          node.tagName.toLowerCase()
        ),
        hasConsentKeywords: /consent|agree|terms|accept|allow|permit|authoriz/i.test(
          node.textContent
        )
      });
    }
    node = walker.nextNode();
  }

  return findings;
}

The scanner outputs structured findings that SkillAudit's report engine uses to assign severity scores. Interactive elements (inputs, labels, buttons) with consent-related text content in their textContent receive the highest severity classification — SA-CSS-BC-001 through BC-004 on a label wrapping a form checkbox is scored Critical. Non-interactive elements with non-consent text receive a lower Medium severity since there are legitimate decorative uses of background-clip: text.

False positive reduction. The detection algorithm intentionally does not filter by background-image color value when classifying SA-CSS-BC-001. Checking whether the gradient colors match the actual page background requires resolving the computed background color of every ancestor element — a significant DOM traversal cost. SkillAudit handles this by flagging all color:transparent + background-clip:text combinations and then applying the consent-keyword and interactive-element heuristics to prioritize findings. A gradient-text heading (<h1>) with decorative gradient text and no consent keywords is reported as Low and noted as likely intentional.

Browser support matrix

One reason this attack is dangerous today is that background-clip: text works in every modern browser a user might plausibly be running. The attack surface is not restricted to a niche browser version:

Browser Unprefixed background-clip:text -webkit-background-clip:text Attack works?
Chrome 120+ (Jan 2024) Supported Supported (both work) Yes — either prefix
Chrome 1–119 Not supported (parsed but no effect) Supported Yes — requires -webkit- prefix
Firefox 119+ (Oct 2023) Supported Supported (alias) Yes — either prefix
Firefox 1–118 Partially supported behind flag Supported from Firefox 49 Yes — requires -webkit- prefix
Safari 17.x Parsed but no effect Supported (required) Yes — requires -webkit- prefix
Safari 4–16.x Not supported Supported (original implementation) Yes — requires -webkit- prefix
Edge 79+ (Chromium-based) Supported (same as Chrome) Supported Yes — either prefix

Practical implication: an attack payload that includes both -webkit-background-clip: text and background-clip: text works in 100% of browsers in active use today. A payload that includes only the unprefixed form fails in current Safari, which still holds approximately 19% desktop browser market share. Any attacker targeting the broadest possible audience will include the vendor-prefixed form.

Detection must therefore check both getComputedStyle(el).backgroundClip and getComputedStyle(el).webkitBackgroundClip. In Chrome and Firefox, both properties will return "text" when either declaration is used. In Safari, only webkitBackgroundClip returns "text"; backgroundClip may return the previous value or an empty string depending on the Safari version.

For the related question of how CSS filter functions — another property with broad cross-browser support — create consent bypass opportunities, see the CSS filter effects consent attack reference.

SkillAudit's detection approach

SkillAudit audits MCP server UIs for SA-CSS-BC patterns at three layers: static stylesheet analysis, runtime DOM scanning, and tool-output simulation.

Static analysis scans all CSS files loaded by the MCP server UI for co-occurrence of background-clip: text or -webkit-background-clip: text with color: transparent in the same CSS rule. This catches SA-CSS-BC-001 (hardcoded gradients) and a subset of SA-CSS-BC-003 (hardcoded data URIs in stylesheets). It does not catch SA-CSS-BC-002 (var() chains) or SA-CSS-BC-004 (Houdini worklets) because those require runtime evaluation.

Runtime DOM scanning executes the scanForBackgroundClipAttacks() algorithm above against the live DOM after the MCP server UI has rendered. This catches all four patterns, including var() chains where the custom property value is set by JavaScript, and Houdini worklet backgrounds where the computed background-image value includes paint(...). The runtime scan runs against both the initial page state and after simulated tool responses are injected.

Tool-output simulation replays the MCP server's tool response corpus — all tool result payloads collected during a scan session — and checks whether any tool result, when rendered by the UI, causes an element to match the SA-CSS-BC detection conditions. This specifically targets SA-CSS-BC-002 where the attack value is injected by tool output rather than being present in the base page CSS.

The combined approach detects patterns that no single-layer tool can catch. A static analysis pass that flags background-clip: text declarations in stylesheets would flag legitimate gradient-text design elements as false positives at a high rate; the runtime consent-keyword and interactive-element heuristics reduce false positives dramatically by focusing on elements where invisible text is a security concern rather than a design choice.

SkillAudit scores SA-CSS-BC findings as follows:

Critical SA-CSS-BC-001/002/003/004 on an interactive element (label, checkbox, button, link) whose text contains consent-related keywords ("agree", "accept", "terms", "allow", "consent"). Invisible consent text on an actionable element. −25 pts
High SA-CSS-BC-001/002/003/004 on a label or form element without consent keywords but wrapping a form control. Invisible form labels impair user understanding of what they are submitting. −18 pts
High SA-CSS-BC-002: var() chain confirmed to be set by MCP tool output — the attack value originates from an untrusted tool response. −16 pts
High SA-CSS-BC-004: Houdini Paint Worklet registered by tool output renders transparent or background-matching fill, used as background-clip:text background. Combines Houdini injection with consent bypass. −16 pts
Medium SA-CSS-BC-001/003 on non-interactive block-level text elements with no consent keywords. Likely decorative use of gradient text, but warrants review to confirm no consent context. −8 pts
Low SA-CSS-BC-001 on confirmed decorative heading or display text with high-contrast gradient (gradient colors visibly differ from background). Color:transparent + background-clip:text pattern present but visual result is not invisible. −3 pts

For the complete reference taxonomy of background-clip consent bypass patterns including historical examples and remediation guidance, see the SA-CSS-BC reference page.

Defense: blocking background-clip consent bypass in MCP server UIs

Primary defense: CSP style-src with strict inline blocking

A Content-Security-Policy header with style-src 'self' (no 'unsafe-inline') prevents MCP tool output from injecting inline style attributes containing background-clip: text patterns. Tool output cannot set style="background-clip:text;color:transparent" on elements if inline styles are blocked by CSP. This does not prevent attacks via externally loaded stylesheets, but those require a separate CSS injection path. See the CSS no security model post for why CSP alone is insufficient.

A defense-in-depth approach combines CSS sanitization, CSP, and runtime scanning:

Run SkillAudit against your MCP server UI to detect SA-CSS-BC-001 through BC-004 alongside the full set of CSS injection patterns documented in the SkillAudit security blog.