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.
- Worklet code injection via sharedStorage.run(). If the MCP tool output calls
sharedStorage.worklet.addModule()with an attacker-controlled URL, the worklet code is attacker-provided. The worklet can read all stored keys for the attacker's origin and return any URL index — including one pointing tohttps://attacker.example/collect?data=.... - Stored value manipulation. If the tool output can write to shared storage (
sharedStorage.set()) before the worklet runs, the attacker can influence which URL the worklet selects by controlling the stored values the worklet logic reads. - URL list control. The URL list is passed by the calling page — controlled by the MCP tool output. Even if the worklet is legitimate, the tool output chooses which URLs are in the list.
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:
- URL A (e.g. a small ad image) renders in ~50ms.
- URL B (e.g. a heavy tracking page) renders in ~300ms.
- The parent measures load time via
PerformanceObserveron thefencedframeelement and infers which URL was selected — a binary oracle that leaks the worklet's decision.
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 type | CSP inheritance | Attack 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
| Defense | Blocks 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
selectURL() with attacker-controlled URL in the URL list — navigation target not constrained to legitimate origins
sharedStorage.worklet.addModule() from an external origin — worklet code is not audited and controls Fenced Frame navigation
<fencedframe> elements without Permissions-Policy: shared-storage=() in MCP renderer responses — worklet-navigation attack possible
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.