You see a normal looking page. It says “Please double click to confirm you are human,” with a single button in the middle. You double click. By the time your second click lands, the button under your cursor is no longer the one you saw. It is a real “Authorize” button on a site where you are already logged in, and you just granted an app full access to your account. That is double clickjacking, a technique published by Paulos Yibelo in 2024. It revives an old idea that browsers were supposed to have killed, and it does so by abusing the gap between the two clicks of a double click.
The classic defense baseline
Old school clickjacking loaded a target site inside an invisible iframe on the attacker’s page. The attacker made the real frame transparent and lined up its sensitive button with whatever the user thought they were clicking. The click passed through to the framed site. The user believed they pressed “Play video.” They actually pressed “Delete account” or “Send money.”
Browsers and sites pushed back with three controls, and together they shut most of this down:
X-Frame-Options. A response header that tells the browser whether a page may be framed at all. Set it toDENYand no other site can put your page in aniframe.frame-ancestorsin Content Security Policy. The modern replacement.Content-Security-Policy: frame-ancestors 'none'does the same job with more control over who is allowed to frame you.- SameSite cookies. Marking a session cookie
SameSite=LaxorStrictmeans the browser does not attach it to many cross site requests, so a framed action often runs logged out and fails.
These work because they all assume the same thing: the attack needs the target page to be rendered inside a frame the attacker controls. Block the frame, block the attack.
Why double clickjacking sidesteps every one of them
Here is the move. Double clickjacking does not render the target inside a frame during the click. It puts the target in the top level window, the real tab, at the exact moment the second click happens. No frame is involved in the sensitive action, so frame busting headers have nothing to bite on.
The frame defenses guard against being embedded. They say nothing about what your top window shows between the first and second click. That timing gap is the whole attack.
X-Frame-Options and frame-ancestors only fire when a page is loaded as a sub frame. The target here loads as a normal navigation in a window the user already trusts. SameSite cookies do not help either, because the sensitive page is the user’s own first party session. The user is logged in, on the real domain, clicking a real button. Nothing looks cross site at all.
The timing trick, step by step
This is the conceptual flow, kept defensive so you can recognize it and design against it. The point is to see the shape, not to build it.
- Step one. The user lands on an attacker page with a believable reason to double click. “Double click to verify,” a fake captcha, a “double click to close this ad.”
- Step two. The first
mousedowntriggers JavaScript that opens a new top window pointed at the target’s sensitive page, an OAuth consent screen or an account action where the user is already authenticated. - Step three. In the same instant, the original page closes its own parent so the second click of the double click falls onto the now focused target window, right where its “Authorize” or “Confirm” button sits.
- Step four. The second click lands on the real button. The action completes. The decoy is gone before the user can read what happened.
The user only ever decided to double click a harmless prompt. The browser saw two ordinary clicks. The target site saw one legitimate click from a logged in user on its own page. Every layer behaved as designed, and the account still got compromised.
A sketch of the bait
The attacker side is mundane. The danger is in the window juggling that follows, not in clever markup. A stripped down decoy looks this innocent:
<!-- attacker decoy page, simplified -->
<div id="prompt">
<p>Please double click to verify you are human</p>
<button id="verify">Double click here</button>
</div>
<script>
// On the FIRST press, open the real target as a top window.
document.getElementById('verify')
.addEventListener('mousedown', openTarget);
function openTarget() {
// Target is the user's own authenticated consent/settings page.
window.open('https://app.example.com/oauth/authorize?...');
// The decoy then gets out of the way so the SECOND click
// of the same double click lands on the real button.
}
</script>
Notice what is not here: no iframe wrapping the target, no transparent overlay on top of app.example.com. That absence is exactly why the frame headers never trigger.
What gets targeted
The attack pays off wherever a single click does something important on a page where the victim is already signed in:
- OAuth consent screens. One “Authorize” click can hand a third party app read and write access to your email, files, or repos. This is the prize target, because the grant is durable and quiet.
- Account changes. “Confirm new email,” “add this device,” “disable two factor,” “make this user an admin.” Anything gated by one confirmation button.
- One click approvals. Payment confirmations, friend or follow grants, app install prompts, any flow that bragged about being a single click.
This sits in the same family as CSRF, where the attacker gets the victim’s browser to perform an action they did not intend. The difference is the path. CSRF forges the request in the background. Double clickjacking borrows a real, deliberate click from the user. It also differs from CORS misconfiguration, where the leak comes from a server reading cross origin responses it should not. Double clickjacking never needs to read anything. It only needs the click to land.
Defenses that actually fit this
Keep the frame headers, they still stop classic clickjacking. But they do not cover this case, so the real defenses live in how your sensitive actions are designed.
Make a single stray click not enough
- Require a non trivial gesture. A sensitive action should not complete on one bare click. Ask for a typed confirmation, a checkbox the user must tick first, or a drag, something a hijacked second click cannot satisfy on its own.
- Disable the button until the page settles. Yibelo’s proposed defense keeps the dangerous button inert until a short delay passes or a real interaction signal arrives, like the user moving the mouse or scrolling on that page. A button that wakes up only after genuine engagement cannot be hit by a click that arrived in the same millisecond the window opened.
Refuse to trust a fresh, unattended click
- Re authenticate for high impact actions. Prompt for the password, a passkey, or a code before granting OAuth scopes or changing security settings. A stolen click cannot type a password.
- Avoid one click authorize. For consent flows, add a deliberate second step that is not a single button, such as reviewing the exact scopes and confirming them. Friction here is the feature.
- Watch the window context. Yibelo also suggested browser side and page side signals, like noticing when a page was opened and immediately focused, and treating that as suspicious for sensitive actions. On your own pages you can check whether the window just received focus before honoring a critical click.
Keep the old protections too
None of this means dropping X-Frame-Options or frame-ancestors. Layer them. The frame headers close the original hole, and the gesture and re auth rules close the timing hole that double clickjacking opened. Each control covers a different assumption.
The assumption that breaks
Strip out the window tricks and one belief is left standing. Sites assume that a click on their own page, from their own logged in user, was meant for the thing under the cursor. Double clickjacking shows the second half of a double click can be redirected onto a button the user never saw. The fix is to stop treating any single click as proof of intent for actions that matter. This is the kind of flaw you find by asking what a flow assumes about its user’s intent, not by matching a known payload. An early signal we find encouraging: a frontier model drove the full methodology on its own and identified and verified real access control and injection issues in test applications it had not seen before. Read more on our about page.
Frequently asked questions
What is double clickjacking?
Double clickjacking is a technique published by Paulos Yibelo in 2024 that tricks a user into double clicking a harmless looking prompt. Between the first and second click, the attacker page swaps the top level window to a sensitive page where the user is already logged in, like an OAuth consent screen, so the second click lands on a real Authorize or Confirm button. The user only meant to double click a decoy, but they approved a real action on their own account.
Why do X-Frame-Options and frame-ancestors not stop it?
Those defenses only fire when a page is loaded inside a frame the attacker controls. Double clickjacking never renders the target in a frame during the click. It opens the target in the real top level window, so there is no sub frame for X-Frame-Options or the Content Security Policy frame-ancestors directive to block. SameSite cookies do not help either, because the sensitive page is the user’s own first party session and nothing looks cross site.
What does double clickjacking usually target?
It targets any action that completes with a single click on a page where the victim is already signed in. The prize target is OAuth consent screens, where one Authorize click can grant a third party app durable access to email, files, or repositories. It also hits account changes like confirming a new email, disabling two factor, or promoting a user to admin, plus one click approvals such as payments and app installs.
How do you defend against double clickjacking?
Stop treating a single click as proof of intent for important actions. Require a non trivial gesture such as a typed confirmation or a ticked checkbox. Yibelo’s proposed defense is to keep sensitive buttons disabled until a short delay passes or a real interaction signal arrives, so a click that lands the instant a window opens does nothing. Re authenticate before granting OAuth scopes or changing security settings, avoid one click authorize, and keep the frame headers in place as a separate layer.
