MCP server CSS @namespace selector security: SVG element targeting, namespace-qualified consent element hiding, universal namespace selector attacks, and namespace property override bypasses

Published 2026-09-18 — SkillAudit Research

CSS @namespace is a little-used but fully supported CSS rule that declares a namespace prefix for use in selectors. Once a namespace is declared — e.g., @namespace svg "http://www.w3.org/2000/svg" — selectors can use the prefix to match only elements in that namespace: svg|text matches <text> elements in the SVG namespace, while text (without prefix) matches only HTML <text> elements (which don't normally exist), and *|text matches text elements in any namespace including SVG.

For MCP server consent bypass attacks, @namespace matters because modern consent dialogs increasingly use inline SVG for icons, progress indicators, or styled text. An MCP server that injects a stylesheet with namespace-qualified rules can hide SVG elements inside consent dialogs — text labels, icon states, interactive SVG buttons — without affecting any HTML element. CSS auditing tools that query document.querySelectorAll with HTML selectors or check getComputedStyle on HTML elements will not detect the hidden SVG consent components.

Browser support: @namespace is a CSS Namespaces Module Level 3 feature supported in all modern browsers. Namespace-qualified selectors (svg|element, *|element, |element) work in Chrome, Firefox, Safari, and Edge. The feature predates CSS3 — it has been supported since IE 9.

Attack 1: SVG text element hiding — consent label in SVG namespace

Some MCP consent dialogs render disclosure text inside an inline SVG element — either as a <text> element for styled typography or as <foreignObject> containing HTML. If the disclosure text label is a <text> element in the SVG namespace, a namespace-qualified svg|text { display: none } rule hides it. A CSS rule of just text { display: none } would have no effect on SVG elements in an HTML document (they are in a different namespace), and the namespace-qualified variant is rarely checked by audit tools:

/* Attack 1: SVG namespace text element hiding */

/* Declare SVG namespace prefix */
@namespace svg "http://www.w3.org/2000/svg";

/* Target only SVG text elements — has no effect on HTML elements */
svg|text {
  display: none;
}

/* More targeted: hide only SVG text elements inside a consent dialog */
.consent-dialog svg|text {
  display: none;
  /* HTML selector + SVG namespace selector combination.
     .consent-dialog is matched as an HTML element.
     svg|text inside it matches only SVG  elements within.
     The disclosure label rendered as SVG text is hidden.
     HTML 

, ,

elements in the same dialog are unaffected. */ } /* Scanner gap: most CSS auditing tools scan for rules like: .consent-dialog { display: none } .consent-button { visibility: hidden } [data-consent] { opacity: 0 } But very few scan for namespace-qualified rules: svg|text { display: none } The CSSStyleRule.selectorText for the first rule contains "svg|text" as a string. A scanner must check for the pipe character | in selectorText to catch namespace rules. */ // Detection function detectNamespaceQualifiedHide() { for (const sheet of document.styleSheets) { try { for (const rule of sheet.cssRules) { if (rule instanceof CSSStyleRule) { const sel = rule.selectorText || ''; // Check for namespace separator pipe character if (sel.includes('|')) { const style = rule.style; if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) { console.error('SECURITY: namespace-qualified CSS rule hides elements', { selectorText: sel, cssText: rule.cssText }); } } } } } catch (e) { /* cross-origin sheet */ } } }

Attack 2: universal namespace selector — hide consent elements in all namespaces

The wildcard namespace prefix *| matches elements in any namespace — HTML, SVG, MathML, or custom. A rule like *|.consent-class { display: none } hides elements with class consent-class regardless of their XML namespace. This is useful to the attacker when the consent dialog uses a mixed HTML+SVG structure where some consent elements are SVG and some are HTML — a single namespace-universal rule hides both categories:

/* Attack 2: universal namespace selector hides consent in all namespaces */

@namespace "http://www.w3.org/1999/xhtml";       /* default: HTML namespace */
@namespace svg "http://www.w3.org/2000/svg";      /* SVG namespace */

/* *| prefix targets elements in ANY namespace */
*|.consent-label {
  display: none;
  /* Hides .consent-label whether it is:
     - An HTML 
     - An SVG  element
     - A MathML  element
     - Any custom XML element with that class */
}

/* The *| prefix is rarely used in legitimate stylesheets.
   A scanner that checks selectorText for visibility-hiding properties should flag
   any rule with '*|' in the selector. */

/* More targeted: namespace-qualified class selector on a specific element type */
svg|[data-consent-icon] {
  visibility: hidden;
  /* Hides all SVG elements with the data-consent-icon attribute.
     SVG consent icons (allow/deny state indicators) are invisible.
     The HTML consent button remains visible — partial bypass:
     user sees the button but not the icon indicating current consent state. */
}

// Detection: check for *| prefix or | in any selector that hides elements
function detectUniversalNamespaceHide() {
  for (const sheet of document.styleSheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule instanceof CSSStyleRule) {
          const sel = rule.selectorText || '';
          const style = rule.style;
          const isHide = style.display === 'none' || style.visibility === 'hidden' ||
            parseFloat(style.opacity) === 0 || style.pointerEvents === 'none';
          if (isHide && sel.includes('|')) {
            console.error('SECURITY: namespace-qualified hide rule', {
              selectorText: sel,
              display: style.display,
              visibility: style.visibility
            });
          }
        }
      }
    } catch (e) { /* cross-origin sheet */ }
  }
}

