Skip to content

Invalid/expired stream authSig raises HTTPUnauthorized (401), which HA counts as failed logins and can IP-ban clients #955

Description

@joyjit

Summary

When /api/webrtc/ws rejects a missing, invalid, or expired
authSig, WebSocketView raises HTTPUnauthorized (401).

Home Assistant's HTTP ban middleware treats any raised
HTTPUnauthorized as a failed login
(homeassistant/components/http/ban.pyprocess_wrong_login),
regardless of requires_auth = False on the view.

With ip_ban_enabled / a positive login_attempts_threshold,
repeated expired-ticket reconnects (e.g. a sleeping browser tab
with a WebRTC card open) can ban the client IP. Behind a reverse
proxy with use_x_forwarded_for, that can be the household WAN
address and lock out all external access.

This is not a real login failure (no password/token for a user
account). It is a short-lived stream signature check failing.

Evidence

Component code (current):

# WebSocketView — requires_auth = False
elif not utils.validate_signed_request(request):
    raise HTTPUnauthorized()  # → HA ban counter

HA core only increments the ban counter on HTTPUnauthorized,
not on HTTPForbidden (403). HA itself uses 403 for already-banned
IPs in that same middleware.

Observed in the wild (HA 2025.x / WebRTC v3.6.1):

  • Card calls auth/sign_path for /api/webrtc/ws
    (default expires=30 seconds).
  • Sleeping/throttled Firefox tab presents the ticket ~60–110s
    later → JWT expired → 401 → "Login attempt or request with
    invalid authentication" against /api/webrtc/ws?...&url=....
  • After login_attempts_threshold (e.g. 5) hits, IP is written
    to ip_bans.yaml.

Related historical symptom (different root cause, same ban
fallout): #318 / #321 / #320 — broken signing after HA 2022.7
also produced IP bans via the same 401 path. Signing was fixed;
using 401 for ticket rejection was not.

Tracked internally: https://issues.infotune.com/tasks/476

Expected behavior

Rejecting an invalid/expired stream ticket should not
increment HA's failed-login / IP-ban counter.

Suggested fix: raise HTTPForbidden (403) instead of
HTTPUnauthorized (401) when validate_signed_request fails
(and similarly anywhere else a stream signature/cookie check
uses 401 for the same reason, e.g. HLS cookie check if it has
the same ban side effect).

Connection is still denied; real login brute-force protection
is unchanged.

Optional card-side hardening (separate)

Also consider passing a longer expires to auth/sign_path
and/or ensuring reconnect never opens a WebSocket with a stale
signed URL. That reduces expiry races but does not remove
the ban footgun while 401 remains.

Environment

  • custom component: AlexxIT/WebRTC (e.g. v3.6.1)
  • Home Assistant with login_attempts_threshold ≥ 1
  • Often: reverse proxy + use_x_forwarded_for

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions