github new-usemame/Calibre-Web-NextGen v4.0.18
v4.0.18 — sync upstream Calibre-Web fixes (8 security + 4 stability)

latest releases: v4.0.168, v4.0.167, v4.0.166...
one month ago

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.md

Publishing 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 with resolve_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 through get_filtered_book like the rest of the UI. (cps/web.py)
  • debug_info admin endpoint leaked tokens / secrets — the dictionary export filtered keys containing api, but missed token and secret (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 .key Fernet key (which protects every encrypted column — SMTP password, LDAP service password, OIDC client secret) inherited the process umask on first create. Now chmod 0600 immediately. (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 editsatom:updated returned Books.timestamp (date added, never changes), so OPDS clients couldn't tell when a book's cover or title had been edited. Now returns last_modified with fallback to timestamp. (cps/db.py)
  • /tmp/calibre_web no 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 via after_this_request hook. (cps/helper.py)
  • do_calibre_export skips cover siblingcalibredb export writes <uuid>.jpg next to <uuid>.<format> by default; calibre-web never reads it. Pass --dont-save-cover alongside 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.py already coalesces provider failures with or [].

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.

Don't miss a new Calibre-Web-NextGen release

NewReleases is sending notifications on new releases.