Fixed
- DNB search results couldn't be added to the wanted list (#545, #561)
— DNB bib records expose author names but not author IDs, so every
result had the Add button greyed out with a misleading "try a more
specific search" hint. The fix extracts ISBN(s) from MARC 020 in DNB
records and adds a cross-provider author resolver: when a search result
lacks a foreign author ID, the backend looks up the ISBN in OpenLibrary
and rewrites the request to use OL's canonical author/book identity.
Books that resolve end up under their OpenLibrary record (with OL's
title and metadata); books with no OL match return a clear "add the
author manually first" error instead of silently failing. - Telemetry chart hides the freshly cut release (#546) —
/stats
truncated the version chart to top-8 by count, so a brand-new release
with one or two installs disappeared into(other)until it
organically out-ranked older versions (sometimes weeks). The chart now
pins the configuredLATEST_VERSIONinto the visible region and
annotates it(latest)so newly cut releases are immediately visible. - Transmission retry path silently used corrupted bodies (#558) — On
retry against the Transmission RPC endpoint,io.ReadAllerrors were
dropped and an empty / partial slice was used as the response body.
Errors now propagate viafmt.Errorf("transmission: read retry body: %w", err)so a torn body fails loudly instead of producing
garbage-decoded torrent state. refreshBookStatuscould zero a user's file paths on transient DB
errors (#558) —Scanerrors on thebook_fileslookup were dropped
via_ = QueryRowContext(...).Scan(&path), so any error other than
sql.ErrNoRows(lock timeout, corruption, connection drop) wrote""
back tobook.EbookFilePath/book.AudiobookFilePath. Now distinguishes
sql.ErrNoRows(legitimate empty path) from real failures via
errors.Is, returning the wrapped error in the latter case.- Non-ASCII filenames mangled in Content-Disposition (#558) — Library
file downloads now emit RFC 5987filename*=UTF-8''<percent-encoded>
alongside the legacyfilename="..."parameter, so titles with German /
Cyrillic / CJK characters land on disk with the correct name instead of
being rewritten to a quoted-printable mojibake form. - Frontend timer leaks on unmount (#556) —
AuthSettings's
copy-to-clipboard "copied" badge andDiscoverPage's toast clear-out
used baresetTimeoutcalls that fired against unmounted components,
producing React's "can't update state on unmounted component" warning.
Both are nowuseEffect-driven withclearTimeoutcleanup.
Changed
- Log persistence shutdown is now graceful (#558) —
LogHandler.Stop()
closes the in-memory channel and waits (bounded by a 5s context) for
the drain goroutine to flush any in-flight log entries before the
process exits. Wired intocmd/bindery/main.goas a deferred call.
Previously the goroutine leaked for the lifetime of the process and
any buffered entries were lost on shutdown. Note: thedeferonly
fires on clean main-return paths, not on signal-driven termination —
full benefit requires #559 (signal-based graceful shutdown), tracked
separately. - Several previously-dropped errors now surface in the log stream
(#558) —slog.Warncalls were added tointernal/api/imageproxy.go
(response write),internal/api/auth_oidc.go(provider parse),
internal/api/authors.go(batch dedup updates),
internal/db/recommendations.go(genres marshal), and
internal/prowlarr/syncer.go(SetLastSyncAtafter a successful
sync). Behaviour is unchanged; visibility is not.
Security
- API key compared with
subtle.ConstantTimeCompareinstead of==
(#555) — Both the main HTTP middleware (internal/auth/middleware.go)
and the OPDS auth path (internal/api/opds_auth.go) used variable-time
string equality on the API key, leaking enough timing information to
enable a remote byte-by-byte recovery attack against a sufficiently
determined attacker. Both sites now usesubtle.ConstantTimeCompare,
with the existing empty-key short-circuit preserved so empty submissions
don't telegraph the real key's length. - GitHub Actions in
ping-server.ymlpinned to commit SHAs (#555) —
Five actions (actions/checkout,docker/login-action,
docker/setup-qemu-action,docker/setup-buildx-action,
docker/build-push-action) were pinned by tag, leaving the workflow
vulnerable to a tag-rotation supply-chain attack. All are now pinned to
the same commit SHAs already in use inci.yml. cmd/telemetry-server/Dockerfilebase images pinned by digest
(#555) —golang:1.25-alpineandalpine:3.21are now pinned to their
content-addressable digests so a tag rotation can't silently swap the
base image during the next ping-server build.- Dedicated
*http.Clientfor telemetry pings (#555) — Replaces
http.DefaultClientfor the once-per-day telemetry ping path with a
package-local client carrying its own timeout. The 10s context deadline
was already in place, but the dedicated client guards against unrelated
code mutatingDefaultClientand reaching into the ping path.