github vavallee/bindery v1.40.1

3 hours ago

Security fixes from a full review of Bindery, import matching fixes, and one upgrade step for login free installs

This release fixes the findings of a security review of the whole codebase. The most serious let a non admin account read your indexer and Prowlarr API keys through the grab endpoint, and let a download containing symlinks expose files such as Bindery's own database through the book download button. Logout now ends the session on the server, login limits hold under a burst, credentials stay out of responses, logs and redirects, a malicious ebook or audio file can no longer run Bindery out of memory, and the login free modes are protected against DNS rebinding. Most of these matter most on installs with more than one account or with Bindery reachable from other machines, so updating is recommended for everyone. Import no longer offers or attaches a book by the wrong author when a folder holds another author's audio files, a tiny notes file is no longer taken for a book, a download in a language your profile does not allow is rejected and searched again instead of relabelling the book, and an import can no longer report success while tracking nothing or overwrite a file another book holds. Torrent grabs now honour the global download path remap, which changes the folder some clients are sent (see the upgrade notes).

If you run the local-only or disabled auth mode and reach Bindery by a domain name, read the upgrade note below before updating. This release adds two database migrations, both additive, which run on their own at startup.

Upgrade notes

  • Using local-only or disabled mode with a domain name? Set BINDERY_ALLOWED_HOSTS before you upgrade (#2959). If you open Bindery by a name such as books.example.com, a Tailscale ts.net name, nas.fritz.box or a Kubernetes name like bindery.media.svc, add it to BINDERY_ALLOWED_HOSTS (comma separated, *.example.com covers a domain you own). Otherwise that name will ask you to sign in after the upgrade. Never wildcard a shared domain such as duckdns.org. Setting it to * turns the protection off and is not recommended. The default enabled mode needs nothing.
  • :latest now means the latest release, and nothing else (#2956). The image tag used to move on every merge to main as well, so anyone running :latest could pick up unreleased code. It now moves only when a release is tagged. If you want the head of main, use the new :edge tag.
  • If you run Bindery with non admin accounts, consider rotating your indexer and Prowlarr API keys (#2960), since earlier versions let those accounts read them.
  • Using BINDERY_DOWNLOAD_PATH_REMAP with rTorrent, or qBittorrent without a category? Run Diagnose after you upgrade (#2665). The folder Bindery sends those clients changes to the global remap's client side. Run Diagnose on each such client after upgrading. If the client mounts Bindery's download folder at the same path, because the global remap is meant for a different client, give it an identity path remap such as /downloads:/downloads so it keeps being sent Bindery's own folder.

Security

  • Grabbing is limited to real search results for user accounts (#2960). A non admin account could make Bindery fetch any URL through the grab endpoint, with an indexer key attached when the host matched an indexer, and read part of the answer back in the error. That exposed indexer and Prowlarr settings, including their API keys, and let a user read other services on your network. User accounts now grab only releases their recent searches returned, and Bindery sends the download link it recorded rather than the one in the request. With tenancy on, a user can also no longer grab into another user's book, and a release held by another user's queue entry is refused unless that entry failed more than six hours ago, the same wait the automatic search observes. A claimed entry starts over clean and keeps nothing of the other user's download.
  • Symlinks from a download can no longer reach your library or leak files (#2961). A move mode audiobook import now copies only the regular files of a download that contains symlinks or special files, so the links never land in the library, and an audiobook download folder that is itself a symlink is refused in every import mode. Book downloads, audiobook zips, OPDS and the Calibre bridge refuse to serve a book file that is a symlink, and Library Scan and the Audiobookshelf import no longer attach one to a book. Linked folders keep working, including a library folder reached through a symlink or bind mount and an author folder linked to another disk. A Calibre bridge delivery whose file is temporarily unreadable now stays queued instead of being skipped. Filenames containing a backslash or quote are now escaped properly in download headers.
  • Login free modes now check the host name (#2959). In local-only and disabled auth mode a web page could reach Bindery through your browser by pointing its own DNS name at Bindery's address, then act as the administrator. Those modes, and the OPDS feed in them, now skip the login only for IP addresses, localhost, single label names like bindery, names under .local, .lan, .home.arpa, .internal, .localdomain or .localhost, and names listed in BINDERY_ALLOWED_HOSTS. Any other name gets the login page with a note explaining why. The default enabled mode, API keys and signed in sessions are not affected.
  • Logging out now ends that session on the server (#2962). Before, logout only cleared the browser's cookie, so a copied cookie kept working until it expired. Your other devices stay signed in.
  • Login attempt limits hold under a burst (#2962). Many simultaneous wrong passwords from one address could all be checked before the limit kicked in. The same fix covers OPDS reader logins. OPDS readers that send their password with every request are remembered for a few minutes after a successful check, so fetching many covers at once stays fast instead of tripping the attempt limit.
  • Password checks can no longer exhaust memory (#2962). Only a few run at once, so a flood of login requests queues instead of running the server out of memory.
  • First run setup creates exactly one admin (#2962), even if the setup form is submitted several times at once.
  • NZB grabs through Prowlarr no longer hand its API key to the indexer (#2958). SABnzbd and NZBGet fetches now drop the Referer header when following a download redirect, and every redirect is checked against the same address rules as the original link, including when an outbound proxy is set.
  • More credentials are kept out of responses, logs and errors (#2958). Jackett keys, tracker passkeys (passkey, torrent_pass, authkey, rsskey), tokens, signatures, the r key in newznab getnzb links and magnet tracker URLs are now stripped from download links, GUIDs and detail links in search, queue, pending and history results, and redacted from stored errors and the log export, the same way the indexer API key already was. Grabs still send the real values.
  • Webhook secrets no longer show up in notifier errors (#2958). A failed notification logs only the webhook's host, so Discord, Slack, Telegram, Teams, Home Assistant, Apprise and ntfy tokens stay private.
  • Blocklist and profile changes are admin only (#2955). A non admin account could list the whole blocklist and delete entries from it, letting releases an admin had blocked be grabbed again, and could create, edit or delete metadata profiles. Both now answer 403 to anyone but an admin, matching the Settings screens, which were already admin only. Blocklisting a release from your own History still works for every user.
  • The Hardcover series comparison respects per user libraries (#2955). With BINDERY_ENFORCE_TENANCY on, comparing a series with Hardcover listed other users' books from that series. It now shows only the books that user can open.
  • Bounded memory when reading book metadata (#2957). A small malicious EPUB could make Bindery allocate gigabytes while reading its title during import, and a tiny FLAC, Ogg or Opus file could do the same through its embedded cover art during a library scan. Bindery now caps how much embedded metadata it reads and checks that the sizes a file declares fit inside it. When they do not, it falls back to the filename, so a bad release can no longer run the container out of memory.
  • Bounded HTTP metric labels (#2954). Unauthenticated requests with made up methods or unknown paths could each create a new permanent Prometheus series, growing memory without limit. Unknown methods are now counted as OTHER and requests no route matched as unmatched.
  • Release binaries are now signed with build provenance (#2956). Every archive and the checksums file on a GitHub Release carries a SLSA provenance attestation, like the container image already did. Check a download with gh attestation verify <file> --repo vavallee/bindery.
  • Tracker passkeys in the URL path are now hidden too (#2987). Some private trackers put the passkey or RSS key in the download link's path instead of a parameter, so it still showed up in search, queue, pending and history results and in the log export. Those path keys are now redacted the same way, and grabs still send the real link. IPTorrents tp keys, magnet source links and encoded magnet tracker links are covered as well, and info links you click keep working.
  • Usernames and passwords written into a feed or download link are hidden (#2987). A link like https://user:pass@host/... no longer shows its credentials in results, history or the log export.
  • Bounded memory when reading MP3 and M4B tags (#2989). A large audiobook download whose tags claimed gigabytes of cover art or text could make every library scan hold that much in memory. Bindery now checks those sizes before reading the tags, as it already does for FLAC and Ogg, and falls back to the filename when they are out of bounds. Each MP4 cover atom and each MP3 picture can be up to about 16 MiB, so real covers and chapter art still read.
  • A malformed audio file no longer stops a library scan (#2989). An M4B whose artist or title tag was stored as the wrong type crashed the tag reader and ended the whole scan, so every file after it went unscanned. Bindery now logs a warning with the file path, falls back to the filename for that file and carries on.
  • Release downloads now include their build provenance as a file (#3005). Each GitHub release carries bindery_<version>.intoto.jsonl next to the archives, the same signed attestation GitHub already stored for them. Check a download with gh attestation verify <archive> --repo vavallee/bindery --bundle bindery_<version>.intoto.jsonl and it reads the attestation from that file instead of asking GitHub for it.

Fixed

  • Import no longer offers a look alike by the wrong author (#2942). When audio files sit in another author's folder, Import, In your library now reads the author and book from their tags or track names, says so on the row ("Files say Katy Evans, folder says James Patterson"), and suggests that author's books first, scored on the book the files name. The folder author's books are still listed but never preselected or offered as a one click adopt, and a title that only shares its opening letters with an unrelated one no longer shows up as an 80% match. Thanks to foobarbigtime for the report.
  • Tiny notes files are no longer treated as books (#2944). A 1 KB .txt named after a book could become that book's ebook: the library scan attached it on its own and marked the book imported, so the real ebook was never searched for; adding the author could do the same; and Import, In your library offered it for adoption. An ebook format file under 4 KiB is now never matched by a scan or by adding an author, is listed on its own row as too small to be a book with Ignore as its action, and cannot be adopted. A notes file beside the matched book is still counted quietly as its companion. Rescanning applies all of this to files not yet tracked; files already attached are left alone, and Manual Import still takes a file of any size if you need it. When your audiobooks folder is separate from your library, a row found in the other format's folder (an ebook under the audiobooks root, say) is labelled with that folder and is never a one click Confirm. Thanks to foobarbigtime for the report.
  • Norwegian Bokmål and Nynorsk count as Norwegian (#2998). Books and files tagged nb, nob, nn or nno no longer fail a profile that allows Norwegian. Thanks francisrath.
  • Every two letter language code is recognised (#2998). Books and files tagged with codes such as uk, he or sk were not recognised as Ukrainian, Hebrew or Slovak, so a Ukrainian EPUB could import under an English only profile. Chinese tagged cmn or yue now counts as Chinese.
  • An import onto a file another book already tracks now fails and names that book (#2937). Bindery records each file against one book only, and when a file was already attached to the wrong book, importing it for the right one recorded nothing yet still reported success, wrote a history entry for the right book, and left that book Wanted with no file. For an ebook, the new file could also overwrite the file the other book tracked. A later manual import then said the file already existed. Now the download stops as Import Blocked with a message naming the book that holds the file and its id, the book you imported for stays Wanted, and the file that other book tracks is left exactly as it was. Use Fix match on that book to move the file where it belongs; the move now shows in History as File Moved. When the book that held the file has since been deleted, a download or manual import takes the file over automatically; adopting such a file from your library is still refused. The same check covers Audiobookshelf imports, adopting files from your library, and the library scan, none of which report a file as theirs when another book holds it.
  • The global path remap now applies to the save path Bindery sends (#2665). With only BINDERY_DOWNLOAD_PATH_REMAP set, rTorrent and qBittorrent (when the client has no category) were told to save into Bindery's own folder, such as /downloads, which may not exist on the client's side. The save path now goes through the same remaps Bindery reads paths back with: the client's own path remap first, then the global one. The qBittorrent category health check now honours the global remap too.
  • Deluge categories with capital letters now label the torrent (#2665). Deluge stores labels in lowercase and rejects any other spelling, so a category typed as Books left grabs unlabelled and any move path on the label was skipped. Bindery now sends the label lowercased, logs a warning when Deluge still refuses a label (for example when the Label plugin is off or the label does not exist), and Diagnose checks your Deluge categories against the labels Deluge has.
  • Audnex enrichment no longer overwrites edits made while it runs (#2926). Audiobookshelf imports, Hardcover list syncs and the Enrich button on the book page used to write back the whole book as it was before the Audnex lookup, so a change you saved during that lookup (unmonitoring the book, a new cover, a narrator fix) could be silently undone. Background enrichment now skips the book when it changed underneath, and the Enrich button retries once on top of your edit or asks you to try again. Rebind gets the same protection while it fetches the new record.

Changed

  • Grabbing an old search result after a restart now asks you to search again (#2958, #2960). Search results are remembered for 24 hours. A grab from the web UI of a result Bindery no longer remembers answers "this search result has expired, search again" instead of sending a link with its credentials removed. API key callers can still post their own download URL.
  • Quality and metadata profiles are now shared and managed by admins (#2955). Users can no longer create or edit profiles, and existing profiles, including any a user made before, are managed by admins. Every user still sees every profile when adding or editing an author, also with BINDERY_ENFORCE_TENANCY on.
  • The container image is built with the same Go toolchain as the release binaries and CI (#2956), Go 1.26.8. The image had drifted to a newer Go than the one the test suite and vulnerability scan run against.
  • A download in a language your profile does not allow is now rejected instead of relabelling the book (#2998). When a release name did not say its language, a foreign edition could slip past the search filter, and at import Bindery read the EPUB's language and quietly switched the book to it, so an English only library filled up with Swedish and Dutch books that looked imported and were never searched again. Now, when the author's metadata profile lists the languages it allows and the downloaded EPUB declares one outside that list, nothing is imported: the Queue row says which language the file declares and which the profile allows, the release is blocklisted so the next search picks another, and the book stays Wanted. Files that declare no language, or an allowed one in any spelling (en, en-US, eng), import as before, an EPUB that declares several languages is allowed when any of them is, a release with both an allowed and a disallowed EPUB imports the allowed one, and profiles that allow any language keep the old relabelling. Manual import and Match to book are never refused, and books already in your library are not touched. Thanks to foobarbigtime for the report.

Don't miss a new bindery release

NewReleases is sending notifications on new releases.