github ZL154/JellyfinSecurity v2.4.11

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

v2.4.11 — Bug fix: X-Forwarded-For honoured in session-start bypass, response-compression suppression promoted from v2.4.10.1

Bundles two distinct fixes:

  1. New LAN-bypass-via-reverse-proxy fix for the #35 follow-up reported by @FsxShader2012.
  2. Promotes the v2.4.10.1 prerelease's response-compression suppression and expanded diagnostics — those changes confirmed working in the field for the original stuck-on-login symptom and are now in the catalog for everyone.

What broke (LAN bypass)

When LAN bypass was enabled and the server sat behind a reverse proxy, AuthenticationEventHandler.OnSessionStarted called BypassEvaluator.Evaluate with forwardedFor=null. The bypass evaluator could therefore not walk X-Forwarded-For right-to-left at authentication time, so it fell straight through to the raw TCP peer IP — which on a reverse-proxy setup is the proxy's IP, often inside 10.0.0.0/8 or whatever LAN range you've configured as a bypass CIDR. Every external request through the proxy was treated as a LAN client regardless of the real upstream client IP, silently skipping 2FA for any external user behind a trusted reverse proxy. The Trust X-Forwarded-For + Trusted Proxy CIDRs settings had no effect on this code path; only the middleware (TwoFactorEnforcementMiddleware) read XFF correctly.

What broke (response compression)

Reported as the original symptom in #35. TwoFactorEnforcementMiddleware reads the auth response body via a buffered MemoryStream after _next() returns. If a downstream response-compression middleware (Jellyfin's own gzip, ASP.NET's ResponseCompression, or a wrapping component) had already gzip/brotli-encoded the body, the substring sniff in LooksLikeAuthResponse matched no markers — the response then passed through with the bearer token intact, RequestBlockerMiddleware 401'd subsequent calls, and the 2FA challenge UI never rendered. Users got a permanent spinner on login.

Fix (LAN bypass)

  • Injected IHttpContextAccessor into AuthenticationEventHandler (same registration as TwoFactorAuthProvider, line 50 of PluginServiceRegistrator).
  • Snapshotted X-Forwarded-For synchronously inside OnSessionStarted before the Task.Run handoff. The background task outlives the HTTP request, so reading the header inside HandleSessionAsync would race with request completion and see HttpContext=null.
  • Plumbed the captured value through HandleSessionAsync into BypassEvaluator.Evaluate, which already implemented the SEC-H2 right-to-left walk in PickRealClientIp — the inputs just weren't being supplied at the session-event path.

Fix (response compression)

  • TwoFactorEnforcementMiddleware now sets Accept-Encoding: identity on auth-path requests before _next() runs, so any downstream response-compression middleware skips compression for that response. Auth response grows from ~600 bytes gzipped to ~2000 bytes plain — negligible. Scoped to auth paths only.
  • The LooksLikeAuthResponse failure log now surfaces first-8-bytes hex (gzip = 1f 8b, zlib = 78 9c, plain JSON = 7b, HTML = 3c), first-100-chars preview, Content-Encoding header, and Content-Type header — so any future weird response shape is identifiable in a single round-trip rather than another diagnostic-prerelease cycle.

Implementation

  • AuthenticationEventHandler.cs: +1 dependency (IHttpContextAccessor), +1 sync XFF capture block, HandleSessionAsync gains string? forwardedFor parameter, the Evaluate call goes from passing null to passing the captured value.
  • TwoFactorEnforcementMiddleware.cs (promoted from v2.4.10.1): Accept-Encoding rewrite + expanded LogInformation payload + LooksLikeAuthResponse substring comparisons switched to OrdinalIgnoreCase so the log accurately reports what the gate actually checks.
  • No schema changes. No config changes. No new dependencies.

172/172 tests pass. Build clean with TreatWarningsAsErrors=true.

Upgrade

In-place upgrade. No config changes required. Users on a reverse-proxy + LAN-bypass setup will find external requests now go through 2FA enforcement as expected (the previous silent-skip was a security hole, not a feature — re-test that any LAN bypass you rely on still works for your actual LAN, which it should since the TCP peer IP for direct LAN clients is still in the LAN range). Users who hit the stuck-on-login symptom on v2.4.8 or v2.4.10 stable should now log in cleanly without needing the v2.4.10.1 prerelease.

Verify the release

cosign verify-blob \
  --certificate Jellyfin.Plugin.TwoFactorAuthv2.4.11.0.zip.pem \
  --signature Jellyfin.Plugin.TwoFactorAuthv2.4.11.0.zip.sig \
  --certificate-identity-regexp 'https://github.com/ZL154/JellyfinSecurity/.+' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  Jellyfin.Plugin.TwoFactorAuthv2.4.11.0.zip

SLSA build provenance: Jellyfin.Plugin.TwoFactorAuthv2.4.11.0.zip.intoto.jsonl — verify with gh attestation verify --owner ZL154 Jellyfin.Plugin.TwoFactorAuthv2.4.11.0.zip.

Don't miss a new JellyfinSecurity release

NewReleases is sending notifications on new releases.