github LavX/bazarr v2.7.2
Bazarr+ v2.7.2 (Atlas)

2 hours ago

Bazarr+ v2.7.2 (Atlas)

Codename: Atlas (patch on the v2.7 line).

A stability and security patch on the v2.7 line. A request-host check closes a DNS-rebinding hole while login is off, credentials are scrubbed out of logs, jobs and stored diagnostics, the Subtitle Editor stops carrying its key in playback URLs, and a queued translation can no longer overwrite the subtitle a provider just delivered. Around them, the Jobs API learns a real cancelled status, the container follows a base URL changed in the app, the compat endpoint resolves imdb ids again, and the library and Wanted views gain tag and subtitle-completeness filters. There are no schema changes.


Headline: A request-host guard, and credentials out of the places they leaked

The request host is checked while login is off

While no login is configured, Bazarr+ answers requests only for hostnames it recognizes: addresses, localhost, single-label names and names under .local, .lan, .home.arpa, .internal or .localhost are always answered, and any other hostname needs an entry under Settings > General > Allowed Hosts (or BAZARR_ALLOWED_HOSTS in the environment). The entry can also be added by hand as allowed_hosts under general in config.yaml, which applies after a restart. When such a request is refused, the page it gets names the hostname and offers a one-time approval code: the code is printed to the console Bazarr+ runs in (the terminal, docker logs for a container, journalctl for a service) and appears nowhere else; each use replaces it, and every restart prints a fresh one. A reverse proxy has to hand Bazarr+ the hostname the client asked for: as the Host header it forwards, or as X-Forwarded-Host from a proxy named in general.trusted_proxy. That hostname has to be recognized, and the proxy has to refuse hostnames it does not serve; a proxy that rewrites the Host header to an internal upstream name leaves the address-bar name unanswered, because the rewritten name is the one judged and the address-bar name never arrives. When a forwarded name is believed, it is judged together with the Host the proxy sends, so both need to be recognized, and a refusal names the one that failed. Webhooks and plugins that point at a hostname outside this set fail until the hostname is allowed.

Refusal reports are accounted once. A refused hostname is logged to the console at most once per window, then one line an hour, the most recent refused hostnames appear as health issues under System > Status, and a restart does not count the same refusal a second time. A misconfigured proxy or a hostile scanner therefore cannot flood the console, and the health page names the hosts worth allowing.

Credentials are scrubbed from everything the app stores and shows

A credential can leak in two shapes: a shape a library or a provider echoed back without knowing the value (Authorization: Bearer ..., x-goog-api-key: ..., Google AIza... keys, OpenRouter sk-or-v1-... keys), and an exact value this install holds in its settings or its Provider Hub plugin secrets. Both are now masked as *** in the places those values travel: the log formatter, job progress messages, job errors, job actions, Provider Hub worker errors and tracebacks, and the Provider Hub's stored state diagnostics. The boot rewrites diagnostics an earlier version already stored.

Two properties keep the mask from turning into a bug of its own. Credential containers, the plugin secrets a provider needs to receive intact, are never rewritten by the text scrub, so a value that happens to equal a provider id cannot rename an installation. And a Provider Hub state document that cannot be read leaves the registered values as they were, instead of publishing a set that is missing every Hub credential. Credential values shorter than six characters are not registered as exact values, since masking them would redact ordinary words wherever they appear; the shape patterns still cover them when they look like a token.

The editor's playback stops carrying the API key

The Subtitle Editor's native HLS playback, used by Safari and iOS where a player cannot add headers to the playlist and segment requests, now asks for a short-lived signed stream token over an authenticated call and puts that token in the playlist URL instead of the API key. A token binds one media item, one arr instance and one audio track, and its expiry covers the whole playback: the media's duration plus thirty minutes, at least one hour, at most twelve hours, and four hours when the duration is unknown. The signing key is derived from a secret generated once per process and never persisted; a restart or an API key change invalidates outstanding tokens, and the editor recovers by minting a new one. The other three editor calls already sent their key in the X-API-KEY header since v2.7.1.

Secrets fail closed, and plugin secrets are write-only

A secret store that cannot decrypt its payload now refuses the operation instead of continuing with a value it cannot trust. Plugin secrets are encrypted at rest like the built-in ones and become write-only: the settings API answers a descriptor for a stored secret rather than the value, so the value can be replaced but not read back. A stored secret also stops following a new address: changing the URL or host it belongs to makes Bazarr+ ask for the key again at the new address, instead of silently sending the old key to a different host.


Other Improvements & Fixes

