Skip to content

fix: socket reconnection loop and trust proxy validation error#4604

Merged
robertsLando merged 7 commits into
masterfrom
fix/socket-reconnect-loop-4564
Apr 29, 2026
Merged

fix: socket reconnection loop and trust proxy validation error#4604
robertsLando merged 7 commits into
masterfrom
fix/socket-reconnect-loop-4564

Conversation

@robertsLando

@robertsLando robertsLando commented Mar 25, 2026

Copy link
Copy Markdown
Member

Summary

Fixes two issues affecting users behind reverse proxies (notably the Home Assistant addon).

Socket reconnection loop (Fixes #4564)

When the server's auth middleware rejected a socket connection (e.g. expired JWT), the client silently looped on reconnection forever. Socket.IO v4 emits connect_error on rejected handshakes, but the client only listened for error.

  • src/App.vue — JWT expiry is checked client-side before attempting to connect; an expired token triggers logout() instead of a connect attempt.
  • src/App.vue — added a connect_error handler. Three consecutive auth failures trigger logout (single failures are tolerated to ride out transient issues); other errors update status to Reconnecting.

ERR_ERL_UNEXPECTED_X_FORWARDED_FOR validation error (Fixes #4535)

express-rate-limit emitted a noisy ValidationError on every restart for users running behind a reverse proxy. The pre-existing TRUST_PROXY env-var path looked correct but was effectively broken: TRUST_PROXY=1 was forwarded to app.set('trust proxy', '1') as a string, which Express treats as the IP literal "1" — leaving trust proxy effectively off and the validation error firing.

  • api/app.tsparseTrustProxy() coerces the raw env value: "true"/"false" → boolean, numeric strings → hop count integer, anything else → passed through verbatim (IP/CIDR list or preset name).
  • api/app.ts — moved app.set('trust proxy', ...) into startServer() via configureTrustProxy(), mirroring how HTTPS is handled. Logs the resolved value at startup.
  • docs/guide/env-vars.md — expanded the TRUST_PROXY description with accepted formats and a note about ERR_ERL_PERMISSIVE_TRUST_PROXY (don't set it to true; prefer an explicit hop count or IP list).

This pairs with home-assistant/addons#4597, which sets TRUST_PROXY=1 by default in the HA addon — that PR only actually resolves #4535 once this coercion is in place.

An earlier revision of this PR added an X-Forwarded-For auto-detect middleware and a UI toggle for trust proxy; both have been dropped. Auto-detect was spoofable (any client could trip it via a forged XFF header and bypass the login rate-limiter), and the UI field became redundant once the HA addon set the env var by default.

Test plan

  • Verify socket connects successfully when running behind HA ingress proxy
  • Verify no ERR_ERL_UNEXPECTED_X_FORWARDED_FOR errors in logs when behind proxy with TRUST_PROXY=1
  • Verify expired JWT token triggers logout instead of reconnect loop
  • Verify TRUST_PROXY=1 is applied as a hop count (integer), not as the IP literal "1"
  • Verify TRUST_PROXY accepting an IP/CIDR list still works
  • Verify direct connections (no proxy) are unaffected
  • npm run lint passes

Fix infinite connect/disconnect loop when running behind a reverse proxy
(e.g. Home Assistant ingress) by addressing multiple contributing factors:

- Add connect_error handler to detect auth failures and redirect to login
  instead of silently retrying with a stale token forever
- Check JWT expiry client-side before attempting socket connection
- Suppress ERR_ERL_UNEXPECTED_X_FORWARDED_FOR errors from express-rate-limit
  when TRUST_PROXY is not explicitly set
- Auto-detect X-Forwarded-For header and enable trust proxy automatically
  for environments where users cannot set env vars (e.g. HA Green)

Closes #4564

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coveralls

coveralls commented Mar 25, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 25097969982

Coverage decreased (-0.02%) to 18.285%

Details

  • Coverage decreased (-0.02%) from the base build.
  • Patch coverage: 27 uncovered changes across 1 file (0 of 27 lines covered, 0.0%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
api/app.ts 27 0 0.0%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 23558
Covered Lines: 4054
Line Coverage: 17.21%
Relevant Branches: 445
Covered Branches: 335
Branch Coverage: 75.28%
Branches in Coverage %: Yes
Coverage Strength: 1.09 hits per line

💛 - Coveralls

robertsLando and others added 2 commits March 25, 2026 15:25
…rror

A single connect_error with "Authentication error" can happen transiently
(server restart, temporary network issues). Only force logout after 3
consecutive auth failures. Reset counter on successful connect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use proxyDetected flag to short-circuit middleware after first detection
- Rename _req to req since the parameter is used (read headers)
- Add security note about X-Forwarded-For spoofing risk
- Add comment referencing server auth middleware for error string match

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@DenverOps

Copy link
Copy Markdown

Has this made it into the release yet?

@robertsLando

Copy link
Copy Markdown
Member Author

@DenverOps Nope, I have to do some tests before merging but didn't have time yet

@DenverOps

Copy link
Copy Markdown

Thank you sir. No pressure was just trying to understand if I should test on my end on this next release and I didn't see it in the release notes si figured I'd ask.

robertsLando and others added 4 commits April 29, 2026 08:59
Drops the X-Forwarded-For sniffing middleware that flipped
`trust proxy` to `true` on the first proxied request: any client can
spoof XFF, which would let attackers bypass the login rate-limiter.

Also drops the blanket `xForwardedForHeader: false` validator
suppression — when trust proxy is correctly configured, the warning
no longer fires on its own.

Replaces both with:
- `trustProxy?: string` on `GatewayConfig` (hop count, IP/CIDR list,
  or `loopback`/`linklocal`/`uniquelocal`).
- A `Trust Proxy` text field in the General settings panel.
- `configureTrustProxy()` in `startServer`: env var wins, falls back
  to settings; coerces `"true"`/`"false"`/numeric strings properly
  (the prior env-var path forwarded `"2"` as a literal IP).
- `gateway.trustProxy` reported in `managedExternally` when the env
  var is set.

Refs #4535

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the new General settings entry and explains the accepted formats
(hop count, IP/CIDR list, presets). Calls out that `true`/empty
triggers the express-rate-limit `ERR_ERL_PERMISSIVE_TRUST_PROXY`
warning, and that the env var overrides the UI value.

Refs #4535

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Home Assistant addon defaults TRUST_PROXY=1 (home-assistant/addons#4597),
which covers the largest user group affected by #4535. A redundant UI
field would just create two ways to configure the same thing.

Reverts:
- trustProxy field on GatewayConfig
- gateway.trustProxy in managedExternally
- Trust Proxy text field in Settings.vue General panel
- Trust Proxy entry in docs/usage/setup.md
- Settings fallback in configureTrustProxy()

Kept (still needed):
- parseTrustProxy() coercion — required for TRUST_PROXY=1 to be
  applied as a hop count rather than the IP literal "1" (the prior
  env-var path forwarded the raw string to app.set, which Express
  treats as an IP, leaving trust proxy effectively off).
- Removal of the X-Forwarded-For auto-detect middleware (spoofable).
- Removal of the blanket xForwardedForHeader validator suppression.

Refs #4535

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@robertsLando robertsLando changed the title fix: resolve continuous socket reconnection behind proxy fix: socket reconnection loop and trust proxy validation error Apr 29, 2026
@robertsLando
robertsLando merged commit aed2082 into master Apr 29, 2026
13 checks passed
@robertsLando
robertsLando deleted the fix/socket-reconnect-loop-4564 branch April 29, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Z-Wave JS UI reconnecting continuously Add Option for The 'X-Forwarded-For' header for Express 'trust proxy'

3 participants