RedScore.ai

Fixes

Web Application Security · Updated 2026-05-02

X-Frame-Options / frame-ancestors

Anti-clickjacking. Pass needs X-Frame-Options DENY/SAMEORIGIN or CSP frame-ancestors. Modern: use CSP. Legacy compat: send both.

Clickjacking is the attack where someone embeds your page in a hidden iframe on their site and tricks users into clicking on attacker-positioned elements (Like "Confirm transfer" or "Authorize app"). Defenses come in two forms: the older X-Frame-Options header (DENY or SAMEORIGIN), and the newer CSP frame-ancestors directive. Either one satisfies this check; modern best practice is to send both for older-browser compatibility.

How the check works

Per primary host, the check looks at two signals:

  • X-Frame-Options header value, normalized to lowercase. Pass if it is exactly deny or sameorigin.
  • Content-Security-Policy header. Pass if it contains frame-ancestors anywhere in the policy.

Either signal alone satisfies the check. The deprecated X-Frame-Options ALLOW-FROM uri syntax is not recognized; modern browsers do not support it either.

How the verdict maps to evidence

  • Pass (8/8 per host): X-Frame-Options is deny or sameorigin, OR Content-Security-Policy contains frame-ancestors.
  • Fail (0/8 per host): neither signal present.

Fix: pick a strategy, then send both headers

Decide who is allowed to embed your pages in an iframe:

  • Nobody (most pages): use frame-ancestors 'none' and X-Frame-Options: DENY.
  • Only your own origins: use frame-ancestors 'self' and X-Frame-Options: SAMEORIGIN.
  • Specific partner origins: use frame-ancestors with explicit hosts. X-Frame-Options cannot express this; modern browsers respect frame-ancestors over X-Frame-Options when both are sent, so older browsers fall back to SAMEORIGIN behaviour and modern browsers honour the partner allow-list.

If you do not embed your own pages in iframes anywhere, default to deny. It is more restrictive and rules out clickjacking entirely.

Recommended headers

Lock down (no embedding allowed)

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

Same-origin only

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

Specific partners (modern browsers honour CSP, older fall back to SAMEORIGIN)

Content-Security-Policy: frame-ancestors 'self' https://partner.example.com https://embed.partnerco.io;
X-Frame-Options: SAMEORIGIN

Per-server snippets

nginx

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

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'"
header X-Frame-Options "DENY"

Cloudflare (Transform Rules)

Set both response headers via Transform Rules:
  Content-Security-Policy: frame-ancestors 'none'
  X-Frame-Options:         DENY

Express / Node.js (Helmet)

import helmet from "helmet";

// helmet.frameguard is X-Frame-Options.
app.use(helmet.frameguard({ action: "deny" }));

// CSP frame-ancestors via the contentSecurityPolicy middleware:
app.use(helmet.contentSecurityPolicy({
  directives: { frameAncestors: ["'none'"] },
}));

Django

# settings.py
X_FRAME_OPTIONS = "DENY"  # default in modern Django
# CSP frame-ancestors via django-csp or your CSP package

If you have an existing CSP

If you already publish a CSP (see Content-Security-Policy fix), add frame-ancestors as a directive. The two pieces of the policy do not interfere:

Existing CSP plus frame-ancestors

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  frame-ancestors 'none';

Note: frame-ancestors does NOT inherit from default-src. If default-src is set but frame-ancestors is not, your pages remain framable.

Verify the fix

  • curl -sI https://yourdomain.tld | grep -iE 'x-frame-options|content-security-policy' should show one or both.
  • Open the page in a browser, open DevTools, and try to embed it in a test iframe (a local HTML file with <iframe src="https://yourdomain.tld"></iframe>). The iframe should be blank with a console error about X-Frame-Options or frame-ancestors.
  • Re-run the RedScore lookup. Pass requires the signal on every primary HTTPS host.

Common pitfalls

  • Using X-Frame-Options: ALLOW-FROM uri. Deprecated; not supported by modern browsers and not recognized by this check. Use CSP frame-ancestors instead.
  • Setting frame-ancestors via CSP but forgetting that the rest of CSP also needs to be valid. CSP applies as a whole; if your CSP has a syntax error, browsers may ignore the entire header including frame-ancestors.
  • Header set at one layer, overridden at another. CDN, load balancer, and origin can all set X-Frame-Options. Multiple X-Frame-Options headers are technically invalid and browsers may pick either. Set at one layer and confirm with curl.
  • Pages legitimately embedded by your own product. If your design system, marketing site, or status page is meant to be embedded by partners, deny will break their embeds. Use frame-ancestors with explicit hosts instead.
  • OAuth, SSO, and payment widgets that frame third-party pages. The third party's frame-ancestors policy controls whether you can embed them, not yours. Your frame-ancestors policy controls whether they can embed you.
  • PDF previews, document viewers, and analytics replay tools that frame your pages internally. If you intentionally frame your own pages from your own dashboard, sameorigin or self is the right choice, not deny.

What to do next

See how these recommendations apply to your site's current scan results.

Scan domain