Blog · MCP Server Security
MCP server IntersectionObserver security — visibility timing side channels, element presence oracle, and cross-origin iframe leakage in MCP UIs
IntersectionObserver reports whether a DOM element intersects with the viewport or a parent container. In an MCP UI, that report is available to any same-origin script — including a compromised MCP skill's injected code. The callback fires with the element's boundingClientRect, intersectionRatio, and time, revealing when tool output enters the viewport, how tall the rendered output is, and whether specific result elements are present — all without reading the element's text content.
The visibility oracle attack in MCP UIs
Consider an MCP UI that renders each tool result in a separate container element with a data-tool-id attribute. A compromised MCP skill injects a script that creates an IntersectionObserver targeting all elements matching [data-tool-id]. When any tool result container enters the viewport, the observer callback fires with:
entry.target— the element itself (readable from same-origin script)entry.boundingClientRect— element dimensions including height (reveals content length)entry.intersectionRatio— fraction of element visibleentry.time— high-resolution timestamp of when visibility changedentry.isIntersecting— boolean: did the element enter the viewport?
From the callback the attacker can infer: which tool result is now being viewed by the user (presence oracle), the approximate length of the tool output (element height correlates with content length for text-heavy results), and how long the user spent viewing each result (by correlating isIntersecting: true and isIntersecting: false timestamps). The callback fires with no user gesture required and no permission prompt — IntersectionObserver is a standard Web API available to all scripts with DOM access.
Why this matters for MCP: If the MCP UI renders tool results that contain sensitive data (API responses, database query results, user records), the visibility oracle reveals which results were rendered and viewed — even if the attacker cannot read the text content directly. Combined with a MutationObserver (which can read content), IntersectionObserver adds a timing dimension: the attacker knows when sensitive content became visible.
Content-length inference via bounding rect
For text-based MCP tool output, element height correlates with content length. A result containing a 10-line API response renders taller than a one-line "success" confirmation. The boundingClientRect.height from the IntersectionObserver callback does not reveal the text, but it narrows the space of possible outputs. In combination with knowledge of the tool's output schema (available from the MCP server manifest), height correlation can identify specific outputs from a finite set of expected responses.
// Attacker's IntersectionObserver in injected script
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
// Height reveals approximate content length
const approxLength = Math.round(entry.boundingClientRect.height / 20); // chars per line estimate
exfiltrate({
toolId: entry.target.dataset.toolId,
height: entry.boundingClientRect.height,
time: entry.time,
approxContentLines: approxLength
});
}
}
}, { threshold: 0.1 }); // fires when 10% visible
document.querySelectorAll('[data-tool-id]').forEach(el => observer.observe(el));
Cross-origin iframe content fingerprinting
IntersectionObserver can observe elements inside the same-origin document. For cross-origin iframes embedded in an MCP UI (e.g., a sandboxed tool result preview), IntersectionObserver cannot read the iframe's DOM — but it can observe the iframe element itself. The intersection callback reports the iframe's boundingClientRect, which reflects the iframe's rendered dimensions. If the cross-origin content in the iframe causes the iframe's height to change (e.g., via postMessage height adjustment from the iframe to the parent), those dimension changes are observable via IntersectionObserver on the parent frame.
This is a weaker vector than direct DOM access, but it leaks whether specific content was loaded in the iframe (by correlating height changes with known content fingerprints) and whether the user scrolled to view the iframe (via isIntersecting transitions).
Defense: limit IntersectionObserver attack surface in MCP UIs
| Attack vector | Defense |
|---|---|
| Injected script observing tool result containers | Render tool results in a separate browsing context (sandboxed iframe with sandbox attribute) so injected scripts from tool output cannot access parent DOM |
| Height-based content length inference | Normalize container heights to fixed values (e.g., fixed-height scrollable container) so bounding rect does not reveal content length |
| Visibility timing as user behavior oracle | Do not expose tool execution timestamps to injected scripts; use CSP script-src to block inline scripts from tool output |
| IntersectionObserver on cross-origin iframe | Avoid exposing cross-origin iframes that auto-resize based on content; use fixed-height iframe containers |
The most effective defense is strict Content Security Policy that prevents any script from tool output from executing in the MCP UI context. IntersectionObserver is a DOM API — it requires script execution. If injected scripts cannot execute, the observer cannot be created.
// Strict CSP preventing tool-output script injection
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM_NONCE}';
style-src 'self' 'unsafe-inline';
frame-src 'none';
object-src 'none'
SkillAudit check: SkillAudit scans MCP server manifests and accompanying UI documentation for patterns that suggest tool output is rendered without sandboxing, and flags UIs that lack a strict script-src CSP — the prerequisite for IntersectionObserver-based side channel attacks. Audit your MCP server →
SkillAudit findings
script-src CSP. Inline scripts injected via tool output execute without restriction, enabling IntersectionObserver, MutationObserver, and other DOM observation APIs. −16 pts
See also: MCP server MutationObserver security (DOM exfiltration of tool output) · MCP server PerformanceObserver security (timing side channels)