Security model

HArvest's security boundary is the token. A token grants access to a specific, server-defined list of entities with a specific capability (badge, view only, or control). The HA long-lived access token (your HA credentials) never leaves the server. Visitors interact only with the HArvest WebSocket proxy, which forwards only the exact service calls the integration permits.

Tokens are independent - compromising one doesn't affect others. They can be revoked instantly from the panel, dropping all active sessions for that token within seconds.

What HArvest protects

  • Your HA credentials (long-lived access token) never reach the client
  • Visitors can only access entities explicitly listed in their token
  • Write access is separate from read access and must be explicitly granted
  • Tier 3 domains (alarm panels, locks, cameras, scripts, automations) are blocked at token creation time - they cannot be added regardless of capability setting
  • Only service calls in the allowed services whitelist can be executed (exception: harvest_action entities bypass this whitelist - see harvest_action)
  • Tokens can be restricted to specific websites, IP ranges, and time windows
  • All auth events and service calls are logged with session, origin, and entity details
  • Tokens can be paused or revoked instantly, dropping all active connections

What it doesn't protect

Being clear about limitations is important for making good deployment decisions.

  • Token ID in page source - the token ID is visible in your page HTML. Anyone who copies it can use it from a non-browser tool (curl, etc.) as long as the origin check passes or isn't set. Use HMAC signing and strict origin restrictions for write-access tokens.
  • Browser developer tools - visitors with DevTools open can inspect the WebSocket traffic, see entity IDs and states, and manually send valid commands for entities in the token scope. You cannot prevent this for in-scope entities on a control-enabled token.
  • Origin header - the origin check runs on the HTTP Origin header, which browsers send reliably but non-browser tools can forge. Origin restriction is effective for browser-based attacks, not for determined attackers with script access to your token ID.
  • Network-level attacks - DDoS, infrastructure flooding, and similar attacks are outside HArvest's scope. Use Cloudflare, fail2ban, or similar tools at the infrastructure level.
  • Your HA instance security - HArvest doesn't protect a weak HA installation. Keep HA updated, use a strong password, enable MFA, and don't use a weak long-lived access token.

Token security options

All security options are configured per-token in the widget detail screen or wizard.

Capability

Set per entity in the token. Badge - compact pill indicator, state only, no service calls, no companions, no gestures. View only - full card, state updates only, no service calls. Control - full card, state updates plus allowed service calls for that domain.

Excluded attributes

Per entity. Prevents specific HA state attributes from being sent to the widget. Useful for hiding location coordinates, internal IDs, or other attributes you don't want visible in network traffic.

HMAC authentication

By default, knowing a token ID is sufficient to connect to it from any page (subject to origin restrictions). HMAC signing adds a second factor: the connecting page must prove it has a shared secret.

When HMAC is enabled on a token, the widget signs each auth message with the secret using HMAC-SHA256. The server verifies the signature and rejects auth requests that don't include a valid, non-stale signature. A timestamp is embedded in the signature payload: requests older than 60 seconds, or more than 5 seconds in the future, are rejected. The 5-second forward tolerance absorbs minor clock drift between the host that signs the message (typically the WordPress server) and the Home Assistant host.

The token-secret attribute on <hrv-card> is the HMAC secret. It appears in your page source, so it doesn't hide from someone looking at your page. The purpose of HMAC is to prevent someone who copied your token ID from using it on a different page - the token ID alone isn't enough, they also need the secret.

Set a token secret in the widget detail screen under Security. Minimum 16 characters. Use a random string.

Token secret storage

The token secret is stored as plaintext in HA's .storage/harvest_tokens file on your HA instance. This is by design - HMAC verification requires the plaintext secret.

The secret also appears as a data-token-secret attribute in the rendered HTML of any page that uses the token-secret shortcode parameter (WordPress) or token-secret attribute (standalone embed). Anyone who views the page source can read it. The security value of HMAC is that copying the token ID alone is not sufficient to connect from a different page - the attacker also needs the per-page secret. It is not a hidden credential.

Origin restriction

Restrict which websites can connect using a token. Add allowed origins in the widget detail screen under Origins.

Each origin entry is a full URL without path, e.g. https://mysite.com. Optionally add path restrictions to allow only specific pages: https://mysite.com + path /smart-home means only pages at https://mysite.com/smart-home* can connect.

The origin check uses the HTTP Origin header from the WebSocket upgrade request. Browsers send this reliably. Non-browser tools can omit or forge it - if the Origin header is absent, the check is skipped by default (the page_path check in the widget auth message provides a soft verification).

Allow any origin disables origin checking entirely for that token. Use only for read-only public data tokens where security through obscurity is acceptable.

