Back to scan results
Check 13 of 24

Clickjacking Protection

We look for either an X-Frame-Options header or a Content-Security-Policy with a frame-ancestors directive on your homepage. Either one tells browsers to refuse to embed your page in a frame on someone else's site.

What this check probes

The check accepts any of these as a pass:

  • X-Frame-Options: DENY — no framing allowed, anywhere.
  • X-Frame-Options: SAMEORIGIN — only your own pages can frame you.
  • Content-Security-Policy: frame-ancestors 'none' — modern equivalent of DENY.
  • Content-Security-Policy: frame-ancestors 'self' — modern equivalent of SAMEORIGIN.
  • Content-Security-Policy: frame-ancestors https://trusted.example.com — explicit allow-list.

X-Frame-Options: ALLOW-FROM is non-standard and ignored by most browsers — flagged as a partial pass.

Why this matters for PCI DSS

A clickjacking attack works like this: the attacker loads your real, legitimate page in an invisible iframe at z-index 1. They overlay decoy content (often "Click here to claim your prize") on top. The user clicks the prize button, but the click actually lands on the "Confirm payment" or "Delete account" button in the framed real page. Because the user is logged in, the action succeeds.

For PCI scope: clickjacking on a payment confirmation page can authorize unintended charges. PCI DSS 4.0 Requirement 6.2.4 requires defenses against "common software attacks" — clickjacking is on every standard list (OWASP, SANS).

How to fix it

Pick one approach and apply it to every response:

nginx:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;

Apache:

Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"

IIS:

<httpProtocol><customHeaders>
  <add name="X-Frame-Options" value="SAMEORIGIN" />
  <add name="Content-Security-Policy" value="frame-ancestors 'self'" />
</customHeaders></httpProtocol>

Express (Node.js):

const helmet = require('helmet');
app.use(helmet.frameguard({ action: 'sameorigin' }));
app.use(helmet.contentSecurityPolicy({
    directives: { 'frame-ancestors': ["'self'"] }
}));

WordPress — most security plugins (Wordfence, iThemes Security) have a one-click toggle.

Note on payment iframes: if your payment page is itself a Stripe / Adyen / Braintree iframe, the customer's browser is loading that iframe from the gateway's origin — clickjacking protection there is the gateway's responsibility, and they handle it. Your responsibility is the page that hosts the iframe.

Verify with: curl -I https://example.com/ | grep -i -E "x-frame|frame-ancestors"

Fixed it? Re-run the scan to confirm.

Run scan again