Safety
CSP vs X-Frame-Options for Clickjacking Protection
April 6, 2023
CSP vs X-Frame-Options is a choice between a modern framing policy and a legacy header that older browsers still understand. Content-Security-Policy frame-ancestors is the stronger, more precise control for new sites. X-Frame-Options still matters as a compatibility fallback. Most public marketing sites should ship both, with CSP as the source of truth and X-Frame-Options as DENY or SAMEORIGIN to match.
The practical difference
X-Frame-Options is a single-purpose header. DENY blocks all framing. SAMEORIGIN allows the same origin to frame the page. ALLOW-FROM exists in older docs but is not a reliable modern control.
Content-Security-Policy frame-ancestors can allow none, self, or an explicit origin list. It replaces the need for ALLOW-FROM and can live next to the rest of a CSP policy that also limits scripts, objects, and form targets.
RoastMyOpsec's free header audit reads the primary document response only — no payload injection, no fuzzing. Missing framing controls show up as a no-brainer finding because browsers forgive clickjacking more easily without them.
How to choose based on the site
Start with how the page is used. A brochure marketing site almost never needs to be framed. An embeddable widget, status board, or partner checkout flow might. The header decision follows the product decision, not the other way around.
If the site must never appear inside another origin's iframe, set CSP frame-ancestors 'none' and X-Frame-Options DENY. If same-site tools frame dashboards, use frame-ancestors 'self' and X-Frame-Options SAMEORIGIN. If a named partner origin must frame a specific route, list that origin in frame-ancestors and do not pretend X-Frame-Options can express the same allowlist.
- Marketing homepage, blog, and docs: deny framing unless a real embed exists.
- App shells that iframe themselves: SAMEORIGIN / 'self'.
- Partner embeds: CSP allowlist on that route only; do not weaken the rest of the site.
Side-by-side comparison
The table below is a planning tool, not a claim that one header works for every stack. Confirm what your CDN, reverse proxy, and app framework actually emit on the HTML document.
| Decision factor | X-Frame-Options | CSP frame-ancestors | Takeaway |
|---|---|---|---|
| Precision | DENY or SAMEORIGIN in practice | none, self, or origin list | CSP wins when a named partner must frame one route |
| Browser coverage | Very old browsers still read it | Modern browsers prefer it and it overrides XFO when both exist | Ship both until you know your traffic is modern |
| Scope | Framing only | Can sit beside script-src, object-src, form-action | CSP is a policy surface; XFO is a single lock |
| Misconfig risk | ALLOW-FROM is a false friend | A loose default-src can still leave other holes | Framing is one check; XSS defense is another |
| Best first move | DENY on public marketing HTML | frame-ancestors 'none' on the same responses | Match the two so CDNs do not disagree |
When X-Frame-Options still wins
X-Frame-Options wins as a compatibility belt. Some older enterprise browsers and crawlers still honor it. It also documents intent in a header that security scanners and humans can read without parsing a full CSP.
It does not win as a substitute for CSP. If you only set XFO and skip frame-ancestors, you are betting that every relevant client still treats XFO as authoritative.
When CSP frame-ancestors wins
CSP wins when you need an allowlist, when you already maintain a CSP, and when you want one policy language for framing plus script control. It also wins on routes that must be framed by a specific HTTPS origin without opening the rest of the site.
The tradeoff is operational: CSP is easy to break with a tag manager, a chatbot widget, or an inline style. Start report-only if you are new to CSP, but do not leave framing unprotected while you iterate on script-src.
Common mistakes
The first mistake is setting X-Frame-Options on APIs and forgetting the HTML document. Clickjacking is a document problem.
The second mistake is allowing framing globally because one blog embed needed it. Scope the exception.
The third mistake is a CSP that never mentions frame-ancestors, then assuming default-src covers it. It does not. frame-ancestors is its own directive.
Check your public headers
Paste the URL you own into RoastMyOpsec for a free header audit. The scan reads the primary response, grades CSP quality signals, HSTS, framing controls, Referrer-Policy, nosniff, and Permissions-Policy, and leaves exploit payloads out of the picture.
If framing is missing, fix it at the edge so every HTML route inherits the policy. Then re-scan.
Free audit the URL you own
RoastMyOpsec is a defensive public-surface roast: headers, cookies, sensitive paths, and more — no exploit payloads. Start with the free audit, then open the vault if the blurred findings look expensive.
Free audit nowFAQ
- Is CSP or X-Frame-Options better for clickjacking?
- CSP frame-ancestors is the better modern control. Keep X-Frame-Options as a matching fallback (DENY or SAMEORIGIN) so older clients are not left open.
- Does frame-ancestors replace X-Frame-Options?
- In supporting browsers, CSP frame-ancestors is the framing authority when both are present. Shipping only one header still leaves gaps, so most public sites should send both until you know you do not need the legacy signal.
- Should a marketing site use DENY or SAMEORIGIN?
- Use DENY and frame-ancestors 'none' unless a same-origin tool actually frames the page. SAMEORIGIN is for first-party iframes, not for 'maybe someday' embeds.
- Does RoastMyOpsec exploit clickjacking?
- No. The audit is a consented read of public headers and responses. Private IPs and localhost are blocked. No exploit payloads.