Access schedules

Restrict when a token can be used to specific days of the week and hours of the day. Outside the schedule, the token behaves as if it's paused - existing sessions are dropped and new connections are rejected.

Configure schedules in the widget detail screen under Schedule. Set the timezone using an IANA timezone name (e.g. America/New_York, Europe/London). The server evaluates all schedule checks in the token's configured timezone.

Example use: a public kiosk widget that only needs to be active during business hours, or a token for a specific event that should only work on event day.

IP restriction

Restrict connections to specific IP ranges using CIDR notation. Set in the widget detail screen under Security.

192.168.1.0/24      # home network
203.0.113.0/24      # office network
198.51.100.42/32    # single IP

IP restriction requires that your reverse proxy forwards the real client IP. Configure trusted_proxies in HArvest's integration settings (Settings > HArvest > Trusted proxies). HArvest reads its own list, not the one in HA core's configuration.yaml:

trusted_proxies:
  - 127.0.0.1        # localhost (nginx/Caddy running on same host)
  - 172.16.0.0/12    # Docker network
  - 104.16.0.0/12    # Cloudflare edge (if Cloudflare is in front)

For multi-hop deployments (e.g. Cloudflare → nginx → HA), list every intermediate proxy. HArvest walks X-Forwarded-For from right to left, skipping trusted hops, and uses the first untrusted entry as the real client IP. If any intermediate hop is missing from the trusted list, HArvest stops at that hop and treats it as the client.

Without trusted_proxies configured

If trusted_proxies is not configured, HArvest treats the direct TCP peer as the client and ignores X-Forwarded-For. Behind a reverse proxy this means HArvest sees the proxy's IP for all requests, so IP restrictions will either block all traffic (if the proxy IP isn't in the allowlist) or allow all traffic from anywhere (if it is). Configure trusted_proxies before relying on IP restrictions.

Session controls

Sessions automatically expire and require renewal. This limits how long a stale session can stay active after a token is compromised.

ControlDefaultDescription
Session lifetime60 minutesDuration before the widget must send a renewal request
Max session lifetime24 hoursMaximum total session duration via renewals
Absolute cap72 hoursHard expiry regardless of renewals - applies globally to all tokens
Max concurrent sessionsunlimitedSet a per-token limit to prevent session flooding

For security-sensitive tokens, use shorter session lifetimes (15-30 minutes) so compromised sessions expire quickly.

Kill switch

The global kill switch in Settings > Sessions immediately drops all active sessions across all tokens and blocks new connections. It stays active until you turn it off. Use this if you suspect a widespread compromise.

Individual tokens can be paused (drops sessions, blocks new connections for that token) or revoked (permanently disabled) from the widget detail screen without affecting other tokens.

Hardening checklist

Recommended practices by use case:

Badge or read-only public widgets (sensor displays, status boards)

  • Set capability to Badge (minimal pill indicator) or Read (full card) for all entities
  • Add an origin restriction for the embedding website
  • Set an expiry date (90 days and rotate, or use a schedule)
  • Use entity aliases to avoid exposing real entity IDs in page source

Interactive widgets (lights, climate, covers)

  • Use the narrowest entity scope possible - don't include entities you don't need
  • Add strict origin restrictions with path restrictions
  • Enable HMAC signing with a strong random secret
  • Set session lifetime to 15-30 minutes
  • Consider IP restrictions if your visitors come from predictable networks
  • Enable access schedule if the widget is only needed at certain times
  • Monitor the activity log for unusual patterns

High-traffic public widgets

  • Set max_sessions to limit concurrent connections per token
  • Consider per-token rate limit overrides to constrain command frequency
  • Use Cloudflare or a CDN in front of HA to absorb traffic spikes

HA event bus events

HArvest fires events on the HA event bus for use in automations. Configured in Settings > HA Event Bus.

EventDefaultPayload
harvest_suspicious_origin Enabled token_id, origin, source_ip
harvest_token_revoked Enabled token_id, label, reason
harvest_session_limit_reached Enabled token_id, label
harvest_flood_protection Enabled session_id, origin
harvest_session_connected Disabled session_id, token_id, origin
harvest_auth_failure Disabled token_id, origin, error_code

harvest_session_connected and harvest_auth_failure are disabled by default because they fire frequently on active sites and can generate significant HA log volume. Enable them for auditing or when investigating a specific issue.

Example HA automation to notify on suspicious origin:

automation:
  trigger:
    platform: event
    event_type: harvest_suspicious_origin
  action:
    service: notify.mobile_app
    data:
      message: >
        HArvest: suspicious origin {{ trigger.event.data.origin }}
        for token {{ trigger.event.data.token_id }}