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 --- packages/meshbay-client/src/main.js | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) (limited to 'packages/meshbay-client') diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js index 33388e8..8845b7b 100644 --- a/packages/meshbay-client/src/main.js +++ b/packages/meshbay-client/src/main.js @@ -115,29 +115,16 @@ function nodeLogHint() { // The hub is reachable under connect-src, for its API and its signaling socket. // It is deliberately absent from script-src: nothing it returns is executed, // which is the whole reason this application exists (T3). -// -// The one exception is reCAPTCHA, used to gate sign-up (and password reset) the -// same way it gates them in the browser. Its script comes from www.google.com, -// its challenge is a www.google.com iframe, and its assets sit on -// www.gstatic.com. These two hosts — and only these two — are allowed under -// `script-src`, `frame-src` and `img-src` for that purpose. It is a real, if -// small, dent in "no third-party code runs here": Google's reCAPTCHA script -// executes in the renderer. It is accepted deliberately so a native sign-up is -// gated like a web one without asking the user to do anything extra, and it is -// the *same* dependency the hub-served SPA already carries. If sign-up ever -// moves to a proof-of-work challenge, delete RECAPTCHA_SRC and the three -// directives that spread it, and the widget in auth-page.js with them. -const RECAPTCHA_SRC = 'https://www.google.com https://www.gstatic.com'; const CSP = [ "default-src 'none'", - `script-src 'self' 'wasm-unsafe-eval' ${RECAPTCHA_SRC}`, + "script-src 'self' 'wasm-unsafe-eval'", "style-src 'self' 'unsafe-inline'", - `img-src 'self' data: blob: ${RECAPTCHA_SRC}`, + "img-src 'self' data: blob:", "media-src 'self' blob:", "font-src 'self'", // Every hub call leaves from this process, so the page connects to nothing - // but its own files and reCAPTCHA; see the hub's webapp.CSP. - `connect-src 'self' ${RECAPTCHA_SRC}`, + // but its own files; see the hub's webapp.CSP. + "connect-src 'self'", "worker-src 'self'", // `blob:` here and in `frame-src` are one thing, not two: the PDF preview. // @@ -157,7 +144,7 @@ const CSP = [ // is PDFium parsing bytes that came from a node — exactly what a browser // does with the same file. "object-src blob:", - `frame-src blob: ${RECAPTCHA_SRC}`, + "frame-src blob:", "frame-ancestors 'none'", "base-uri 'none'", "form-action 'none'", -- cgit v1.2.3