Missing X-Frame-Options (clickjacking)
What it is
Your site sends neither the X-Frame-Options header nor a CSPframe-ancestors directive. Without them, anyone can embed you in an <iframe> on a site they control.
Why it’s a problem
The attack is called clickjacking. The attacker builds an ordinary-looking page, loads your site inside an iframe, makes that iframe invisible with a CSS trick, and places a “Win an iPhone” button on top. The visitor clicks — but really they clicked “Delete account” on your site, where they’re logged in.
Practical scenarios:
- Changing a password (
POST /change-passwordfor the same account, from a CSRF-protected form). - One-click purchase — sneaking in a “Confirm purchase” click.
- Approving an OAuth application.
How to fix it
There are two headers — you can send both:
X-Frame-Options (older, supported by every browser)
X-Frame-Options: DENY
# or
X-Frame-Options: SAMEORIGINDENY — nobody may embed you in an iframe, not even you.SAMEORIGIN — only your own domain may embed you in an iframe.
CSP frame-ancestors (more modern, more granular)
Content-Security-Policy: frame-ancestors 'none';
# or:
Content-Security-Policy: frame-ancestors 'self';
# or with an allowlist:
Content-Security-Policy: frame-ancestors 'self' https://partner.example.cz;When you have a CSP, frame-ancestors takes precedence over X-Frame-Options.
Specific configuration
nginx:
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;Apache:
Header always set X-Frame-Options "DENY"
Header always set Content-Security-Policy "frame-ancestors 'none'"When NOT to use DENY?
Sometimes a site genuinely needs to be inside an iframe — on your own e-shop (an embedded widget), in a partner portal, in a white-label solution. In that case, don’t use DENY; instead set a specific allowlist via frame-ancestors.