Blog · MCP Server Security

MCP server ResizeObserver security — layout-based exfiltration, content-size oracle, data length inference, and ResizeObserver loop DoS in MCP UIs

ResizeObserver fires its callback every time a watched element's size changes. In an MCP UI, the tool result container grows to fit new content each time a tool call returns. An attacker with injected same-origin script can observe every resize event on every tool result container — inferring output length, detecting when results arrive, and correlating size changes with tool identity. A second attack is the ResizeObserver loop: if the observer callback modifies the element's size, the browser fires the callback again, creating an infinite loop that freezes the tab.

Content-size oracle via ResizeObserver

Every time an MCP tool returns a result and the UI renders it into a container, that container's layout changes. If the result is 50 characters long, the container is shorter than if the result is 5,000 characters. An attacker who can inject a ResizeObserver on the tool result container receives:

The height reveals the approximate content length. For structured output (JSON with a known schema), the height distribution is often narrow enough that an attacker can distinguish between a short "no results" response and a long "found N records" response.

// Attacker's ResizeObserver on tool result containers
const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const height = entry.contentRect.height;
    const toolId = entry.target.dataset.toolId;
    // Height → approximate content size
    // Combined with tool schema knowledge → narrow the output
    exfiltrate({ toolId, height, timestamp: performance.now() });
  }
});

document.querySelectorAll('[data-tool-result]').forEach(el => ro.observe(el));

// Also observe future results via MutationObserver
const mo = new MutationObserver((mutations) => {
  for (const m of mutations) {
    for (const node of m.addedNodes) {
      if (node.dataset?.toolResult) ro.observe(node);
    }
  }
});
mo.observe(document.getElementById('results'), { childList: true, subtree: true });

Combined attack: ResizeObserver (size oracle) + MutationObserver (content text) + IntersectionObserver (visibility timing) running in parallel from a single injected script gives the attacker a complete picture of tool output: when it arrived, how large it is, what it says, and when the user viewed it — all from standard Web APIs with no permissions required.

ResizeObserver loop denial of service

If the ResizeObserver callback modifies the size of the observed element (directly or via style change), the browser schedules another resize notification, which fires the callback again, which changes the size again — infinitely. Browsers detect this loop and throw a ResizeObserver loop completed with undelivered notifications error, but the detection is not guaranteed to terminate the loop before significant page freeze. A malicious MCP skill can trigger this intentionally:

// DoS via ResizeObserver loop
const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    // Modify width → triggers another resize → callback fires again → infinite
    entry.target.style.width = (entry.contentRect.width + 1) + 'px';
  }
});
ro.observe(document.body); // observe the root — affects entire page layout

The loop causes continuous layout recalculation, consuming 100% of one CPU core and making the UI unresponsive. The defense at the application level is to not expose modifiable root elements to injected scripts, and at the CSP level to prevent inline script execution from tool output.

Defense patterns for MCP UIs

AttackSeverityDefense
ResizeObserver height oracle on tool result containers High Render tool results in sandboxed cross-origin iframe; same-origin scripts cannot observe iframe element dimensions via ResizeObserver in the way they can observe same-origin elements
Content-size inference for output classification Medium Pad tool result containers to fixed heights; use overflow: hidden on fixed-height containers so layout height does not vary with content length
ResizeObserver loop DoS High Block inline script execution via strict CSP script-src 'nonce-X'; injected scripts from tool output cannot create ResizeObserver instances
Combined observer attack (Resize + Mutation + Intersection) Critical Sandbox all tool output; strict CSP prevents any of these observers from being instantiated by injected code

Fixed-height container pattern

For MCP UIs that cannot use sandboxed iframes for tool output, fixed-height containers with scrollable overflow prevent height-based content-length inference:

/* Tool result container — fixed height prevents content-size oracle */
.tool-result {
  height: 400px;          /* fixed — does not change with content length */
  overflow-y: auto;       /* content scrolls within fixed container */
  contain: strict;        /* layout containment — changes inside don't affect outside */
}

The contain: strict property adds layout, style, and size containment, preventing layout changes inside the container from propagating to ancestor elements. This limits ResizeObserver signal to the container itself (which no longer changes height) rather than its parent chain.

SkillAudit detection: SkillAudit checks MCP server UI manifests for dynamic-height tool result containers and flags them as a ResizeObserver attack surface when combined with missing CSP script-src directives. Audit your MCP server →

SkillAudit findings

High Tool output rendered in dynamic-height same-origin containers without CSP script-src restriction. Injected scripts can create ResizeObserver instances that report content-length-correlated height changes for every tool result. −18 pts
High No CSP preventing inline script execution from tool output. ResizeObserver loop attack can freeze the UI tab indefinitely if a malicious tool result injects a looping observer. −16 pts
Medium Tool result containers lack contain: strict. Layout changes inside tool result containers propagate to ancestor elements, expanding the ResizeObserver observable surface. −10 pts

See also: MCP server IntersectionObserver security · MCP server MutationObserver security