summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-14 13:34:47 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-14 13:34:47 +0200
commit51705056a578d2649a989bf14a056fbcfae58a6b (patch)
treedfd078aba518427387fca29b9d43c3723b030008 /packages/meshbay-hub/src/meshbay_hub/api/webapp.py
parentdaec4bc6a2a9f9e038e5269586b06018ff327a27 (diff)
downloadmeshbay-51705056a578d2649a989bf14a056fbcfae58a6b.tar.gz
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 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/webapp.py')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/webapp.py23
1 files changed, 11 insertions, 12 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
index badbdda..adbd522 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
@@ -82,21 +82,19 @@ _NO_STORE = {"Cache-Control": "no-store"}
# `<script>`. `'wasm-unsafe-eval'` is required for the Argon2id WASM. The hub's
# own origin is deliberately absent from `script-src`: a response it returns is
# never executed, which is the point of T3.
-_RECAPTCHA_SRC = "https://www.google.com https://www.gstatic.com"
CSP = "; ".join([
"default-src 'none'",
- f"script-src 'self' 'wasm-unsafe-eval' {_RECAPTCHA_SRC}",
+ "script-src 'self' 'wasm-unsafe-eval'",
"style-src 'self' 'unsafe-inline'",
- f"img-src 'self' data: blob: {_RECAPTCHA_SRC}",
+ "img-src 'self' data: blob:",
"media-src 'self' blob:",
"font-src 'self'",
# The page talks to its own origin and nothing else — the hub, the node
- # arrives over WebRTC, which this directive does not govern. reCAPTCHA's
- # two origins are the only others, kept so its script may call home from
- # this page; the register and reset views were driven in Chrome and Firefox
- # against Google's test keys with this line and reported no violation.
- # `https: wss:` let an injected script post anything anywhere.
- f"connect-src 'self' {_RECAPTCHA_SRC}",
+ # arrives over WebRTC, which this directive does not govern. No third
+ # party has an origin here: the sign-up check is a proof of work the hub
+ # issues itself (captcha.py). `https: wss:` let an injected script post
+ # anything anywhere.
+ "connect-src 'self'",
"worker-src 'self'",
# `object-src` exists for one thing, and `frame-src`'s `blob:` for the same
# thing: previewing a PDF without writing it anywhere.
@@ -123,9 +121,10 @@ CSP = "; ".join([
# response it is holding. Without it Chrome refuses the frame, the worker is
# never asked, and the page waits out its timeout for a download that cannot
# happen — on Firefox and Safari that is the *only* way to write a large
- # file to disk, so the whole path was dead. Added when reCAPTCHA needed a
- # frame, which is why nobody connected the two.
- f"frame-src 'self' blob: {_RECAPTCHA_SRC}",
+ # file to disk, so the whole path was dead. It was missing because the
+ # directive had been written for a third-party captcha frame, and nobody
+ # connected the two.
+ "frame-src 'self' blob:",
# `'self'`, not `'none'`, and the difference is one same-origin iframe.
#
# The threat frame-ancestors answers is clickjacking: a *foreign* page