github ZL154/JellyfinSecurity v2.5.1

latest releases: v2.5.19, v2.5.18, v2.5.17...
one month ago

v2.5.1

OIDC enhancements + i18n bugfixes + admin UX polish. In-place upgrade. 254/254 tests pass.

OIDC

  • Per-provider ForceHttps toggle — pins the OIDC redirect_uri scheme to https regardless of forwarded headers. Fixes Cloudflare Tunnel and some Caddy / nginx setups that terminate TLS upstream but don't propagate X-Forwarded-Proto, where the IdP would reject the http:// redirect_uri. The X-Forwarded-Host trust boundary is untouched — only the scheme is overridden, so the existing redirect_uri-poisoning defence against attacker-sent X-Forwarded-Host on untrusted peers is preserved. Wired through OidcProvider model, controller upsert DTO, OidcRedirectUriBuilder, admin form checkbox, and 8-language i18n. 4 new xUnit tests including the untrusted-host regression case.

  • OIDC Token Exchange endpoint (RFC 8693-style): new anonymous endpoint POST /TwoFactorAuth/Oidc/Exchange/{providerId} accepts an id_token from a native client (Swiftfin, Findroid, Tizen apps) that ran its own auth-code+PKCE flow at the IdP with our configured ClientId. Server re-verifies signature, issuer, audience=ClientId, and expiry. Nonce check is skipped because the native client used its own nonce — the other id_token protections all still apply. Returns a one-shot bridge token the native client POSTs to /Users/AuthenticateByName, identical to the browser callback flow. Same IP-ban + 20/5min rate limit + per-user IP allowlist gates as /Oidc/Login.

    Internal refactor: OidcService.VerifyIdTokenAsync's expectedNonce parameter is now nullable, and a new private FinalizeSignInAsync helper holds the shared post-id_token pipeline (userinfo merge, AMR enforcement, group allowlist, user resolution, SsoLink persistence, AuthenticationProviderId reassignment) so both CompleteAsync and ExchangeIdTokenAsync use the same code path.

i18n

  • Language picker selection-state bugfix: renderLanguagePicker rendered synchronously during each page's inline init, but _lang was still 'en' at that point because loadTranslations() is async. The <select> locked to "English" and never resynced — even hard refresh didn't help because every page-load re-raced. Fix: after rendering, attach _readyPromise.then(syncSelectionFromResolvedLang) so the <select> value updates to _lang once translations resolve. Idempotent for already-resolved promises (subsequent SPA renders).

  • Admin Overview i18n race fix: in non-English locales the bundle fetch finished AFTER renderOverview's innerHTML write, then applyTranslations re-walked [data-i18n-key] containers and overwrote the rendered cards back to translated "Loading…". Three-pronged fix:

    • Gate the initial renderOverview on window.tfaI18n.ready so the bundle is loaded before the first _tr() call. The ready promise always resolves (even on bundle fetch failure) so this never hangs.
    • Defensively strip data-i18n-key from factorsGrid + enrollmentBars before setting innerHTML so any later applyTranslations sweep skips these containers.
    • Wire renderLanguagePicker's onChange callback to call renderOverview() so dynamic content re-renders in the new locale when the user switches language.

Admin UX

  • 15-second in-memory response cache on /Dashboard/Overview keyed by range (1w / 1m / 1y). The endpoint runs the full security-score pipeline (users + audit log + chain verify) + chart build on every hit, so tab switches and range flips would re-pay the bill. With the cache, first load computes (a few hundred ms on a healthy server); subsequent loads within 15s return instantly. Cross-admin (data is server-wide); clears on process restart.

  • admin-script.js stays [AllowAnonymous] with an explicit comment so future CodeQL CWE-285 alerts on it can be dismissed as a false-positive. The named-resource heuristic flags the admin- prefix but the resource is pure JavaScript UI code shipped to every admin browser anyway — same-origin <script src> tags carry COOKIES but NOT the Authorization: MediaBrowser header Jellyfin's auth pipeline checks, so [Authorize] would break the script load and lock the admin page on "Computing…".

CI

  • update-badge.yml workflow: top-level permissions: read-all, per-job opt-in for contents: write. Matches the pattern in release.yml / ci.yml / codeql.yml and resolves the OSSF Scorecard Token-Permissions check.
  • update-badge.yml if: expression now wrapped in double quotes so strict YAML parsers (OSSF Scorecard) don't trip on the colon inside the commit-message literal.

Verification

  • 254/254 xUnit tests pass.
  • Clean build with TreatWarningsAsErrors=true — 0 warnings, 0 errors.
  • OSSF Scorecard: 7.5/10 (was 6.8 during the YAML-error window; restored after the fix).

Don't miss a new JellyfinSecurity release

NewReleases is sending notifications on new releases.