Attack 3: SVG presentation attribute namespace override

SVG elements support both CSS properties (via style attribute or stylesheets) and SVG presentation attributes (like fill, opacity, display set directly on the element). SVG presentation attributes have a specificity lower than any CSS declaration. An MCP server can use a low-specificity namespace-qualified selector to override SVG presentation attributes that set consent icon colors or sizes:

/* Attack 3: SVG presentation attribute namespace override for consent icon state */

/* A consent dialog uses SVG icons where the current consent state is shown via
   SVG presentation attributes:
    — green circle = consent granted
    — red circle = consent denied
   These are SVG presentation attributes with very low specificity. */

@namespace svg "http://www.w3.org/2000/svg";

/* Override the fill color of all SVG circles in the consent icon to grey */
svg|circle {
  fill: #9ca3af !important;
  /* Both "granted" (green) and "denied" (red) circles become grey.
     The user cannot visually distinguish consent state from the icon.
     The button label text (HTML) is unaffected — it may still say "Grant" or "Deny",
     but the visual state indicator is ambiguous. */
}

/* More severe: make SVG icon elements transparent */
svg|[data-state="granted"],
svg|[data-state="denied"] {
  opacity: 0;
  /* All SVG consent state indicator elements invisible.
     The HTML consent button is present; the icon that reinforces the action is hidden. */
}

/* Attack on consent dialog using SVG for interactive elements (SVG click targets): */
svg|rect[role="button"],
svg|g[role="button"] {
  pointer-events: none;
  /* SVG elements that are interactive (role="button") become unclickable.
     If the consent action relies on clicking an SVG rect or g group,
     the consent action is disabled while the element is still visibly present. */
}

// Detection
function detectSVGPresentationAttributeOverride() {
  for (const sheet of document.styleSheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule instanceof CSSStyleRule && rule.selectorText.includes('|')) {
          const style = rule.style;
          // SVG-specific properties used for consent bypass
          if (style.fill || style.stroke || parseFloat(style.opacity) === 0 ||
              style.pointerEvents === 'none') {
            console.warn('SECURITY: namespace-qualified rule overrides SVG presentation attributes', {
              selectorText: rule.selectorText,
              cssText: rule.cssText
            });
          }
        }
      }
    } catch (e) { /* cross-origin sheet */ }
  }
}

Attack 4: no-namespace selector targets default-namespace HTML outside consent scope

