Why is the page still vulnerable to clickjacking?

clickjacking-protection-gapspage

Two distinct failure patterns leave pages vulnerable to clickjacking despite apparent configuration. The first is complete absence: neither Content-Security-Policy frame-ancestors nor X-Frame-Options is present, so browsers impose no framing restriction at all. The second is silent override: X-Frame-Options is set, but a Content-Security-Policy header with frame-ancestors is also present, and browsers discard XFO entirely per the CSP specification. If that frame-ancestors value is permissive or absent from the directive, the restrictive XFO value has no effect.

What’s wrong

The page has no effective framing protection. In the first pattern, neither X-Frame-Options nor CSP frame-ancestors appears in the response. The browser applies its default behavior: allow embedding from any origin. In the second pattern, X-Frame-Options is present and may even be set to DENY, but a Content-Security-Policy header also exists containing a frame-ancestors directive. Per the W3C CSP specification, when both headers are present, browsers must use frame-ancestors and ignore X-Frame-Options entirely. If frame-ancestors allows broader embedding than XFO intended — or if frame-ancestors was set to a permissive value like * — the XFO restriction is silently discarded. The page is frameable despite the operator believing XFO provides protection.

Why it matters

The complete-absence case is straightforward: the page has no clickjacking defense. Any origin can embed it in an iframe, overlay it with deceptive elements, and intercept authenticated user actions. The XFO-override case is more dangerous because it creates false confidence. An operator inspects response headers, sees X-Frame-Options: DENY, and concludes the page is protected. Security audits that check for XFO presence report no issue. But if a CSP header with frame-ancestors is also present, browsers follow CSP and discard XFO. The protection the operator verified never takes effect. This is not a theoretical edge case — it follows directly from the CSP specification and is consistent across all modern browsers.

The correct change

For pages with no framing headers at all, add Content-Security-Policy with frame-ancestors set to ‘none’ or specific trusted origins. Optionally include X-Frame-Options: DENY for backward compatibility with legacy clients. For pages where XFO is being overridden by CSP, the fix is to set frame-ancestors to the intended restriction within the existing CSP header. Do not rely on XFO when CSP is present. If the CSP header is managed by a different team or system than XFO, coordinate: the frame-ancestors directive in CSP is the only value browsers will enforce. The end state is a single, authoritative framing policy expressed through frame-ancestors, with XFO as an optional fallback that matches the CSP intent.

Scope

Both conditions apply at the page level. Each HTTP response carries its own headers, and framing policy is evaluated per-response. A site may have some pages correctly protected while others have gaps — particularly when different application layers, CDN configurations, or middleware each add their own headers without coordination. The XFO-override pattern is most common when CSP and XFO are configured by different systems: a web application framework sets XFO, while a CDN or security proxy injects CSP. Neither system is aware that their headers interact.

How to verify

  • Validation confirms both protection gap patterns are resolved:
    • Content-Security-Policy header includes frame-ancestors directive with ‘none’ or specific origins
    • If X-Frame-Options is also present, its value is consistent with frame-ancestors intent
    • Browser DevTools confirm the page is blocked from embedding by unauthorized origins
    • Attempting to load the page in an iframe from a disallowed origin produces a CSP violation in the console
    • No response lacks both X-Frame-Options and frame-ancestors
    • Operators verify that frame-ancestors in CSP matches the intended policy, not just that XFO is set

Takeaway

  • Two patterns produce clickjacking gaps: no framing headers at all, and XFO being silently overridden by CSP
  • When CSP frame-ancestors and X-Frame-Options both exist, browsers use frame-ancestors and ignore XFO entirely
  • Checking for XFO alone is insufficient — the CSP header determines the effective framing policy
  • Frame-ancestors is the authoritative directive; XFO exists only as a legacy fallback for browsers without CSP support

FAQ

If X-Frame-Options is set to DENY and CSP has frame-ancestors ‘self’, which one wins?

Frame-ancestors wins. The CSP specification is explicit: when a CSP header containing frame-ancestors is present, browsers must ignore X-Frame-Options. In this case, the page would be embeddable by same-origin pages despite XFO saying DENY. The DENY value has no effect. This is consistent behavior across Chrome, Firefox, Safari, and Edge.

Does this mean X-Frame-Options is useless if CSP is deployed?

In modern browsers, yes — when frame-ancestors is present in CSP. XFO still has value as a fallback for legacy clients that do not support CSP. The practical approach is to set frame-ancestors as the primary control and include a matching XFO value for backward compatibility. The two headers should express the same intent.

How does a page end up with no framing protection at all?

Default server configurations typically do not include framing headers. If no middleware, reverse proxy, or application code adds X-Frame-Options or CSP frame-ancestors, the response ships without either. New applications, static file servers, and CDN-served assets commonly have no framing headers unless explicitly configured. The absence is the default state, not a misconfiguration — it simply means protection was never added.

Can frame-ancestors be missing from CSP while XFO still provides protection?

Yes, but only if the CSP header exists without a frame-ancestors directive at all. If CSP is present but does not include frame-ancestors, XFO remains effective because there is no CSP directive to override it. The override happens specifically when frame-ancestors appears in CSP — even if its value is permissive. A CSP with only default-src and script-src but no frame-ancestors leaves XFO intact.

Set Content-Security-Policy with frame-ancestors ‘none’ (or specific origins if embedding is required). Also set X-Frame-Options: DENY (or SAMEORIGIN to match). Both headers express the same intent, but frame-ancestors is the one browsers enforce. XFO provides coverage for the diminishing set of clients without CSP support. Avoid contradictions between the two — operators reading response headers should not get conflicting signals.

Why do security scanners sometimes miss the XFO-override problem?

Many scanners check for X-Frame-Options presence and valid values independently from CSP evaluation. If XFO is present and set to DENY, the scanner reports clickjacking protection as configured. The scanner does not simulate the browser’s precedence logic to determine that frame-ancestors overrides XFO. This is a tool limitation, not a security property. Manual review of both headers together is necessary to confirm effective protection.

The fix

Ensure effective clickjacking protection by addressing missing framing headers and XFO-override conflicts

Done when: Content-Security-Policy includes frame-ancestors with explicit framing restrictions, and any X-Frame-Options header is consistent with the CSP intent rather than contradicting or being silently overridden

Do not

  • Do not rely on X-Frame-Options alone when CSP is present — browsers ignore XFO when frame-ancestors exists
  • Do not assume XFO presence means protection is active without checking for a CSP frame-ancestors directive
  • Do not use permissive frame-ancestors values like * when the intent is to block framing
  • Do not configure XFO and CSP through independent systems without verifying they express the same policy

How to verify

  • Content-Security-Policy includes frame-ancestors directive
  • Frame-ancestors value matches intended framing restriction
  • X-Frame-Options value is consistent with frame-ancestors if both are present
  • Browser blocks unauthorized framing attempts based on effective policy
  • No response lacks both framing headers entirely

Find every page with this

probeo monitors this continuously across every page of your site, and records which pages passed, which failed, and which it could not determine.

Book a Call