technical

Block clickjacking with frame-ancestors

MetricSpot checks for X-Frame-Options or CSP frame-ancestors. These headers stop attackers from embedding your site in a hidden iframe to trick users into clicking.

What this check does

Inspects the response headers for one of:

  • X-Frame-Options: DENY or X-Frame-Options: SAMEORIGIN
  • Content-Security-Policy: frame-ancestors 'none' or frame-ancestors 'self'

The check fails when neither is present and an attacker could embed your page in a hidden iframe on a malicious site.

Why it matters

Clickjacking is a UI-redress attack: the attacker loads your site in a transparent iframe overlaid on a button that looks innocent (“Free iPad! Click here”). The user’s click actually lands on a real button in your iframe — sending a payment, changing a password, transferring funds.

  • The attack works against any logged-in session. If a user is signed in to your site in another tab, the iframe inherits the session cookies, so the click executes as that user.
  • It’s been used in the wild against banking sites, social networks, and admin dashboards. OAuth flows and 2FA enrollment pages have been hit.
  • The fix is one header. Browsers refuse to render your page in an iframe when the header is present — the attack stops being possible.

Frame protection is the cheapest mitigation in web security: one response header per page, no code change, no performance cost.

How to fix it

Pick one of the two mechanisms. Modern browsers prefer CSP frame-ancestors; X-Frame-Options is the legacy header that works in older browsers too. Send both for maximum coverage — they don’t conflict.

Deny all framing (recommended for sites that aren’t meant to be embedded):

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

Allow framing only from your own origin:

Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN

Allow framing from specific trusted origins (only CSP supports this — X-Frame-Options can’t list multiple domains):

Content-Security-Policy: frame-ancestors 'self' https://partner.example.com

nginx:

add_header Content-Security-Policy "frame-ancestors 'none'" always;
add_header X-Frame-Options "DENY" always;

The always flag is critical — without it, nginx skips the header on error responses.

Apache:

Header always set Content-Security-Policy "frame-ancestors 'none'"
Header always set X-Frame-Options "DENY"

Caddy:

header {
  Content-Security-Policy "frame-ancestors 'none'"
  X-Frame-Options "DENY"
}

Cloudflare — Rules → Transform Rules → Modify Response Header, add both headers globally. Or use the Page Shield product if you want WAF-level control.

Next.js (next.config.js):

module.exports = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          { key: "Content-Security-Policy", value: "frame-ancestors 'none'" },
          { key: "X-Frame-Options", value: "DENY" },
        ],
      },
    ];
  },
};

Express:

import helmet from "helmet";
app.use(helmet.frameguard({ action: "deny" }));
app.use(
  helmet.contentSecurityPolicy({
    directives: { frameAncestors: ["'none'"] },
  })
);

Test it:

curl -sI https://yourdomain.com/ | grep -iE 'frame'

Expect both headers, or at least frame-ancestors. Then try embedding in an iframe locally to confirm the browser refuses.

Frequently asked questions

Should I deny all framing or just same-origin?

Default to frame-ancestors 'none' and only relax if you actively need to embed your pages. Most marketing sites, admin dashboards, and apps should never be framed — and the OAuth providers (Google, Apple, Stripe) that genuinely need framing have well-defined embed flows that don’t require relaxing your CSP.

What about X-Frame-Options: ALLOW-FROM?

Deprecated and unsupported in Chrome and Firefox. Use Content-Security-Policy: frame-ancestors instead — it accepts multiple origins and works in every modern browser.

Does this break Google’s PageSpeed Insights or any tool?

No. Those tools fetch the page directly, not in an iframe. The header only affects iframe embedding.

Sources

Last updated 2026-05-11