Missing X-Frame-Options or a permissive frame-ancestors directive is reachable - confirming a sensitive interaction is capturable at a predictable position is the step that decides severity.
Clickjacking hides a sensitive action beneath a transparent iframe. A user believes they are clicking a decoy button; the browser delivers the click to the framed target instead. Missing X-Frame-Options or a permissive frame-ancestors directive is reachability - confirming that a specific sensitive interaction is actually capturable is the step that decides whether this is a cleanup item or an incident.
Every confirmed clickjacking finding rests on two independent facts. The first is embeddability: the target origin will load inside a cross-origin frame. The second is a capturable sensitive action: a button, a link, or a form submit on the embedded page sits at a predictable, stable position within a fixed viewport, so a decoy page can position a transparent overlay over it precisely. Neither fact alone is the finding. A page embeddable only from the same origin blocks an external attacker. A page that can be framed but exposes only read-only content carries no escalation path worth a critical rating.
Embeddability is a header classification problem. The probe sends a request to the target page and classifies the framing policy from the response. Two headers govern the browser's decision; when both are present, modern browsers give CSP frame-ancestors precedence over X-Frame-Options. Checking only one header when both are set produces a false result in either direction.
# Step 1 - request the target page as a browser would GET /account/delete HTTP/1.1 Host: app.example Sec-Fetch-Mode: navigate # Step 2 - classify the framing policy from response headers HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 # REACHABLE - no framing protection present # X-Frame-Options header: absent # Content-Security-Policy header: absent # contrast: protected response Content-Security-Policy: frame-ancestors 'none' # blocks all framing X-Frame-Options: DENY # legacy reinforcement # both present - frame-ancestors wins in modern browsers
A frame-ancestors value of 'self' blocks a cross-origin attack but remains relevant if a stored XSS exists on the same origin. A wildcard frame-ancestors * is as permissive as no header at all. The probe records the exact header values as the confirming artifact rather than a binary pass/fail, because an auditor reviewing severity needs to see the literal policy string, not a summary.
Once embeddability is confirmed, the scanner identifies the sensitive interactions available on the framed page at a stable viewport. Element position is read from the page's layout at a fixed resolution - the confirming artifact is the bounding box of the sensitive element, not a screenshot that triggers the action. No click is simulated against a production session and no irreversible action is taken during the probe.
The capturable action determines severity. A page that only renders read-only profile data is low regardless of embeddability. A page with a payment-confirmation button or an account-deletion trigger at a predictable position is critical. The table below shows how the rating scales with the action the overlay can force:
| Capturable action | Severity | Reason |
|---|---|---|
| Read-only content display | Info / Low | No state change achievable via a forced click |
| Follow / like / share action | Low | Low-impact social manipulation; no account takeover path |
| Email or password change form | High | Account takeover via credential substitution |
| MFA disable or trusted-device add | High | Weakens subsequent account protection |
| Fund transfer or purchase confirmation | Critical | Direct financial loss achievable via single overlay click |
| Account deletion (irreversible) | Critical | Irreversible destructive action, one interaction |
The fix is a server-side header that restricts which origins may embed the page in a frame. CSP frame-ancestors is the current standard; X-Frame-Options remains useful as a defense-in-depth fallback for older browsers that do not support CSP. Sending only X-Frame-Options leaves a gap in Safari versions and some embedded WebViews that process the CSP directive but have inconsistent X-Frame-Options handling. Sending only CSP leaves older IE and some legacy proxies unprotected. Both headers, consistently, is the safe baseline.
# Recommended: CSP frame-ancestors with X-Frame-Options fallback add_header Content-Security-Policy "frame-ancestors 'none';" always; add_header X-Frame-Options "DENY" always; # If same-origin embedding is needed (e.g. a dashboard widget): add_header Content-Security-Policy "frame-ancestors 'self';" always; add_header X-Frame-Options "SAMEORIGIN" always; # Never: wildcard frame-ancestors or missing both headers # frame-ancestors * == no protection for cross-origin clickjacking
Re-authentication gates and CSRF tokens on destructive endpoints reduce impact but do not close the framing vector. A user who is already authenticated and whose browser auto-submits session cookies to a framed origin can be walked through a multi-step flow by a sequence of overlays - each step is a separate click, and re-auth prompts disappear if the session is live. The header is the only reliable control.
Clickjacking findings fail quietly in both directions without a consistent two-fact confirmation. Reporting embeddability without a capturable action over-claims; missing a permissive wildcard directive because only one header was checked under-reports. The confirming artifact - the exact header values plus the element bounding box on the sensitive endpoint - is what turns a header observation into a rated, evidence-backed finding.