Requests, files, and servers
Stop clickjacking
Control which sites may frame a page and add confirmation for sensitive actions that should never be triggered through a disguised interface.
Clickjacking places your real page inside a frame on a hostile site, then overlays a decoy so the user clicks a genuine control they cannot see. The browser reports a legitimate authenticated click, because that is exactly what happened.
A transparent frame places the real “Delete project” button under a fake “Play” button. The click is genuine from the application’s perspective.
Control who may frame you
The primary defense is the CSP frame-ancestors directive, which lists the origins allowed to embed the page. Keep the older X-Frame-Options header too, for clients that predate CSP.
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
If one partner legitimately embeds a dashboard, name that origin instead of blocking everything.
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com
You can set the header from the application for the routes that need it.
res.set('Content-Security-Policy', "frame-ancestors 'none'")
For destructive operations, add a second barrier that a hidden click cannot satisfy: require typed confirmation or recent authentication, so a single disguised click is never enough.
Blocking every frame can break a product that intentionally embeds a dashboard for one partner. Use a narrow frame-ancestors allowlist when embedding is required.
Build a local attacker page that frames the sensitive route and show the attack before the header change. Afterward, capture the browser refusal and confirm any approved embedding origin still works.
Lesson completed