Why is the page still vulnerable to clickjacking?
clickjacking-protection-gaps page
The issue
A page with no effective framing restriction can be loaded by any site inside an invisible frame, so a visitor clicking a decoy is really clicking your page, signed in. It happens two ways: no framing header at all, or an X-Frame-Options header that looks right but is ignored because the Content Security Policy also sets frame-ancestors with a looser value.
The cause of the second is that browsers which support frame-ancestors follow it and disregard X-Frame-Options. When the two headers are set by different systems, they drift apart.
Scope
Framing is decided per response. Different layers often set the two headers, the application X-Frame-Options and a CDN or proxy the policy, so some pages can be covered while others aren’t.
How to check you have it
- In probeo: a policy with no
frame-ancestorsis reported ascsp:missing-frame-ancestors, graded minor, whether or notX-Frame-Optionsis also sent. A response with no policy is reported ascsp-missing. - By hand: run
curl -sI https://your-site.example/and readx-frame-optionsand theframe-ancestorspart ofcontent-security-policytogether. Where both exist,frame-ancestorsdecides. - In the browser: load the page in an
<iframe>on another origin. A protected page doesn’t render, and the console names the header that blocked it.
The fix
Set frame-ancestors in the Content Security Policy to the framing you intend, and make X-Frame-Options agree with it.
For most pages that is frame-ancestors 'none' with X-Frame-Options: DENY. If the site frames its own pages, use 'self' with SAMEORIGIN. If one system owns the policy and another owns X-Frame-Options, decide which sets framing and have the other follow.
Done when: every response sends frame-ancestors with the intended value, any X-Frame-Options matches it, and the check above comes back clean.
Do not
- Do not rely on
X-Frame-Optionsalone where the policy setsframe-ancestors. - Do not take an
X-Frame-Options: DENYheader as proof without readingframe-ancestors. - Do not use
*inframe-ancestorswhen the intent is to block framing. - Do not let two systems set the two headers without checking they agree.
FAQ
If X-Frame-Options is set to DENY and CSP has frame-ancestors ‘self’, which one wins?
frame-ancestors. Browsers that support it ignore X-Frame-Options, so same-origin pages can frame this one despite DENY.
Does this mean X-Frame-Options is useless if CSP is deployed?
In current browsers, when frame-ancestors is set, yes. It still helps old clients that don’t support the policy, so keep it with a matching value.
How does a page end up with no framing protection at all?
By default. Servers, frameworks and CDNs don’t usually add either header unless configured to, so a page has none until someone adds one.
Can frame-ancestors be missing from CSP while XFO still provides protection?
Yes. If the policy doesn’t set frame-ancestors, browsers follow X-Frame-Options. The override only happens when frame-ancestors is present. Probeo still reports the missing directive, because the policy is where framing belongs.
What is the recommended configuration for defense in depth?
frame-ancestors 'none' in the policy and X-Frame-Options: DENY alongside, or 'self' with SAMEORIGIN if the site frames itself. The two should never disagree.
Why do security scanners sometimes miss the XFO-override problem?
A check that reads X-Frame-Options on its own sees DENY and passes. Seeing the override means reading both headers together, the way the browser does.
Which product detects this
Probeo checks this on every page of your site as part of Site Clarity. This page explains one instance; the product tells you every page that has it.