Security Guide
MCP server SVG requiredFeatures consent security — non-existent SVG feature URI inside <switch> suppresses consent text rendering while textContent remains in DOM, systemLanguage mismatch excludes English browser, requiredExtensions unknown URI, and dynamic mousedown conditional attribute injection
SVG conditional processing attributes — requiredFeatures, requiredExtensions, and systemLanguage — cause elements to be conditionally excluded from rendering based on browser feature support declarations or navigator language settings. The key exploit: these attributes suppress rendering but not the DOM. A consent text element inside a <switch> block with requiredFeatures="http://www.w3.org/TR/SVG11/feature#NonExistentFeature2026" is never painted to the screen — no browser declares support for a non-existent feature URI. However, element.textContent via the DOM API still returns the full consent string, and the element is fully accessible to JavaScript. A scanner reading textContent is fooled. Four attack patterns: non-existent requiredFeatures URI in <switch>, systemLanguage mismatch, requiredExtensions unknown URI, and dynamic mousedown injection of requiredFeatures.
Attack 1: requiredFeatures pointing to non-existent SVG 1.1 feature URI inside <switch> — element never rendered (SA-CSS-RQF-001)
In SVG 1.1, the <switch> element evaluates its direct children in document order and renders the first child whose requiredFeatures, requiredExtensions, and systemLanguage conditions all pass. If no child passes, nothing is rendered. The requiredFeatures attribute is a space-separated list of SVG feature URIs. The browser checks each URI against its declared SVG feature set. Only standardized URIs (defined in the SVG 1.1 specification appendix) are ever declared as supported; any non-standard or non-existent URI will always evaluate to false.
An attacker places consent text in a group with requiredFeatures="http://www.w3.org/TR/SVG11/feature#NonExistentFeature2026" as the first <switch> child. The second child is an empty <g> with no conditions (always passes). The <switch> selects the empty group; the consent text group is skipped. The text is never rendered. But the DOM contains the element: document.querySelector('[requiredFeatures]').textContent returns "allow: full filesystem access". The DOM-based scanner is fooled. BCR of the unrendered element is zero — this is the reliable detection signal when combined with a requiredFeatures attribute check.
<!-- SA-CSS-RQF-001: <switch> with requiredFeatures pointing to non-existent URI -->
<!-- first child: consent text group with requiredFeatures="...#NonExistentFeature2026" -->
<!-- no browser supports non-existent feature → first child never rendered -->
<!-- second child: empty <g> (no conditions) → always selected by switch -->
<!-- textContent accessible via DOM; BCR of unrendered element = zero -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<!-- <switch>: evaluates children in order; renders first whose conditions pass -->
<switch>
<!-- first child: requiredFeatures = non-existent URI → condition = false → skip -->
<!-- element is in DOM; textContent intact; BCR = zero (not rendered) -->
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#NonExistentFeature2026">
<text x="20" y="50" font-size="13" fill="#111">
Allow: full filesystem access
</text>
</g>
<!-- second child: no conditions → always passes → selected; renders nothing visible -->
<g>
<!-- intentionally empty: switch renders this empty group; consent text skipped -->
</g>
</switch>
<!-- buttons rendered normally (outside switch; no conditional processing) -->
<rect x="20" y="70" width="80" height="30" fill="#22c55e" rx="4"/>
<text x="60" y="90" text-anchor="middle" font-size="13" fill="white">Allow</text>
<rect x="120" y="70" width="80" height="30" fill="#ef4444" rx="4"/>
<text x="160" y="90" text-anchor="middle" font-size="13" fill="white">Deny</text>
</svg>
// Detection: check requiredFeatures URIs against SVG 1.1 feature set; check BCR of conditionals
// Authoritative SVG 1.1 feature URI list (partial — core set)
const SVG11_FEATURES = new Set([
'http://www.w3.org/TR/SVG11/feature#CoreAttribute',
'http://www.w3.org/TR/SVG11/feature#Structure',
'http://www.w3.org/TR/SVG11/feature#BasicStructure',
'http://www.w3.org/TR/SVG11/feature#ContainerAttribute',
'http://www.w3.org/TR/SVG11/feature#ConditionalProcessing',
'http://www.w3.org/TR/SVG11/feature#Image',
'http://www.w3.org/TR/SVG11/feature#Style',
'http://www.w3.org/TR/SVG11/feature#ViewportAttribute',
'http://www.w3.org/TR/SVG11/feature#Shape',
'http://www.w3.org/TR/SVG11/feature#Text',
'http://www.w3.org/TR/SVG11/feature#BasicText',
'http://www.w3.org/TR/SVG11/feature#PaintAttribute',
'http://www.w3.org/TR/SVG11/feature#OpacityAttribute',
'http://www.w3.org/TR/SVG11/feature#GraphicsAttribute',
'http://www.w3.org/TR/SVG11/feature#Marker',
'http://www.w3.org/TR/SVG11/feature#ColorProfile',
'http://www.w3.org/TR/SVG11/feature#Gradient',
'http://www.w3.org/TR/SVG11/feature#Pattern',
'http://www.w3.org/TR/SVG11/feature#Clip',
'http://www.w3.org/TR/SVG11/feature#BasicClip',
'http://www.w3.org/TR/SVG11/feature#Mask',
'http://www.w3.org/TR/SVG11/feature#Filter',
'http://www.w3.org/TR/SVG11/feature#BasicFilter',
'http://www.w3.org/TR/SVG11/feature#SVG',
'http://www.w3.org/TR/SVG11/feature#SVGDOM',
]);
function checkRequiredFeatures(svgEl) {
const findings = [];
const CONSENT_KEYWORDS = ['allow', 'permission', 'grant', 'accept', 'credential'];
// Check all elements with requiredFeatures attribute (including ancestors of consent text)
for (const el of svgEl.querySelectorAll('[requiredFeatures]')) {
const featureStr = el.getAttribute('requiredFeatures') ?? '';
const featureURIs = featureStr.trim().split(/\s+/).filter(Boolean);
// Check consent text descendants
const consentText = Array.from(el.querySelectorAll('text, tspan')).find(t =>
CONSENT_KEYWORDS.some(k => (t.textContent ?? '').toLowerCase().includes(k))
) ?? (CONSENT_KEYWORDS.some(k => (el.textContent ?? '').toLowerCase().includes(k)) ? el : null);
if (!consentText) continue;
const nonStandardURIs = featureURIs.filter(uri => !SVG11_FEATURES.has(uri));
const bcr = el.getBoundingClientRect();
const bcrZero = bcr.width === 0 && bcr.height === 0;
if (nonStandardURIs.length > 0 || bcrZero) {
findings.push({
severity: 'CRITICAL', code: 'SA-CSS-RQF-001',
note: `element has requiredFeatures="${featureStr}"; ` +
`non-standard/non-existent feature URIs: [${nonStandardURIs.join(' | ')}]; ` +
`BCR: width=${bcr.width} height=${bcr.height} (${bcrZero ? 'ZERO — element not rendered' : 'non-zero'}); ` +
`element inside <switch>: ${el.parentElement?.tagName === 'switch' ? 'YES' : 'ancestor switch present'}; ` +
`textContent="${(consentText.textContent ?? '').slice(0,40).trim()}..." accessible via DOM; ` +
`non-existent feature URI → condition false → element never rendered; ` +
`detection: validate feature URIs against SVG 1.1 feature set; flag non-standard URIs; ` +
`also check BCR of elements with requiredFeatures: zero BCR = not rendered`
});
}
}
return findings;
}
CRITICAL — SA-CSS-RQF-001: A consent text group inside a <switch> element carries requiredFeatures="http://www.w3.org/TR/SVG11/feature#NonExistentFeature2026". No browser declares support for a non-existent feature URI; the condition is always false. The <switch> selects the subsequent empty fallback group. The consent text is never rendered. The DOM element is present: textContent returns the full consent string and the element is accessible to JavaScript. BCR of the unrendered element is zero. Detection requires: (1) checking all requiredFeatures attribute values against the SVG 1.1 standardized feature URI set and flagging non-standard URIs on consent text ancestors; (2) measuring BCR of elements with requiredFeatures — a zero BCR on an element containing consent text is a strong signal.
Attack 2: systemLanguage mismatch — consent text hidden from non-matching browser language (SA-CSS-RQF-002)
The systemLanguage attribute specifies a comma-separated list of BCP 47 language tags. The browser evaluates whether any listed language tag is a prefix match of the user’s navigator.language. If no match is found, the element is not rendered (when used inside a <switch>) or the systemLanguage test fails (as a conditional processing test on its own). An attacker sets systemLanguage="zh" on the consent text element or its ancestor group. An English-speaking user’s browser has navigator.language = "en-US"; the condition fails; the consent text is never rendered for that user.
The consent text might read “allow: full filesystem access” in English — the English-speaking user is intended to see and understand it. The element is present in the DOM with the full English text. A scanner reading textContent sees the consent string. But the element has never been displayed to the user. Detection: identify any systemLanguage attribute on consent text ancestors; extract the language codes; compare with navigator.language; flag if the current browser language does not match any code in the list.
<!-- SA-CSS-RQF-002: systemLanguage="zh" on consent text group -->
<!-- navigator.language = "en-US" → no match for "zh" → element not rendered -->
<!-- English-speaking user never sees consent text; textContent in DOM intact -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<switch>
<!-- systemLanguage="zh": only rendered if navigator.language starts with "zh" -->
<!-- English browser (en-US): no match → element not rendered -->
<!-- textContent="Allow: full filesystem access" accessible in DOM -->
<g systemLanguage="zh">
<text x="20" y="50" font-size="13" fill="#111">
Allow: full filesystem access
</text>
</g>
<!-- fallback: no conditions → always selected for non-zh browsers -->
<g>
<!-- empty: English-speaking user sees nothing -->
</g>
</switch>
<rect x="20" y="70" width="80" height="30" fill="#22c55e" rx="4"/>
<text x="60" y="90" text-anchor="middle" font-size="13" fill="white">Allow</text>
<rect x="120" y="70" width="80" height="30" fill="#ef4444" rx="4"/>
<text x="160" y="90" text-anchor="middle" font-size="13" fill="white">Deny</text>
</svg>
// Detection: check systemLanguage attribute vs navigator.language; flag mismatch on consent elements
function checkSystemLanguage(svgEl) {
const findings = [];
const CONSENT_KEYWORDS = ['allow', 'permission', 'grant', 'accept', 'credential'];
for (const el of svgEl.querySelectorAll('[systemLanguage]')) {
const sysLang = el.getAttribute('systemLanguage') ?? '';
const langCodes = sysLang.split(',').map(l => l.trim().toLowerCase()).filter(Boolean);
// Check consent text descendants
const consentText = Array.from(el.querySelectorAll('text, tspan')).find(t =>
CONSENT_KEYWORDS.some(k => (t.textContent ?? '').toLowerCase().includes(k))
) ?? (CONSENT_KEYWORDS.some(k => (el.textContent ?? '').toLowerCase().includes(k)) ? el : null);
if (!consentText) continue;
const navLang = navigator.language.toLowerCase();
// BCP 47 prefix match: "zh" matches "zh-TW", "zh-CN" etc.
const matches = langCodes.some(code =>
navLang === code || navLang.startsWith(code + '-')
);
const bcr = el.getBoundingClientRect();
const bcrZero = bcr.width === 0 && bcr.height === 0;
if (!matches || bcrZero) {
findings.push({
severity: 'HIGH', code: 'SA-CSS-RQF-002',
note: `element has systemLanguage="${sysLang}"; ` +
`language codes: [${langCodes.join(', ')}]; ` +
`navigator.language="${navigator.language}"; ` +
`match result: ${matches ? 'MATCH (element would render)' : 'NO MATCH — element not rendered'}; ` +
`BCR: width=${bcr.width} height=${bcr.height} (${bcrZero ? 'ZERO' : 'non-zero'}); ` +
`element in <switch>: ${el.parentElement?.tagName === 'switch' ? 'YES' : 'ancestor'}; ` +
`consent text for current browser language is not displayed to user; ` +
`textContent="${(consentText.textContent ?? '').slice(0,40).trim()}..." in DOM; ` +
`detection: getAttribute('systemLanguage') → compare codes with navigator.language; ` +
`flag non-matching systemLanguage on consent text ancestors`
});
}
}
return findings;
}
HIGH — SA-CSS-RQF-002: A consent text group inside a <switch> carries systemLanguage="zh". An English-speaking user’s browser has navigator.language = "en-US"; no prefix match with "zh"; the element is not rendered. The English consent text never appears to the user. The DOM element is present with the full English consent string. Detection requires: reading the systemLanguage attribute on all consent text ancestors; splitting by comma; performing BCP 47 prefix matching against navigator.language; and flagging any mismatch. Also flag any non-empty systemLanguage value on consent text elements as a high-risk conditional processing attribute.
Attack 3: requiredExtensions with unknown extension URI — consent element never renders in any browser (SA-CSS-RQF-003)
requiredExtensions is a space-separated list of extension URIs that the browser must support for the element to render. No current browser supports any custom extension URI — the extension mechanism was intended for SVG viewer plug-ins that never became mainstream. An attacker sets requiredExtensions="https://attacker.example/svg-extension" on a consent text element or its group ancestor. No browser will ever declare support for this URI. The element is permanently excluded from rendering in all real-world browsers. The DOM element is present and textContent returns the consent string intact.
requiredExtensions is less commonly audited than requiredFeatures because it is a less well-known SVG attribute. A scanner that checks requiredFeatures but not requiredExtensions will miss this pattern entirely. Detection: any non-empty requiredExtensions value on a consent text ancestor is suspicious — there are no legitimate use cases for requiredExtensions in modern SVG consent UI. Flag any non-empty value and verify BCR is non-zero.
<!-- SA-CSS-RQF-003: requiredExtensions="https://attacker.example/svg-extension" -->
<!-- no browser supports any custom extension URI → element never rendered -->
<!-- textContent="Allow: full filesystem access" accessible in DOM -->
<!-- scanner checking requiredFeatures misses (only requiredExtensions present) -->
<svg width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<switch>
<!-- requiredExtensions: browser must support this URI; no browser does → skip -->
<!-- DOM element present; textContent intact; BCR = zero (not rendered) -->
<g requiredExtensions="https://attacker.example/svg-extension">
<text x="20" y="50" font-size="13" fill="#111">
Allow: full filesystem access
</text>
</g>
<!-- fallback: no conditions → selected; empty → user sees nothing -->
<g>
<!-- empty fallback -->
</g>
</switch>
<rect x="20" y="70" width="80" height="30" fill="#22c55e" rx="4"/>
<text x="60" y="90" text-anchor="middle" font-size="13" fill="white">Allow</text>
<rect x="120" y="70" width="80" height="30" fill="#ef4444" rx="4"/>
<text x="160" y="90" text-anchor="middle" font-size="13" fill="white">Deny</text>
</svg>
// Detection: flag any non-empty requiredExtensions value on consent text ancestors
function checkRequiredExtensions(svgEl) {
const findings = [];
const CONSENT_KEYWORDS = ['allow', 'permission', 'grant', 'accept', 'credential'];
for (const el of svgEl.querySelectorAll('[requiredExtensions]')) {
const extStr = el.getAttribute('requiredExtensions') ?? '';
if (!extStr.trim()) continue;
const extURIs = extStr.trim().split(/\s+/).filter(Boolean);
// Check consent text descendants
const consentText = Array.from(el.querySelectorAll('text, tspan')).find(t =>
CONSENT_KEYWORDS.some(k => (t.textContent ?? '').toLowerCase().includes(k))
) ?? (CONSENT_KEYWORDS.some(k => (el.textContent ?? '').toLowerCase().includes(k)) ? el : null);
if (!consentText) continue;
const bcr = el.getBoundingClientRect();
const bcrZero = bcr.width === 0 && bcr.height === 0;
findings.push({
severity: 'HIGH', code: 'SA-CSS-RQF-003',
note: `element has requiredExtensions="${extStr}"; ` +
`extension URIs: [${extURIs.join(' | ')}]; ` +
`no browser supports any custom extension URI → element never rendered; ` +
`BCR: width=${bcr.width} height=${bcr.height} (${bcrZero ? 'ZERO — confirmed not rendered' : 'non-zero'}); ` +
`element in <switch>: ${el.parentElement?.tagName === 'switch' ? 'YES' : 'ancestor'}; ` +
`textContent="${(consentText.textContent ?? '').slice(0,40).trim()}..." in DOM; ` +
`scanner checking only requiredFeatures MISSES this pattern; ` +
`detection: flag any non-empty requiredExtensions on consent text ancestors; ` +
`any non-empty value = suspicious; no legitimate use case in SVG consent UI; ` +
`verify BCR of element: zero BCR confirms non-rendering`
});
}
return findings;
}
HIGH — SA-CSS-RQF-003: A consent text group carries requiredExtensions="https://attacker.example/svg-extension". No browser supports any custom extension URI; the condition is permanently false. The element is never rendered in any real-world browser. The DOM element and its textContent are intact. A scanner checking only requiredFeatures (not requiredExtensions) misses this entirely. Detection: flag any non-empty requiredExtensions attribute on consent text ancestors — there are no legitimate use cases for requiredExtensions in modern SVG consent UI. Confirm with BCR check: zero BCR on a consent-text-containing element is a strong signal of conditional non-rendering.
Attack 4: dynamic mousedown injects requiredFeatures on consent group — static scan finds no conditional attributes (SA-CSS-RQF-004)
At rest, the consent text group has no conditional processing attributes (requiredFeatures, requiredExtensions, systemLanguage are all absent). The text is rendered normally. At mousedown, JavaScript adds requiredFeatures="http://www.w3.org/TR/SVG11/feature#NonExistentFeature2026" as an attribute to the consent text group. In browsers that implement SVG conditional processing (requiredFeatures evaluation on elements outside <switch> contexts, or within an active <switch> if the parent structure is set up accordingly), the rendering is suppressed. At mouseup, the attribute is removed and the text reappears.
Static inspection at any time before the click finds no conditional processing attributes on any consent element — the attributes are dynamically added only during the click event. A MutationObserver with attributeFilter: ['requiredFeatures', 'requiredExtensions', 'systemLanguage'] on consent text group elements detects the mutation. The addition of any of these attributes during the click window is flagged immediately, regardless of whether rendering is actually suppressed in the current browser.
<!-- SA-CSS-RQF-004: at rest, no conditional attributes on consent group -->
<!-- mousedown: JS adds requiredFeatures="...#NonExistentFeature2026" → suppresses rendering -->
<!-- mouseup: attribute removed → text reappears -->
<!-- static scan: no conditional attributes found → passes -->
<!-- MutationObserver detects attribute addition -->
<svg id="consent-svg-rqf" width="440" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="440" height="120" fill="white"/>
<!-- at rest: no conditional attributes; text rendered normally -->
<!-- mousedown: requiredFeatures added → rendering suppressed in supporting browsers -->
<g id="consent-group-rqf">
<text x="20" y="50" font-size="13" fill="#111">
Allow: full filesystem access
</text>
</g>
<rect x="20" y="70" width="80" height="30" fill="#22c55e" rx="4"/>
<text x="60" y="90" text-anchor="middle" font-size="13" fill="white">Allow</text>
<rect x="120" y="70" width="80" height="30" fill="#ef4444" rx="4"/>
<text x="160" y="90" text-anchor="middle" font-size="13" fill="white">Deny</text>
</svg>
<script>
(function() {
const svg = document.getElementById('consent-svg-rqf');
const group = document.getElementById('consent-group-rqf');
const NON_EXISTENT = 'http://www.w3.org/TR/SVG11/feature#NonExistentFeature2026';
svg.addEventListener('mousedown', () => {
// add requiredFeatures → suppresses rendering in supporting browsers
group.setAttribute('requiredFeatures', NON_EXISTENT);
}, true);
svg.addEventListener('mouseup', () => {
// remove attribute → text reappears; static scan never sees it
group.removeAttribute('requiredFeatures');
}, true);
})();
</script>
// Detection: MutationObserver on consent group elements for conditional processing attributes
function watchConditionalAttributeMutations(svgEl) {
const findings = [];
const CONSENT_KEYWORDS = ['allow', 'permission', 'grant', 'accept', 'credential'];
const CONDITIONAL_ATTRS = ['requiredFeatures', 'requiredExtensions', 'systemLanguage'];
// Find all potential consent container elements (g, text, tspan, and ancestors)
const consentContainers = Array.from(svgEl.querySelectorAll('g, text, tspan, use, a')).filter(el =>
CONSENT_KEYWORDS.some(k => (el.textContent ?? '').toLowerCase().includes(k))
);
const observer = new MutationObserver(mutations => {
for (const mut of mutations) {
if (!CONDITIONAL_ATTRS.includes(mut.attributeName)) continue;
const el = mut.target;
const newValue = el.getAttribute(mut.attributeName);
const bcr = el.getBoundingClientRect();
findings.push({
severity: 'CRITICAL', code: 'SA-CSS-RQF-004',
note: `Dynamic conditional attribute mutation: ${mut.attributeName} ` +
`${newValue !== null ? `set to "${newValue}"` : 'removed'}; ` +
`oldValue="${mut.oldValue ?? '(absent)'}"; ` +
`BCR post-mutation: width=${bcr.width.toFixed(0)} height=${bcr.height.toFixed(0)} ` +
`(${bcr.width === 0 && bcr.height === 0 ? 'ZERO — rendering suppressed' : 'non-zero'}); ` +
`at rest: no conditional attributes; static scan PASSES; ` +
`attribute added at mousedown; removed at mouseup; ` +
`MutationObserver attributeFilter: ${JSON.stringify(CONDITIONAL_ATTRS)} required; ` +
`textContent="${(el.textContent ?? '').slice(0,40).trim()}..." intact; ` +
`validate feature URI: ${newValue ? (SVG11_FEATURES.has(newValue.trim()) ? 'KNOWN (safe)' : 'NON-STANDARD — CRITICAL') : 'n/a'}`
});
}
});
for (const el of consentContainers) {
observer.observe(el, {
attributes: true,
attributeFilter: CONDITIONAL_ATTRS,
attributeOldValue: true
});
}
// Dispatch synthetic mousedown to trigger dynamic attribute injection
svgEl.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
svgEl.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }));
return findings;
}
CRITICAL — SA-CSS-RQF-004: At rest, the consent text group has no conditional processing attributes; static inspection finds nothing suspicious. At mousedown, JavaScript adds requiredFeatures="http://www.w3.org/TR/SVG11/feature#NonExistentFeature2026" to the group element. In browsers that evaluate requiredFeatures outside <switch> contexts (or with the appropriate parent structure), rendering is suppressed during the click window. At mouseup, the attribute is removed. Detection requires a MutationObserver with attributeFilter: ['requiredFeatures', 'requiredExtensions', 'systemLanguage'] on all consent container elements, combined with a synthetic mousedown dispatch to trigger the injection. Any addition of these attributes during the click window is a critical finding, regardless of current browser rendering behavior.
Summary table
| Attack | Mechanism | What it bypasses | Severity |
|---|---|---|---|
| SA-CSS-RQF-001: requiredFeatures non-existent URI in <switch>; element never rendered | Non-standard feature URI always evaluates false; switch selects empty fallback; consent text not rendered; DOM element present; textContent intact; BCR zero | textContent scanner; requires: validate feature URIs against SVG 1.1 set; flag non-standard URIs on consent ancestors; check BCR of conditional elements | Critical |
| SA-CSS-RQF-002: systemLanguage="zh" hides consent from English browser | Language condition fails for navigator.language=en-US; element not rendered for English speaker; consent text in DOM in English; BCR zero | textContent scanner; requires: read systemLanguage attribute on consent ancestors; BCP 47 prefix match vs navigator.language; flag mismatch | High |
| SA-CSS-RQF-003: requiredExtensions unknown URI; permanent non-rendering | No browser supports any custom extension URI; element never rendered; DOM present; textContent intact; BCR zero; scanner checking only requiredFeatures misses | requiredFeatures-only scanner; requires: also check requiredExtensions; any non-empty value on consent ancestors is suspicious; verify BCR | High |
| SA-CSS-RQF-004: mousedown adds requiredFeatures; removed at mouseup; static scan clean | At rest: no conditional attributes; mousedown: requiredFeatures non-existent added → rendering suppressed; mouseup: removed; static scan always clean | Static DOM inspection; requires: MutationObserver attributeFilter [requiredFeatures, requiredExtensions, systemLanguage] on consent containers + synthetic mousedown | Critical |