Skip to content

Case Study: Smart Home Mobile Ingress (Home Assistant) ​

This case study demonstrates how to securely configure Home Assistant for external mobile companion access (location updates, push notifications, and automation triggers) while completely cloaking smart door locks, alarm controls, and login panels from public internet exposure using Zero-Trust Allowlisting.


The Threat Model ​

Home Assistant controls the physical perimeter of modern smart homes: smart deadbolts, garage doors, surveillance cameras, alarm panels, and automated relays.

To support mobile presence detection and automation, the official Home Assistant Companion App requires external ingress:

  • Sending GPS location and geofencing beacons (/api/webhook/<token>).
  • Submitting battery level, pedometer, and device sensor state.
  • Responding to actionable push notifications.

However, exposing Home Assistant directly over the internet (home.example.com) introduces existential physical risks:

  1. Root Path Lovelace & UI Exposure: Visiting / serves the Lovelace web UI or triggers a redirect to /auth/login_flow. If not shielded by a default-deny policy, public visitors load the entire frontend application.
  2. Credential Stuffing & Login Probing: Scanners continuously attempt dictionary attacks on /auth/token, /auth/authorize, and /auth/login_flow.
  3. Unauthorized Entity Manipulation: Attackers could probe /api/services/lock/unlock or /api/services/cover/open_cover to compromise physical security.
  4. Supervisor & Configuration Dumps: Automated vulnerability tools scan for /api/hassio/*, /api/config, and YAML secret backups.

The Objective ​

  1. Mobile Ingress (Public Internet): Allow the mobile companion app to deliver device webhooks (/api/webhook/*) seamlessly without requiring an always-on VPN connection on your phone.
  2. Zero-Trust Entity & Admin Shield: Enforce a strict Default-Deny (blockPatterns: ['(?i)^/.*$']) policy so that every unlisted endpoint—including the root path /, Lovelace UI, authentication (/auth/*), and internal APIs—is cloaked with 404 Not Found.
  3. Home LAN & VPN Whitelist: Full web dashboard, lovelace cards, and configuration settings are accessible when connected to home Wi-Fi or via Tailscale / WireGuard VPN (allowedIps).

Configuration (Traefik, Caddy & NGINX) ​

Select your gateway below to inspect the production configuration:

# dynamic_conf.ymlhttp:  middlewares:    homeassistant-shield:      plugin:        routewarden:          enabled: true          methods:            - GET            - POST            - PUT            - DELETE            - PATCH            - HEAD          enableDefaultPatterns: true          # 1. Zero-Trust Allowlist: permit strictly mobile companion app webhook callbacks          allowPatterns:            - '(?i)^/api/webhook/[a-zA-Z0-9_-]+$'          # 2. Catch-all: default-deny all other routes (blocks root Lovelace UI, login, states)          blockPatterns:            - '(?i)^/.*$'          # 3. Trusted Home LAN and Tailscale / WireGuard VPN subnets bypass all blocks          allowedIps:            - "10.0.0.0/8"        # Internal Home LAN            - "100.64.0.0/10"      # Tailscale CGNAT subnet            - "192.168.1.0/24"     # Home Wi-Fi subnet            - "127.0.0.1"         # Localhost          response:            mode: json            statusCode: 404            body: '{"error":"Not Found","message":"Endpoint unavailable on public router"}'  routers:    hass-router:      rule: "Host(`home.example.com`)"      entryPoints:        - websecure      middlewares:        - homeassistant-shield      service: hass-service

Crucial Implementation Notes ​

1. HTTP Methods: Why methods is Mandatory ​

Mobile companion app webhooks transmit location updates and sensor payloads exclusively via POST requests.

Because RouteWarden inspects only GET requests by default, omitting methods would mean inbound webhook traffic would not be evaluated against your allowPatterns and blockPatterns. Explicitly defining methods GET POST PUT DELETE PATCH HEAD ensures full coverage.

2. Secret Webhook Tokens ​

Home Assistant automatically generates a cryptographically random webhook ID for each mobile integration (e.g. /api/webhook/019283a4b5c6d7e8...).

Our allowPatterns rule permits mobile webhook calls to reach Home Assistant while blocking access to /api/config, /api/services/*, and all administrative controls:

(?i)^/api/webhook/[a-zA-Z0-9_-]+$

3. Response Mode: Cloaking as 404 Not Found ​

We recommend mode: json with statusCode: 404.

WARNING

If your reverse proxy is placed behind an edge proxy or CDN (such as Cloudflare, Traefik edge, AWS ALB, or an NGINX reverse proxy), avoid using mode: silentDrop.

Abruptly terminating the TCP connection under silentDrop causes edge proxies to display an HTTP 502 Bad Gateway error page to clients. Using mode: json with statusCode: 404 returns a natural not-found response that gives attackers zero indication that Home Assistant is running.

4. Caddy Directive Ordering ​

When configuring RouteWarden in Caddy, ensure the module is registered before Caddy's built-in reverse_proxy directive in the global options block:

# Caddyfile Global Options{    order routewarden before reverse_proxy}

5. Home Assistant Reverse Proxy Trust (configuration.yaml) ​

Whenever Home Assistant runs behind Traefik, Caddy, or NGINX, Home Assistant's internal security layer requires explicit authorization to accept X-Forwarded-For headers from your proxy container. Add the following to your /config/configuration.yaml:

# /config/configuration.yaml in Home Assistant# Required when placing Home Assistant behind any reverse proxyhttp:  use_x_forwarded_for: true  trusted_proxies:    - 172.16.0.0/12      # Docker default bridge network    - 10.0.0.0/8         # Internal home container/pod subnet    - 127.0.0.1          # Localhost

6. Why Zero-Trust Allowlisting Prevents Root Lovelace & Redirect Bypasses ​

Home Assistant has no standalone public landing page; visiting https://home.example.com/ serves the Lovelace web application or redirects visitors to /auth/login_flow.

Traditional denylists (blocklists) targeting /auth or /api/config fail because the initial GET / request does not match the blocklist, allowing internet scanners to load the web interface and confirm that Home Assistant is running.

By inverting to Zero-Trust Allowlisting (Default-Deny):

  • blockPatterns: ['(?i)^/.*$']: Intercepts all inbound traffic at the reverse proxy. Requests to /, /lovelace, and administrative APIs never reach the Home Assistant container.
  • allowPatterns: ['(?i)^/api/webhook/[a-zA-Z0-9_-]+$']: Because RouteWarden evaluates allowPatterns before blockPatterns, valid mobile companion webhooks pass through seamlessly.
  • allowedIps: Your internal LAN and Tailscale/WireGuard subnets bypass the catch-all block, preserving complete administrative control for trusted devices.

Verification Matrix & Curl Tests ​

Inspect live curl verification commands and offline RouteWarden CLI tests:

# 1. Test Mobile Companion Webhook (Allowed)# Mobile Companion App submits device sensor or location beaconcurl -i -X POST https://home.example.com/api/webhook/d8f76e5a4b3c2d1e0f \  -H "Content-Type: application/json" \  -d '{"type":"update_location","data":{"gps":[37.7749,-122.4194],"battery":85}}'# Response: HTTP/2 200 OK (Content-Type: application/json)# 2. Test Root Web UI & Lovelace Dashboard Probe (Blocked & Cloaked)# External visitor or scanner visits home root URLcurl -i https://home.example.com/# Response: HTTP/2 404 Not Found (Zero-trust default-deny blocks root before Lovelace UI or login redirect)# 3. Test Public Login Attempt (Blocked)# External attacker probes authentication endpointcurl -i -X POST https://home.example.com/auth/token \  -H "Content-Type: application/x-www-form-urlencoded" \  -d "grant_type=password&username=admin&password=password123"# Response: HTTP/2 404 Not Found ({"error":"Not Found","message":"Endpoint unavailable on public router"})# 4. Test Public Service Manipulation Attempt (Blocked)# External bot probes service execution to unlock a smart door lockcurl -i -X POST https://home.example.com/api/services/lock/unlock \  -H "Content-Type: application/json" \  -d '{"entity_id":"lock.front_door"}'# Response: HTTP/2 404 Not Found# 5. Test Home LAN / VPN Access (Allowed)# Administrator connects from Tailscale (100.64.0.15) or Home Wi-Ficurl -i -X POST https://home.example.com/auth/token \  -H "Content-Type: application/x-www-form-urlencoded" \  -d "grant_type=password&username=admin&password=secret"# Response: HTTP/2 200 OK (Access token granted)
Route & IntentPath & MethodPublic InternetHome LAN / VPN (allowedIps)
Mobile Geolocation WebhookPOST /api/webhook/{token}✅ Allowed✅ Allowed
Mobile Push Notification AckPOST /api/webhook/{token}✅ Allowed✅ Allowed
Root Web UI & Lovelace DashboardGET /❌ Blocked (404)✅ Allowed
Web UI AuthenticationPOST /auth/token❌ Blocked (404)✅ Allowed
Service Execution (Smart Locks)POST /api/services/*❌ Blocked (404)✅ Allowed
Supervisor APIGET /api/hassio/*❌ Blocked (404)✅ Allowed
Configuration DumpsGET /api/config❌ Blocked (404)✅ Allowed

Released under the MIT License.