v4.0.18 — sync upstream Calibre-Web 2026-04 fix wave
Suggested release title:
v4.0.18 — sync upstream Calibre-Web fixes (8 security + 4 stability)
How to ship this release
After PR #63 merges:
cd repo
git checkout main && git pull
git tag -a v4.0.18 -m "v4.0.18 — sync upstream Calibre-Web fixes"
git push origin v4.0.18
gh release create v4.0.18 --title "v4.0.18 — sync upstream Calibre-Web fixes (8 security + 4 stability)" --notes-file ../notes/release-v4.0.18-draft.mdPublishing the GitHub release fires .github/workflows/docker-image-build-release.yml (on: release: types: [published]), which builds the multi-arch image and pushes to ghcr.io/new-usemame/calibre-web-nextgen:v4.0.18 and :latest.
Release body (paste below the divider into gh release create)
This release pulls in the April 2026 fix wave from the original Calibre-Web upstream (janeczku/calibre-web). Most of these are security fixes by @jvoisin that haven't shipped to PyPI yet — the upstream release tag is still 0.6.26 from February.
Get it
docker pull ghcr.io/new-usemame/calibre-web-nextgen:v4.0.18
Or update an existing compose stack — same volume layout, no migration needed.
Security fixes
- XXE in EPUB / FB2 parsers — lxml
fromstring()now uses a parser withresolve_entities=False, no_network=True. A crafted EPUB or FB2 could otherwise pull arbitrary local files or out-of-band URLs during metadata import. (cps/epub.py,cps/epub_helper.py,cps/fb2.py) - LDAP injection in login — usernames are RFC 4515-escaped before being placed into the LDAP filter. (
cps/services/simpleldap.py) - OAuth account takeover — when an OAuth identity (Github / Google / generic OIDC) was already bound to user A, a logged-in user B who completed an OAuth callback for that identity would silently get logged out and into A's account. Now rejected with a clear message. (
cps/oauth_bb.py) /show/<book_id>access bypass — the raw book-bytes endpoint applied no per-user content-hiding filters; any logged-in viewer could fetch any book by ID. Now goes throughget_filtered_booklike the rest of the UI. (cps/web.py)debug_infoadmin endpoint leaked tokens / secrets — the dictionary export filtered keys containingapi, but missedtokenandsecret(OAuth tokens, OIDC client secrets, Kobo sync tokens). Now all three are filtered. (cps/config_sql.py)- Shelf reorder permission gap — POSTing a new book order to a public shelf required only view permission, not edit. Now requires edit, matching every other shelf-mutation endpoint. (
cps/shelf.py) - Encryption key file world-readable on creation — the
.keyFernet key (which protects every encrypted column — SMTP password, LDAP service password, OIDC client secret) inherited the process umask on first create. Nowchmod 0600immediately. (cps/config_sql.py) - Stack traces leaked to non-admin 500 pages — the 500 page rendered
traceback.format_exc()to whoever triggered it, including unauthenticated users. Now logged server-side; only rendered to authenticated admins. (cps/error_handler.py)
Stability fixes
- OPDS sync now sees metadata edits —
atom:updatedreturnedBooks.timestamp(date added, never changes), so OPDS clients couldn't tell when a book's cover or title had been edited. Now returnslast_modifiedwith fallback totimestamp. (cps/db.py) /tmp/calibre_webno longer fills the host disk — when embed-metadata + kepubify or calibredb-export is enabled, every download staged a copy and never cleaned up. Bulk OPDS / Kobo syncs would silently 404 once disk filled. Now cleaned up viaafter_this_requesthook. (cps/helper.py)do_calibre_exportskips cover sibling —calibredb exportwrites<uuid>.jpgnext to<uuid>.<format>by default; calibre-web never reads it. Pass--dont-save-coveralongside the existing--dont-write-opf. (cps/embed_helper.py)- Google Drive change-poll comparison fixed — the metadata.db md5 comparison was between a string and a hash object (always not-equal), causing every gdrive change notification to re-trigger the metadata.db backup-and-redownload sequence. (
cps/gdrive.py)
Already in v4.0.17 (mentioned for completeness)
Two upstream fixes from this wave were already in our fork independently:
- Kobo IDOR on
/kobo_auth/generate_auth_token/<user_id>and/kobo_auth/deleteauthtoken/<user_id>— fixed in our PR #18 back at v4.0.7. - ComicVine / Douban None-iteration crash —
cps/search_metadata.pyalready coalesces provider failures withor [].
Credits
Original fixes by @jvoisin (security wave) and @haraldpdl (OPDS / tmp / cover-sibling / s6) on janeczku/calibre-web. Backported as clean cherry-picks with each upstream commit linked in CHANGES-vs-upstream.md. If any of these patches eventually lands on crocodilestick/Calibre-Web-Automated or in a new 0.6.27 release of calibreweb, we'll drop our copy on the next sync.
Tests
21 new unit tests in tests/unit/test_janeczku_backports.py pin the testable contracts (XXE parser config, LDAP escape, debug_info redaction, atom_timestamp priority, embed-helper argv, key-file permissions). Flask-app-bound fixes are covered in the integration suite.