Home Assistant Security Testing: Long-Lived Tokens, Webhook Exposure, Add-on Privilege, and Remote Access

Home Assistant is the most widely deployed open-source home automation platform with over 1.4 million active installations, controlling physical access systems, security cameras, alarm systems, locks, and HVAC across millions of homes and small businesses. Its security implications extend beyond data: long-lived access tokens generated in User Profile → Long-Lived Access Tokens provide permanent API access to all entities, automations, device controls, and system configuration — a token leak means an attacker can unlock doors, disable alarms, or turn off security cameras; webhook automation triggers create HTTP endpoints at /api/webhook/{webhook_id} that execute the automation without any authentication — only the webhook ID is required, and these IDs are often shared in online tutorials and copied between installations; Home Assistant add-ons running as Docker containers have varying privilege levels — the official SSH & Web Terminal add-on runs with host network access, and several community add-ons run with full_access: true giving complete host filesystem access; Nabu Casa and the HA Cloud create authenticated remote access tunnels but also mean HA is reachable from the internet; and the trusted_networks authentication provider in configuration.yaml can be misconfigured to allow authentication from overly broad network ranges. This guide covers systematic Home Assistant security assessment.

Table of Contents

  1. Long-Lived Token Testing
  2. Webhook Trigger Exposure
  3. Add-on Privilege Assessment
  4. Network Authentication Testing
  5. Home Assistant Security Hardening

Long-Lived Token Testing

# Home Assistant long-lived access tokens: permanent API access
# Generated in Profile → Long-Lived Access Tokens (no expiry by default)

HA_URL="https://homeassistant.example.com:8123"
HA_TOKEN="your-long-lived-access-token"

# Verify token and get system information
curl -s "${HA_URL}/api/" \
  -H "Authorization: Bearer ${HA_TOKEN}" \
  -H "Content-Type: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Version: {d.get(\"version\",\"?\")}')
print(f'Location: {d.get(\"location_name\",\"?\")}')
" 2>/dev/null

# List all states (entities) — reveals what devices are connected
curl -s "${HA_URL}/api/states" \
  -H "Authorization: Bearer ${HA_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
if isinstance(d,list):
    domains = {}
    for e in d:
        domain = e.get('entity_id','').split('.')[0]
        domains[domain] = domains.get(domain,0) + 1
    print(f'Total entities: {len(d)}')
    for dom,count in sorted(domains.items(),key=lambda x:-x[1])[:10]:
        print(f'  {dom}: {count}')
" 2>/dev/null

# List all automations
curl -s "${HA_URL}/api/config/automation/config" \
  -H "Authorization: Bearer ${HA_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    print(f'Automations: {len(d) if isinstance(d,list) else \"error\"}')
except: pass
" 2>/dev/null

Webhook Trigger Exposure

# Home Assistant webhooks: /api/webhook/{id} triggers automation WITHOUT authentication
# Webhook IDs are static strings defined in automation config
# Common sources: Home Assistant community forums, YouTube tutorials, shared configs

HA_URL="https://homeassistant.example.com:8123"

# Test webhook trigger — no auth required
WEBHOOK_ID="my_webhook_id"  # Often: alarm_webhook, door_webhook, automation_webhook
curl -s -X POST "${HA_URL}/api/webhook/${WEBHOOK_ID}" \
  -H "Content-Type: application/json" \
  -d '{"test": "security_check"}' \
  -w "\nHTTP Status: %{http_code}" 2>/dev/null
# HTTP 200 = webhook triggered — automation executed without auth

# Enumerate potential webhook IDs from HA community patterns
for ID in "alarm_webhook" "door_webhook" "gate_webhook" "lights_off" \
          "vacation_mode" "alarm_arm" "alarm_disarm" "trigger" \
          "ha_webhook" "automation_webhook"; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    -X POST "${HA_URL}/api/webhook/${ID}" \
    -H "Content-Type: application/json" \
    -d '{}' 2>/dev/null)
  if [ "$STATUS" = "200" ]; then
    echo "WEBHOOK TRIGGERED: ${ID} — automation may have executed"
  fi
done

Home Assistant Security Hardening

Home Assistant Security Hardening Checklist:
Security TestMethodRisk
Long-lived access token with full API accessAuthorization: Bearer {token} on /api/ — token grants all entity read/write, automation execution, and system configuration changesCritical
Webhook triggers executing automations without authPOST /api/webhook/{id} without credentials — webhooks execute automations including physical device control without any authenticationCritical
SSH add-on with host network accessSSH & Web Terminal add-on runs with host network — SSH access to HA add-on = host filesystem and network access, not just HA containerCritical
trusted_networks bypassing authenticationReview configuration.yaml trusted_networks — overly broad ranges (e.g., 10.0.0.0/8) allow unauthenticated access from large network segmentsHigh
HA exposed directly on internet without VPNPort 8123 publicly accessible — exposes login page to brute force and any unpatched CVEs to exploitationHigh
Add-on with full_access: trueAdd-on configuration review — full_access gives access to entire HA filesystem including secrets.yaml containing credentials for all integrationsHigh

Automate Home Assistant Security Testing

Ironimo tests Home Assistant deployments for long-lived access token scope providing permanent API access to all physical device controls, webhook endpoints triggering automations without authentication enabling arbitrary home automation execution, SSH and terminal add-ons with host network access enabling container escape, trusted_networks authentication bypass for overly broad network ranges, direct internet exposure of port 8123 without VPN or proxy authentication, and secrets.yaml accessibility via over-privileged add-on filesystem access.

Start free scan