Repository navigation
reCAPTCHA v2 is no longer detected (and bought) as v3 - #8
Merged
Merged
Conversation
A v2 widget rendered from a `.g-recaptcha` element loads `api.js` with no
`render` parameter. The live detector had no rule for "no size, no render",
so both the checkbox and the invisible form fell through to its last branch
and came out as v3 — and a v3 task bought for a v2 widget is refused by the
solver (ERROR_CAPTCHA_UNSOLVABLE in the report that found this).
v3 cannot be loaded without `render=<sitekey>`. So a loader seen without
one is now v2, and the badge decides invisible vs checkbox — consulted only
after v3 is ruled out, because v3 draws `.grecaptcha-badge` too. The
discovery script now records whether the api.js/enterprise.js loader was on
the page at all, so a page whose loader was not seen keeps the old
fallback instead of being read as implicit v2.
The HTML-only detector had the same mistake in another form: it labelled
`grecaptcha.render` — v2's explicit-render call — as v3. On Google's demos
the v2 explicit page calls render and never execute, the v3 page calls
execute and never render.
Measured 2026-10-22 on Google's own demo pages:
- v2 checkbox (implicit), v2 invisible (two demos), v2 explicit and v3 all
classify correctly in Playwright, Selenium and pyppeteer;
- with the corrected kind, the v2 checkbox and v2 invisible demos were both
solved (36 s, 15 s) and accepted by Google ("Verification Success"), for
$0.00299 in total.
Tests: `captures/recaptcha_discovery.json` holds what the discovery script
returned on those pages (sitekeys replaced by a placeholder) and pins the
kind per page, plus the badge-on-v3 and no-loader cases and both static
rules. `.gitignore` ignored every `*.json`, which would have kept that
fixture out of git and made its check skip in CI; JSON captures are now
committable, and a new check fails on any capture git would not commit.
Five controls, each planted in a disposable copy and caught by its named
check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VrpkXSoXyZMGV5nqF9JV6P
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A reCAPTCHA v2 widget rendered from a
.g-recaptchaelement loadsapi.jswith norenderparameter. The live detector had no rule for "no size, no render", so both the checkbox and the invisible form fell to its last branch and were classified v3. A v3 task bought for a v2 widget is refused by the solver (ERROR_CAPTCHA_UNSOLVABLEin the manual test that reported this).The report saw the invisible demo come out as a plain checkbox; measured here it came out as v3, the same as the checkbox demo. Either way it was the wrong task type.
The HTML-only detector had the same mistake in another place: it labelled
grecaptcha.render, which is v2's explicit-render call, as v3.Behaviour after
Classification, strongest signal first:
render=<sitekey>render=explicitrenderThe badge is checked only after v3 has been ruled out, because v3 draws
.grecaptcha-badgetoo. The discovery script now records whether theapi.js/enterprise.jsloader is on the page at all (loaderSeen); without it, "no render" means nothing.Static detector:
data-sitekey+grecaptcha.render→ v2.Evidence (2026-10-22, Google's own demo pages)
render=explicitrender=<key>grecaptcha.renderappears only on the v2 explicit page andgrecaptcha.executeonly on the v3 page.Tests
captures/recaptcha_discovery.json: what the discovery script returned on those pages, with sitekeys replaced bydemo-sitekey(equality withrenderpreserved, which is all the classifier reads). Pins the kind per page, plus "v3 has a badge and stays v3", "no loader seen falls back" and both static rules..gitignoreignored every*.json, so this fixture would never have reached git and its check would have skipped in CI, which reads as green.!captures/*.jsonadded, plus a check that fails on any file incaptures/that git would not commit.🤖 Generated with Claude Code
https://claude.ai/code/session_01VrpkXSoXyZMGV5nqF9JV6P