Translation

  • An auto-queued translation no longer replaces a subtitle that a provider landed after the job was queued. The auto paths record the destination's version when they queue, decide "already served" from that same capture (a file already on disk is not queued at all), and the write refuses a destination that moved since. A refused job is reported as a skip, not as a failure, so it does not hold the next scan back (#711).
  • A Gemini reply that comes back short no longer fails the item. When the model returns fewer cues than a batch sent, for example 249 of 252, the cues it did return are kept, the missing ones stay in the source language, and the job reports a partial translation with a partial footer in the file. A sports translation still requires every cue (#713).
  • A profile that asks for translation now falls back to the provider search when no translator can serve it: the availability gate answers every manual translation surface, the wanted scans ask it before queueing, and the search covers the language. After a failure, only the variant that failed (plain, HI or forced) is held back, not every variant of the item.
  • The compat endpoint resolves library-miss imdb searches again. Its TVDB v1 fallback could never run, since the installed client's call raised before any request and the v1 API is retired. Resolution now runs keyless through TVDB v4's remote-id search with v4 series hydration and a TMDB find step, with OMDB as an optional bridge. An id that resolves nowhere and carries no hashes logs one warning naming the failing sources; the nameless provider fanout itself is unchanged.
  • A translation targeting a custom language now names its file with the language's regional tag, such as es-419, instead of the internal code.

Jobs

  • A job the user stops is reported as cancelled by the Jobs API and in the Jobs drawer, instead of completed. It stays on the completed history as one terminal queue with its own status, a filter for cancelled answers only stopped jobs, and a stop that arrives after the job's last checkpoint still completes. Scripts that read the jobs API should accept the new status.

Library, search and providers

  • The Movies and Series lists gain a Subtitles filter: Any keeps the current view, Complete shows profiled items with nothing missing, and Missing some shows the profiled items that still need subtitles. Items without a language profile and series without episode files stay visible under Any, and the filter combines with search and the existing filters (#689).
  • The Movies and Series lists gain a Score filter: Any, Full (100%), Not full (below 100%) and Below threshold, with a Lowest score column while it narrows the list. The score is the lowest among the item's current subtitle files and embedded tracks, read from the newest record per file, and it follows a translated file across path mappings. Lists refresh after subtitle changes through one debounced refresh, and the threshold option stays disabled until the setting is known (#716).
  • Series, Movies, Sports and their Wanted views filter by the tags a configured Sonarr, Radarr or Sportarr instance supplies (#697). Sports Wanted rows inherit the tags of their league, the filter panel lists the options across pages, and active tag filters appear as removable chips beside the others.
  • An empty Sonarr episode list now removes the local rows of a series whose episodes are gone, instead of leaving stale episodes behind.
  • The series list no longer shows a negative missing-episode count when a Sonarr exclusion is set; the count query joins the show table.
  • A startup reconcile no longer fails when a dangling library owner collides with the default instance: the dangling rows merge into the default owner, and orphaned rows from a deleted instance are quarantined instead of being stamped onto the remaining one.
  • Expected provider failures log one line instead of an "Unexpected error" traceback, including the requests-style timeout and connection names a plugin worker reports.
  • Catalog sources resolve their refs over the git protocol and skip an unchanged commit, so a refresh no longer downloads a bundle to learn nothing changed.

Post-processing

  • Save-time validation now keeps a placeholder out of every position a program executes, not only the program name. The refusal covers the script and inline-code options of sed, perl, python, node and their shells, tar's exec checkpoint action, rsync's remote path and transport shell, a find payload's own program or nested shell, and the wrappers and interpreters around them, including attached values, bundled short options, equals-separated forms and abbreviated long option names. Whole operands and the values of other options stay open. A command saved before this change that does not pass is reported as a health issue under System > Status, and the job that would run it is refused until the command is edited.

Editor

  • A line translation in the Subtitle Editor adds its cue only once the job is queued, so a refused submission no longer leaves an empty cue behind.
  • The editor preview transcodes HEVC sources to 8-bit H.264 for browsers that use hls.js, so 10-bit HEVC episodes play in Chromium and Firefox; the native player path keeps the HEVC copy (#718).

Login

  • The login screen's backdrop and vignette cover the full view at every window size, with no scrollbar and no uncovered strip at the bottom, and a wheel or touch gesture over the background still scrolls a card taller than the screen (#718).

Settings and keys

  • The stored-key mode is kept when the settings edit dialog opens, and a key is asked for again when the address it belongs to changes.
  • The translator settings Test button disables on the service URL alone, matching the gate that only needs the service for a self-hosted translator, and still sends the Bazarr-side key to the test endpoint when one is set.

Languages

  • ChineseTraditional now has its own track-title matches instead of inheriting the Brazilian Portuguese list. 简繁中文 no longer matches Traditional, Traditional titles such as 繁體中文, Traditional Chinese, CHT and Big5 still do, and the short tc marker only matches as a whole word, so a title like Dutch is not classified as Traditional.

Container and installer

  • The container supervisor resolves the base URL at request time instead of freezing a route table at boot, so a sub-path changed under Settings > General keeps routing without a restart.
  • Each installer run owns its partial backup and its process groups, so a concurrent run, or a signal that ends one, can no longer remove another run's partial or leave the old services stopped.

Security hygiene

  • The open security board is cleared ahead of the cut: the frontend moves axios to 1.20.0 and sharp to 0.35.5, with transitive pins through the existing overrides, and several code-scanning findings are fixed. The post-processing expansion regex class loses a redundant character range with identical matching semantics, the CI guard test asserts the guard's problem structure rather than a substring, and the wizdom and ktuvit providers keep their diagnostic debug lines without the credential-derived values (#701).

CI / Docker

The local mirror resets a SIGHUP that a background launch ignored before it starts, which keeps the installer's trap tests meaningful however the mirror is launched. The end-to-end workflow gains an on-demand sub-path mode: the release-pair dispatch runs the stateless project twice on the one image it builds, once at the root and once under general.base_url=/bazarr. The backlog adds specs for manual search, upload and translation-unreachable, which stay marked pending until the harness runs against a seeded library, alongside the settings recalculation, sports page and system specs that run today. The Platform packages lane simulates the next release past every checked-in lock, and the v2.7.1 release lock is recorded so a manual dispatch resolves it. No base-image change is included.


Dependency Updates

  • cachetools 7.2.1, SQLAlchemy 2.1.4, charset-normalizer 3.5.2, emoji 2.16.0 and cryptography 50.0.2, with the in-app installer's runtime pins kept in step; the Mantine group moves to 9.7.2 (#715).
  • axios moves to 1.20.0 and sharp to 0.35.5.
  • Resolved transitive updates: source-map-js 1.2.2, postcss-selector-parser 7.1.6, fast-uri 3.1.8, undici 7.30.0 (through the ^7.29.1 override) and brace-expansion 5.0.12.
  • filelist stays on 1.0.6, and its nested brace-expansion resolves to 2.1.7 through a nested ^2.1.7 override.
  • braces has no fixed release published upstream and stays flagged until one exists.

Database Migrations

No schema changes. The Alembic head is unchanged from v2.7.1, so the first start after upgrading runs no migration.


Included Pull Requests

  • #689: Filter movie and series lists by subtitle completeness.
  • #693: Complete the v2.7.1 pull request inventory from the shipped ancestry.
  • #695: Record the v2.7.1 release lock.
  • #696: Simulate the packaging lane's next release past every checked-in lock.
  • #698: Integrate the v2.7.2 Atlas lanes.
  • #700: Assert Clear all resets the Subtitles filter.
  • #701: Clear the open security board ahead of the cut.
  • #711: Credential scrubbing, translate queue guard, cancelled jobs, supervisor base URL, language and compat fixes, ARR tag filters.
  • #713: Keep a short Gemini batch as a partial translation.
  • #715: Take the open dependency bumps with their runtime pins.
  • #716: Filter movies and series by subtitle score.
  • #718: Fix the login backdrop and give hls.js an 8-bit H.264 preview for HEVC sources.

Upgrade / Migration Notes

  • Back up your configuration and database before upgrading, as always. There are no schema changes in this release.
  • The Allowed Hosts control is new in this release, so set it before upgrading if Bazarr+ runs behind a reverse proxy with login off: add the address-bar hostname to general.allowed_hosts in config.yaml or to BAZARR_ALLOWED_HOSTS in the environment before the first start, or use the one-time code the refusal page prints to the console afterwards. Webhooks and plugins that point at a hostname outside the recognized set need the same entry.
  • Plugin secrets are write-only after upgrading: the settings API shows a descriptor instead of the value, and changing the address a secret belongs to asks for the key again. Re-enter a value to replace it.
  • Post-processing commands are validated more strictly. A command that puts a placeholder where the program executes it is refused at save time, and a command saved before this change that now fails is reported as a health issue under System > Status and skipped until edited. Check the command after upgrading if it uses placeholders inside options.
  • If you script against the Jobs API, a job the user stopped now answers cancelled instead of completed, and status=cancelled filters to stopped jobs.
  • Native HLS playback in the Subtitle Editor now uses a signed stream token instead of the API key in the URL. Tokens expire with playback and are invalidated by a restart or an API key change; no configuration is needed.
  • The compat endpoint still fans out without a resolved name for an id it cannot resolve; only the resolution chain and its warning changed.
  • If you run the AI translator, the queued-translation guard means a translation can now be skipped when a provider delivers the target subtitle first. That is the intended behavior and needs no action.

Docker

docker pull ghcr.io/lavx/bazarr:2.7.2
docker pull ghcr.io/lavx/bazarr:latest

After upgrade, confirm the UI loads and /api/system/status reports 2.7.2.


Contributors

Thanks to the reporters and contributors who made this release concrete:


Full Changelog: v2.7.1...v2.7.2

Don't miss a new bazarr release

NewReleases is sending notifications on new releases.