Summary

  • X-Frame-Options: SAMEORIGIN let a page tell browsers to keep outsiders from placing it inside a frame. The 2013 RFC documented a blind spot in implementations that compared the protected page with only the top-level page.
  • Put a page from origin A at the top, an untrusted page from B inside it, and A’s protected page inside B. A top-level-only check saw A matching A and missed B. W3C’s 2016 frame-ancestors rule required the browser to inspect every ancestor before loading the protected page.

Three windows, one missed border

Imagine a blue page from site A. It embeds a red page from site B, which in turn tries to embed a sensitive blue page from A. The sensitive page sends X-Frame-Options: SAMEORIGIN. The owner expects the browser to reject the red page’s attempt to frame it. The browser checks the outermost blue page instead. A matches A; the protected page loads inside B’s frame. The policy was present. The inspection stopped at the wrong border.

This is the mechanism RFC 7034 described in October 2013. The document was Informational, recording the header’s use and the variation among browsers at that date. It identified clickjacking as the reason for the header: one page can layer another page’s buttons under its own interface and induce a click on the hidden target. The RFC’s online-shop confirmation page was a defensive example. The A/B/A chain was a documented failure mode, not a report of a named compromised shop.

A server's rule needed a browser's eye

X-Frame-Options carried a simple instruction in an HTTP response. DENY forbade framing anywhere. SAMEORIGIN allowed same-origin framing. ALLOW-FROM named a single allowed origin. The site whose content might be framed set the header; the browser enforced it. That division seemed clean: the page owner declared a boundary, and the visitor’s software kept it.

The boundary was less clean in a nest of frames. RFC 7034 said implementations differed in how they evaluated the header. For SAMEORIGIN, some compared only the protected page and the top-level browsing context, skipping a hostile intermediate frame. A page owner could set the correct header yet still depend on the browser asking an incomplete question. The error was not a missing policy string. It was an incomplete traversal of the page’s ancestry.

That distinction matters beyond this one header. A web page can be embedded several levels deep, with each intermediate page owned by a different site. Comparing two endpoints tells the browser little about who controls the path between them. The site at the top may be trustworthy, while the page immediately wrapping the protected content is not.

The standard inspected the whole family

The W3C Content Security Policy Level 2 Recommendation of December 2016 gave the protected resource a more precise instruction: frame-ancestors. Before loading it into a frame, the browser builds a list of every ancestor browsing context. It checks each ancestor’s document URL against the allowed sources. If any fails, loading stops. In the A/B/A chain, the red B page fails even though the top-level blue A page matches.

The W3C text explicitly contrasted that all-ancestor check with the top-level-only behavior documented for many X-Frame-Options: SAMEORIGIN implementations. It said frame-ancestors obsoleted the older header and should take precedence when both policies were sent. The directive could name more than one allowed host, another limit of the older ALLOW-FROM value. The decisive repair, though, was the unit of inspection: every parent, not a convenient endpoint.

Two small delivery rules show how easily a correct-looking policy can miss the browser. X-Frame-Options had to be an HTTP header; the RFC said browsers ignored a copy placed in a meta http-equiv tag. frame-ancestors also had to arrive in a delivered policy rather than a meta element. A general CSP default-src 'none' did not silently supply a frame-ancestor ban. The owner had to state the embedding rule in the place the browser would actually enforce it.

The story ends with a more exact question for the browser. The page owner can specify who may surround its content. The browser must inspect the full chain that does surround it. A declaration checked against the wrong window is only a well-worded hope.