All 30 checks Page can be framed
Page can be framed
Updated
If nothing tells the browser otherwise, any other website can load your page inside an invisible frame and trick a visitor into clicking buttons on it, an attack known as clickjacking.
HTTP/2 200
content-type: text/html; charset=utf-8
# no x-frame-options
# no frame-ancestors in content-security-policyX-Frame-Options: DENYThe fix for your stack
Fix for: Next.js / Vercel
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async headers() {
return [
{
source: "/:path*",
headers: [{ key: "X-Frame-Options", value: "DENY" }],
},
];
},
};
export default nextConfig;How clickjacking works
An attacker builds a page that frames yours, makes the frame transparent, and puts a tempting button underneath it. The visitor thinks they are clicking the attacker's button, but the click lands on your page, where they may be signed in. The result can be an unwanted purchase, a changed setting, or a confirmed action. The browser is only doing what the page allowed, since by default any site may frame any other.
Two headers close the gap. X-Frame-Options is the older one, with the values DENY and SAMEORIGIN. The frame-ancestors directive of Content-Security-Policy is the modern one and supports a list of allowed parents. A marketing page with no logged-in state has little to lose, but the header is cheap, and a handover review will usually list its absence.
How Skewcheck detects it
Skewcheck reads the response headers of the page. If an x-frame-options header is present, the check passes without reading its value. It also passes if the content-security-policy header contains a frame-ancestors directive. If neither is found, the check fails with medium severity and removes 3 points from the score.
The value of the header is not validated, so an ALLOW-FROM value, which modern browsers ignore, would still pass. A frame-ancestors rule written in a <meta> tag also does not count, because browsers ignore that directive outside a real header. If your page is meant to be embedded elsewhere, such as a booking widget, use frame-ancestors with the specific sites allowed.
curl -sI https://example.com | grep -iE 'x-frame-options|frame-ancestors'Adding it
Send X-Frame-Options: DENY if nobody should frame the page, or SAMEORIGIN if your own site frames its own pages. To use the modern directive instead, add frame-ancestors 'none' or frame-ancestors 'self' to your Content-Security-Policy, and when you send both, browsers that understand the directive follow it. The per-stack tabs above show the header for nginx, Apache, Netlify, Cloudflare, Next.js and WordPress.
Webflow has its own switch for this in its publishing settings, which the Webflow tab above describes, and it sends SAMEORIGIN, which passes. Check every page type, not just the home page, because a login or checkout template sometimes sends its own headers and overrides the global rule. Test after the change: a site that embeds itself, for example in a preview pane, can break under DENY, and an embed that you did intend, like a widget on a partner site, needs an explicit allow rule.
Before and after
HTTP/2 200
content-type: text/html; charset=utf-8HTTP/2 200
content-type: text/html; charset=utf-8
x-frame-options: DENYCheck your page
Run all 30 checks on any URL. Free, no account.