What's new
If you use the Magic Link → Authorise New Device flow (/remote/login), the token in that link is now generated from 128 bits of randomness instead of 32. There is no user-visible change to how the flow works — the link still expires in 10 minutes, the QR code still works, the same Login button on your other device still completes the pairing. The link is just no longer brute-forceable.
docker pull ghcr.io/new-usemame/calibre-web-nextgen:v4.0.69
(or :latest)
Why this mattered
The magic-link token was 4 bytes of os.urandom — 32 bits, 8 hex characters. /ajax/verify_token (the endpoint your other device polls while waiting for you to click the link) has no rate limiting and the token is valid for 10 minutes. At even modest request rates an attacker can probe a meaningful slice of the token space during the validity window, and with multiple concurrent sessions or by triggering you to initiate remote login, the attack becomes feasible.
Bumping to urandom(16) (128 bits, 32 hex characters) puts the token space out of brute-force reach. This matches the Kobo permanent-device token (cps/kobo_auth.py) which already used 16 bytes on the same database column, so the change is schema-safe.
Credits
Thanks @jvoisin for the patch (janeczku/calibre-web#3623). This is the continuation of the April 2026 security wave Julien sent upstream — v4.0.67 picked up his serve_book defense-in-depth headers (#3624), and the earlier security-wave fixes shipped between v4.0.36 and v4.0.66.
Verification
Three regression tests pin the new behavior: an AST source-pin so a future refactor can't silently reintroduce a short width, a behavioral check that fresh tokens are 32 lowercase hex chars, and a uniqueness sanity. Full magic-link round trip (/remote/login → /verify/<token> → /ajax/verify_token) exercised in a local container and via Playwright on the UI; container error logs scanned for fresh failures, none surfaced.