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:
- New LAN-bypass-via-reverse-proxy fix for the #35 follow-up reported by @FsxShader2012.
- 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
IHttpContextAccessorintoAuthenticationEventHandler(same registration asTwoFactorAuthProvider, line 50 ofPluginServiceRegistrator). - Snapshotted
X-Forwarded-Forsynchronously insideOnSessionStartedbefore theTask.Runhandoff. The background task outlives the HTTP request, so reading the header insideHandleSessionAsyncwould race with request completion and seeHttpContext=null. - Plumbed the captured value through
HandleSessionAsyncintoBypassEvaluator.Evaluate, which already implemented the SEC-H2 right-to-left walk inPickRealClientIp— the inputs just weren't being supplied at the session-event path.
Fix (response compression)
TwoFactorEnforcementMiddlewarenow setsAccept-Encoding: identityon 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
LooksLikeAuthResponsefailure log now surfaces first-8-bytes hex (gzip =1f 8b, zlib =78 9c, plain JSON =7b, HTML =3c), first-100-chars preview,Content-Encodingheader, andContent-Typeheader — 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,HandleSessionAsyncgainsstring? forwardedForparameter, theEvaluatecall goes from passingnullto passing the captured value.TwoFactorEnforcementMiddleware.cs(promoted from v2.4.10.1):Accept-Encodingrewrite + expandedLogInformationpayload +LooksLikeAuthResponsesubstring comparisons switched toOrdinalIgnoreCaseso 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.