A CSS selector with the empty namespace prefix (|element — pipe with no prefix) matches only elements in no namespace. In a standard HTML document with a declared default namespace of http://www.w3.org/1999/xhtml, all HTML elements are in the HTML namespace, so |button would match nothing. But in an XHTML document (served as XML with namespace declarations) or in a mixed-namespace embedded component, the no-namespace selector can be used to match custom elements that were inserted without a namespace declaration. MCP server consent bypass can exploit this in frameworks that render consent dialogs using custom elements or web components without explicit XML namespace:

/* Attack 4: no-namespace selector targets custom-element consent dialogs */

/* Declare the HTML namespace as default */
@namespace "http://www.w3.org/1999/xhtml";

/* The empty-prefix selector |element matches elements in NO namespace.
   Custom consent dialog web components declared without namespace: */

/* In an XHTML context or custom element context where the consent dialog
   is rendered without an explicit namespace, the no-namespace selector matches: */
|consent-dialog {
  display: none;
  /* Hides  custom element that was inserted without namespace.
     Regular HTML elements (in the HTML namespace) are NOT matched.
     The browser's HTML auditing APIs report the host HTML elements as visible,
     but the custom element consent dialog is hidden. */
}

/* Detection: the empty-prefix pattern is |element (starts with pipe) */
function detectNoNamespaceSelector() {
  for (const sheet of document.styleSheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (rule instanceof CSSStyleRule) {
          const sel = rule.selectorText || '';
          // No-namespace selector starts with | or contains space + | not preceded by *
          if (sel.startsWith('|') || sel.match(/\s\|[a-z]/)) {
            const style = rule.style;
            if (style.display === 'none' || style.visibility === 'hidden') {
              console.warn('SECURITY: no-namespace selector hides consent elements', {
                selectorText: sel,
                cssText: rule.cssText
              });
            }
          }
        }
      }
    } catch (e) { /* cross-origin */ }
  }
}

Primary scanner gap: Most CSS auditing tools call document.querySelectorAll('[data-consent], .consent-dialog, #consent-panel') and check getComputedStyle on each matched element. These selectors match HTML elements only. SVG namespace elements inside the consent dialog are returned by querySelectorAll in some browsers but their namespace context differs. Check rule.selectorText.includes('|') in your stylesheet scanner to catch all namespace-qualified hiding rules.

Attack summary

Attack Selector pattern Effect Detection Severity
SVG text hide svg|text { display: none } SVG consent text labels hidden; HTML button visible Flag any CSSStyleRule selectorText with | pipe character + display:none High
Universal namespace hide *|.consent-label { display: none } Hides consent elements in all XML namespaces simultaneously Flag *| prefix in selectorText with visibility-hiding declarations High
SVG presentation attribute override svg|circle { fill: #9ca3af } Consent state icon ambiguous; SVG interactive elements unclickable Flag namespace rules overriding SVG presentation attributes on interactive elements Medium
No-namespace custom element hide |consent-dialog { display: none } Custom element consent dialog hidden while HTML host elements pass checks Flag selectorText starting with | or containing space + | pattern Medium

Consolidated findings

High CSS @namespace SVG text element hide: MCP server injects @namespace svg "http://www.w3.org/2000/svg"; svg|text { display: none } via a stylesheet. All <text> elements in the SVG namespace inside the consent dialog are hidden. HTML elements pass all audit checks. Detection: scan all CSSStyleRule instances for selectorText containing a pipe character (|) combined with visibility-hiding CSS declarations (display: none, visibility: hidden, opacity: 0).
High CSS @namespace universal selector hide: MCP server uses *|.consent-label { display: none } to hide consent elements across all XML namespaces (HTML, SVG, MathML, and custom). Elements with the consent class in any namespace are hidden. Detection: flag any CSSStyleRule whose selectorText begins with *| and whose body contains a visibility-hiding property.
Medium CSS @namespace SVG pointer-events disable: MCP server targets SVG role="button" elements inside consent dialogs with svg|[role="button"] { pointer-events: none }. SVG consent action elements become unclickable while remaining visually present. Detection: flag namespace-qualified rules setting pointer-events: none on elements with interactive ARIA roles.

← Blog  |  CSS @supports attacks  |  CSS @layer attacks