From 51705056a578d2649a989bf14a056fbcfae58a6b Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 14 Oct 2026 13:34:47 +0200 Subject: feat(hub): replace reCAPTCHA with a self-hosted proof of work Sign-up and passphrase reset sent every visitor's address and browser details to Google, which needed a consent the sign-up form could not ask for freely. The hub now issues its own challenge (ALTCHA's scheme, signed with a key derived from the hub's private key, single-use, 30 minutes) and the page solves it with SubtleCrypto while the form is filled in. - GET /v1/users/captcha issues a challenge; 404 captcha_disabled when off. - [captcha] enabled / max_number replace site_key / secret_key, which are now ignored with a warning; MESHBAY_CAPTCHA_ENABLED replaces the old variables. - No Google origin left in the CSP of the hub, the desktop shell or the Android shell; captcha_site_key leaves /v1/hub/info. Co-Authored-By: Claude Opus 5.5 --- docs/MESHBAY_DESIGN.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'docs/MESHBAY_DESIGN.md') diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index f9ceaa2..2174879 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -2533,9 +2533,25 @@ Two things survive on purpose: the nodes refuse the account and close the groups' sessions now. A node that is offline misses it. -Registration is gated by a CAPTCHA whenever one is configured — **unconditionally**, +**Registration and the passphrase-reset request are gated by a proof of work** +(`captcha.py`), on unless `[captcha] enabled = false`, and **unconditionally** — not only when some other field is absent, or the real client's ordinary request -skips it. The desktop client renders the widget too. +skips it. The hub signs a challenge (the SHA-256 of a nonce, an expiry and a +number below `max_number`) with a key derived from its own private key; the page +finds the number while the form is being filled in and sends it with the form. +An answer is checked against the hub's signature, its expiry, and the answers +already spent, so one solve is one form. **No third party is involved**: no +script, cookie or visitor address leaves for anyone else, which is what keeps the +sign-up form free of a consent it could not ask for freely. What it buys is cost +per attempt, not a verdict on who is human; the bound that holds against a +caller with machines to spare is the mail budget (§7.4), since these two routes +are what make the hub send mail. + +**The sign-up form states what the hub keeps**, as a notice and never a checkbox: +the account rests on the terms of use and the connection log on a legal +obligation, so there is no consent to collect, and one that blocks sign-up would +be neither free nor withdrawable. The notice links to the legal pages of the hub +in use (`/legal/`), which are its operator's to write. **Sessions end three ways, all admin settings in hours** (`session.*`, §7.4): -- cgit v1.2.3