Security Guide

MCP server Fenced Frames security — CSP bypass, navigate-to restriction circumvention, and size-constraint timing oracle

Fenced Frames (Chrome 116+) are a new HTML element designed for privacy-preserving ad rendering. They render content in a process-isolated context where the parent cannot read the frame's URL, intercept navigation, or access the frame's DOM. The isolation model sounds strong — but Fenced Frames introduce new attack surfaces: the navigate-to URL is set by Shared Storage worklet output (selectURL()), the frame's size creates a timing side channel, and the Fenced Frame's CSP is scoped to the frame's own origin rather than inheriting the parent's restrictive policy. MCP server tool output embedding a Fenced Frame can navigate it to attacker-controlled URLs via worklet manipulation, and the size-constraint side channel leaks binary decisions back to the parent page.

What the Fenced Frames element provides

<!-- Fenced Frame HTML element — Chrome 116+ -->
<!-- Unlike an iframe, the parent cannot inspect frame.contentDocument or frame.src -->
<fencedframe id="ad-frame"></fencedframe>

<script>
// Navigate a Fenced Frame to a URL selected by a Shared Storage worklet
// The parent cannot read which URL was chosen — that's the privacy model

await window.sharedStorage.worklet.addModule('/worklet.js');
const config = await window.sharedStorage.selectURL(
  'select-ad',
  [
    { url: 'https://cdn.example/ad-a.html' },   // URL index 0
    { url: 'https://attacker.example/track.html' } // URL index 1
  ],
  { data: {} }
);
// config is a FencedFrameConfig — opaque to the parent, navigates to selected URL
document.getElementById('ad-frame').config = config;
// The Fenced Frame now loads the selected URL; parent cannot read which one
</script>

Attack surface 1: worklet-controlled navigate-to URL

The critical insight: the Fenced Frame's URL is determined by the Shared Storage worklet's selectURL() return value — which the attacker controls through the worklet code. An attacker who can inject worklet code, or who can influence the stored values the worklet reads, controls where the Fenced Frame navigates.

Fenced Frame as CSP bypass for navigation. The parent page's Content Security Policy restricts which URLs the parent can navigate to or embed. But Fenced Frame navigation URLs are determined by the worklet output, not by the parent page's CSP. A parent with frame-src 'self' can still embed a Fenced Frame configured to load https://attacker.example/ — the CSP frame-src directive does not apply to Fenced Frame navigation targets set via FencedFrameConfig.

Attack surface 2: size-constraint timing oracle

Fenced Frames have constrained dimensions: the parent sets allowed size options, and the selected URL must render within those constraints. The time it takes for the Fenced Frame to reach a stable layout is measurable from the parent via PerformanceObserver:

This timing channel undermines the fundamental privacy guarantee of Fenced Frames: the parent is supposed to be blind to which URL was selected, but timing measurement partially defeats this.

Attack surface 3: Fenced Frame CSP is self-scoped, not inherited

Frame typeCSP inheritanceAttack surface
Standard iframe (cross-origin) Does not inherit parent CSP; parent can set Permissions-Policy restrictions on iframe Standard cross-origin CSP boundary
Sandboxed iframe (sandbox attribute) Inherits some parent restrictions; null origin blocks most APIs Strong isolation when sandbox applied correctly
Fenced Frame Has its own CSP from its origin's response headers; parent cannot add CSP to it; parent Permissions-Policy does not apply inside the frame Frame content's security depends entirely on the frame's own response headers — parent cannot protect the frame

Browser availability and Permissions-Policy

Fenced Frames ship in Chrome 116+ with the Privacy Sandbox feature flags. They are not available in Firefox or Safari. There is no Permissions-Policy directive to disable Fenced Frames globally — but disabling Shared Storage (Permissions-Policy: shared-storage=()) prevents selectURL() calls that create Fenced Frame configs, effectively neutralizing the worklet-navigation attack.

Defenses

DefenseBlocks Fenced Frame attacks?Notes
Permissions-Policy: shared-storage=() Yes — blocks selectURL() which creates Fenced Frame configs Indirect but effective; disabling Shared Storage disables worklet-driven Fenced Frame navigation
CSP script-src to block worklet module loads from external origins Partial — blocks addModule() from non-allowed origins Does not block tool output from using same-origin worklets
Sandboxed cross-origin iframe for tool output Yes — null origin context cannot call sharedStorage APIs Comprehensive; requires cross-origin rendering architecture
Use Firefox or Safari Yes — Fenced Frames not available outside Chrome Browser selection control
Static analysis for fencedframe element and selectURL calls Detects — grep for <fencedframe> and selectURL in tool output templates SkillAudit performs this check

Findings SkillAudit reports

High Tool output creating a Fenced Frame config via selectURL() with attacker-controlled URL in the URL list — navigation target not constrained to legitimate origins
High Tool output loading worklet via sharedStorage.worklet.addModule() from an external origin — worklet code is not audited and controls Fenced Frame navigation
Medium Tool output embedding <fencedframe> elements without Permissions-Policy: shared-storage=() in MCP renderer responses — worklet-navigation attack possible
Low MCP server documentation does not address Fenced Frame CSP isolation model — frame content's security depends on the frame origin's own headers, not the parent's CSP

Related guides: Shared Storage API security, Attribution Reporting API, Topics API.

Get a graded audit. Paste your MCP server's GitHub URL at skillaudit.dev for a report covering Fenced Frames, all Privacy Sandbox surfaces, and your full browser permission posture in 60 seconds.