What is Clickjacking?
Picture this: a user visits what looks like a "Claim Your Prize" page. They click a big green button. But underneath that button, hidden in a transparent iframe, is your app's "Delete My Account" button. The user just deleted their account without ever seeing it.
Clickjacking works because browsers allow any site to embed any other site in an <iframe> by default. Unless your server explicitly says "don't let other sites frame me," your entire app is fair game. The attacker controls the positioning, opacity, and z-index — your users never know what they're actually clicking.
The fix is straightforward: send the right HTTP headers. But that's exactly the kind of thing AI code generators skip, because headers aren't features — they're invisible guardrails that only matter when someone is trying to attack you.
How does Clickjacking work?
The attack relies on your server not sending frame-restriction headers. Without X-Frame-Options or a Content-Security-Policy with frame-ancestors, any website can load your app in an iframe.
Here's what a typical Next.js app looks like without frame protection:
// next.config.js
module.exports = {
// No security headers configured.
// Any site can embed this app in an iframe.
};
// Or a custom server with no headers:
app.get('/', (req, res) => {
// No X-Frame-Options header
// No Content-Security-Policy header
res.render('index');
});// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'Content-Security-Policy',
value: "frame-ancestors 'none';",
},
],
},
];
},
};
// For Express:
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('Content-Security-Policy', "frame-ancestors 'self';");
next();
});Why do AI tools generate Clickjacking vulnerabilities?
AI code generators build what you ask for — pages, forms, APIs. Security headers are never part of the prompt, so they're never part of the output.
- Headers are invisible infrastructure. Nobody prompts "add clickjacking protection." The AI builds the feature, not the security envelope around it.
- Default frameworks ship without frame restrictions. Next.js, Express, and most frameworks don't set X-Frame-Options out of the box. The AI follows the framework defaults.
- Training data rarely includes security headers. Tutorials and example repos focus on making the app work. Response headers are a footnote at best.
Clickjacking protection is a single header. But because it lives in server config — not in component code — AI tools consistently miss it. It's the kind of gap that's trivial to fix but invisible until someone exploits it.
Common Clickjacking patterns
No X-Frame-Options header at all
The most common case. The server sends no frame restriction headers, so any site can iframe the app.
Header set on some routes but not others
API routes have headers but the main app pages don't — or vice versa. Inconsistent coverage leaves gaps.
CSP without frame-ancestors
The app has a Content-Security-Policy header, but it only covers script-src or default-src — frame-ancestors is missing entirely.
ALLOW-FROM with a wildcard or stale domain
Legacy X-Frame-Options: ALLOW-FROM pointing to a domain the team no longer controls, or using a pattern that modern browsers ignore.
How Flowpatrol detects Clickjacking
Flowpatrol checks your app's response headers the way a browser would — by actually loading your pages and inspecting what comes back:
- 1Fetches every page and API route and inspects the response headers on each one.
- 2Checks for X-Frame-Options — looks for DENY or SAMEORIGIN. Flags missing or misconfigured values.
- 3Checks Content-Security-Policy — verifies that frame-ancestors is present and doesn't allow wildcard origins.
- 4Reports inconsistencies — catches cases where some routes are protected but others are wide open.
One missing header is all it takes. Flowpatrol finds the gap before an attacker builds a page around it.
Related terms
Check your app for clickjacking.
Flowpatrol scans your response headers across every route. One URL, five minutes, and you know exactly where you stand.
Try it free