Add Turnstile and ALTCHA captcha backends alongside reCAPTCHA - #384
Merged
Merged
Conversation
Spam is getting through the reCAPTCHA v2 checkbox on the OSL and OpenPOWER forms, and Google has moved classic keys into Google Cloud with a 10,000 verifications a month free tier. The old check also only looked at "success", passed the visitor IP under the wrong parameter name, and raised a 500 on a missing field or a network error. Replace it with a small provider layer so a form chooses its captcha by the field it posts and each site can migrate on its own schedule. - Add captcha.py with Cloudflare Turnstile (cf-turnstile-response), ALTCHA (altcha) and Google reCAPTCHA (g-recaptcha-response) verifiers - Accept a provider only when its secret is set, and fail closed on a missing field, a transport error, an unexpected response shape or an unusable payload - Serve signed, expiring ALTCHA challenges from /altcha with CORS headers so the widget on another origin can fetch them without any third-party service - Remember redeemed ALTCHA nonces per worker, capped, so a solved challenge cannot simply be sent a second time - Refuse a hosted verdict carrying an error code alongside success, which is how reCAPTCHA reports that it has failed open on a spent quota - Enforce CAPTCHA_ALLOWED_HOSTNAMES and, for reCAPTCHA v3 keys, a minimum score, so a token minted elsewhere with the same key pair is refused - Check the captcha before the duplicate check, which records the submission and would otherwise reject the sender's corrected retry as a duplicate - Leave captcha fields out of the duplicate hash, so duplicates are caught again, and out of the debug log, so tokens stay out of the logs - Validate settings at startup and refuse to start on an unusable ALTCHA algorithm, a hostname list that names no hosts, or no provider at all - Add TRUSTED_PROXY_COUNT so the verifier is told the sender's address rather than HAProxy's, and log which providers a process came up with - Rename error 6 to "Invalid Captcha" and document the widget, settings and test credentials for each provider - Cover the verifiers, the challenge endpoint, replay, check ordering and each fail-closed path, and move the __main__ guard so make tests collects them https://docs.cloud.google.com/recaptcha/docs/migrate-recaptcha https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ https://github.com/altcha-org/altcha-lib-py Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Lance Albertson <lance@osuosl.org>
ramereth
force-pushed
the
captcha-providers
branch
from
September 18, 2026 18:54
f24f555 to
efb2324
Compare
Getting a Turnstile site key means filling in a Cloudflare dashboard form whose fields are not self-explanatory, and two of them are easy to get wrong in ways that are hard to debug later. Write down the intended value for each one. - Say which hostname belongs in the widget, the website's rather than Formsender's, and that a hostname already covers its subdomains - Recommend Managed mode, because the other two never escalate for a risky visitor, and leave pre-clearance off on a site Cloudflare does not proxy - Note that CAPTCHA_ALLOWED_HOSTNAMES is matched exactly, unlike the widget's own hostname list, so every hostname serving a form has to be listed - Point the form setup page at the new section for where a site key comes from https://developers.cloudflare.com/turnstile/additional-configuration/hostname-management/ https://developers.cloudflare.com/turnstile/concepts/pre-clearance-support/ Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Lance Albertson <lance@osuosl.org>
ramereth
force-pushed
the
captcha-providers
branch
from
September 18, 2026 18:54
efb2324 to
12cf3e0
Compare
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.
Replaces the Google reCAPTCHA v2 checkbox with a pluggable captcha layer, so the OSL and OpenPOWER Foundation forms can move to Cloudflare Turnstile. It also adds ALTCHA, a self-hosted proof-of-work backend that involves no third party at all. reCAPTCHA keeps working throughout, so each site can migrate on its own schedule.
Spam is getting through the v2 checkbox, which is the variety solver farms target. Google has also moved classic reCAPTCHA keys into Google Cloud, where the free allowance is 10,000 verifications a month per project, and exceeding it without billing attached starts failing requests. The old check had problems of its own. It looked only at
success, ignored the hostname the token was solved on, sent the visitor's address under a parameter name neither provider reads, and raised an HTTP 500 instead of rejecting the submission when the field was missing or the network call failed.The new
captcha.pyis the place to start reading. It picks a backend from whichever response field the form posted and accepts only backends whose secret is configured, which is what lets one instance serve several sites mid-migration. Then readrequest_handler.pyfor the order the checks now run in and for the/altchachallenge endpoint. Turnstile is the recommended replacement because it is free with no request cap and keeps a hosted risk engine behind the widget. ALTCHA is verified locally with no external service, which prices out bulk automation but not a determined operator, so it is the fallback rather than the default.captcha.pywith Cloudflare Turnstile, ALTCHA and Google reCAPTCHA verifiers, selected per submission by the response field the form posted/altchaendpoint with the CORS headers a widget on another origin needs to fetch themsuccess, which is how reCAPTCHA reports that it has failed open on a spent quotaCAPTCHA_ALLOWED_HOSTNAMES, and a minimum score for reCAPTCHA v3 keys, so a token minted on another site sharing the key pair is refusedTRUSTED_PROXY_COUNTso the captcha provider is told the sender's address rather than HAProxy'sInvalid Captchaand document the widget, the settings and the test credentials for each providerDeploy this before changing either website, because the forms keep working on reCAPTCHA until their widget is swapped.
The website changes and the
osl-appChef recipe are deliberately not in this PR. Both containers needTURNSTILE_SECRETin their data bags andTRUSTED_PROXY_COUNT=1set before the widgets change, andRECAPTCHA_SECRETshould be unset once the last form has migrated, because a submission picks its own provider from those configured.References: