BETA — Open to testers. Tell us what to fix on @vomehome or via a tester code.

Agent API & MCP — Security Review

VomeHome lets an AI coding agent read and control your Home Assistant. That is a genuinely sensitive capability, so this page documents how it is constrained, what we deliberately do not allow, and the limitations we currently accept. It is written to be useful to someone deciding whether to trust it — which means the known weaknesses are listed alongside the controls.

For the wider platform, see Security & Privacy. Findings and fixes below are dated so you can tell how current this is.


The Design in One Sentence

The agent never receives a Home Assistant credential. It gets a revocable VomeHome API token, and every Home Assistant read or write is brokered through our API where the policy is enforced server-side.

This is the property everything else rests on. A compromised or misbehaving agent cannot bypass the broker to reach Home Assistant directly, because it was never given anything that would let it — the Home Assistant token stays on our server and is never returned to a caller. A read-only token genuinely cannot change anything; that is not a client-side setting an agent could ignore.

Authorisation & Scopes

  • Reads by default, writes by grant. A token is read-only unless you explicitly grant more. Service calls require ha:write; rewriting automations, scripts and scenes requires the separate ha:config; creating instances requires the account-wide instances:write.
  • Write permissions are per instance, not per account. A token granted write access to one Home Assistant cannot write to another, even though both belong to you.
  • Reads are per instance too. An instance you do not grant is invisible to the token — not merely read-only. A key issued for one Home Assistant cannot enumerate the states, automations or logs of another. Granting control or edit-automations implies read for that same instance, because an agent has to see state before acting on it.
  • Ownership is re-checked on every call, not cached from when the token was minted.
  • Sensitive domains are refused even with write access. Locks, alarm panels, covers, climate, vacuums, valves, water heaters, mowers and cameras are blocked server-side. A generic call such as homeassistant.turn_on is checked against the domain of every target entity, so it cannot be used to reach a blocked domain indirectly.
  • Revocation is immediate and self-service, from the API tokens page.

Credential Handling

  • Tokens are stored only as a SHA-256 hash. The plaintext is shown once, at creation, and cannot be recovered afterwards — not by you, and not by us.
  • 256 bits of entropy from a cryptographically secure generator, so the hash need not be slow to be safe against guessing.
  • Credentials are never written to logs, and never used as a rate-limit or cache key in raw form — where a token identifies a request internally, it is hashed first.

Multi-Tenant Session Isolation

The hosted endpoint serves many customers from one process, so isolation between sessions is a first-order concern.

  • One session, one token. Each connection builds its own isolated context — its own clients and its own view of which instances exist. There is no shared mutable state between sessions.
  • Sessions are bound to the token that opened them. Presenting a valid session identifier with a different token is refused. The comparison is constant-time, so the binding cannot be probed by measuring response timings.
  • Session identifiers are cryptographically random (UUIDv4), not sequential or guessable.
  • Idle sessions are reaped after 30 minutes, rather than lingering until a client happens to disconnect.
  • The instances a session can see come from its own token, resolved at connection time — not from a shared list.

Auditability

  • Every brokered call is logged — allowed and denied, read and write. A refusal is as interesting as a success when you are reconstructing what an agent did.
  • Each entry records the user, the token used, the target instance, the method and path, the domain and service where applicable, the decision, the reason for a refusal, and the resulting status code.
  • Auditing never breaks the request it records. A logging failure is swallowed and reported internally rather than failing the call — a deliberate trade-off favouring availability, with the consequence noted under Limitations.

Transport & Host Controls

  • TLS in public, loopback in private. The endpoint is served over HTTPS with HSTS; the process itself binds only to loopback and is never directly reachable from the internet.
  • Brokered calls stay on the host. Internal API traffic travels over the loopback interface rather than out to the public internet and back.
  • Reduced-privilege service. The process runs as an unprivileged user with NoNewPrivileges, a private /tmp, a read-only view of home directories, and a strict read-only system view.
  • Request bodies are capped before parsing, so an oversized payload is rejected rather than buffered.
  • Per-token request quotas (see Finding 2 below).

Review Findings — August 2026

Both issues below were found during the review of the hosted endpoint, before it had any real usage, and both are fixed. They are published because a security page that lists only successes is not evidence of much.

Finding 1 Shared request quota across hosted users Resolved

Severity: low — availability only. No impact on confidentiality or integrity; permissions and audit logging were unaffected throughout.

Request quotas were enforced per client network address. When the MCP server moved from running on each user's own machine to being hosted by us, every user's brokered calls began arriving from a single address and therefore shared one quota — so a single busy agent could have exhausted it for other users. Quotas for token-authenticated internal traffic are now keyed on the token itself, giving each user their own. Public traffic is still keyed per address, so the change cannot be used to escape a quota.

Finding 3 Read access was account-wide, not per instance Resolved

Severity: moderate — over-broad read access within a single account. No cross-account exposure: a token could only ever reach Home Assistant instances belonging to its own owner, and write protection was never affected.

Ticking instances when creating a key granted write access to those instances, but read access was a single account-wide permission. Every key could therefore read every Home Assistant on its owner's account — states, entities, automations and logs — including instances deliberately left unticked. Two things combined to cause it: reads were checked account-wide rather than per instance, and a key created with no explicit account-wide permissions fell back to a default that included the read wildcard. Reads are now granted and enforced per instance, and an ungranted instance is invisible to the key rather than readable. Reported by a customer who noticed an agent reading an instance they had not given it.

Finding 2 Deployment could silently drop web-server routes Resolved

Severity: low — availability only, no exposure of data or bypass of access control.

Our deployment regenerates the web-server configuration from a template. Any route added by hand outside that template was therefore removed by the next deployment. This affected the new agent endpoint and, more importantly, revealed a pre-existing latent fault in the off-site backup upload route, which had the same weakness and would have been dropped by any deployment. Both routes are now produced by the template itself, so they cannot drift out again.

Known Limitations

Current, accepted trade-offs. We would rather state them than have you discover them.

  • Tokens issued before August 2026 can still read everything. They hold an account-wide read scope, which is honoured as an all-instances wildcard so they keep working. Only newly issued tokens get per-instance read gating. If you minted a key before this change and want it limited to specific instances, re-save its permissions on the API tokens page, or revoke and re-issue it.
  • Authentication is a bearer token, not OAuth. The Model Context Protocol specification expects remote servers to implement OAuth 2.1. We do not yet. In practice this means your token sits in a configuration file on your own machine, in plain text, with the same handling requirements as any other API key — protect the file, and revoke the token if you suspect exposure. Adding OAuth is on the roadmap; it requires an authorisation server and a consent flow for choosing which instances a client may reach, so it is a substantial piece of work rather than a quick change.
  • Tokens do not expire by default. They remain valid until you revoke them. Set an expiry when creating one if that suits your use better.
  • Session state is held in memory. A restart of the service ends open sessions; clients reconnect automatically. It also means the endpoint runs as a single process, which is a capacity consideration rather than a security one.
  • The sensitive-domain list is a block list. Anything not on it is permitted once write access is granted. A newly invented integration category would not be blocked until the list is updated.
  • Audit logging is best-effort. It is designed never to break the request it records, so a database failure could in principle lose an entry rather than refuse the call.
  • An agent with write access can act. No amount of scoping changes the fact that granting ha:write lets an AI agent turn things on and off in your home. Grant the narrowest scope that does the job.

Reporting a Problem

If you find a security issue in the agent API or MCP endpoint, please email security@vome.io. We will investigate every legitimate report and ask for reasonable time to fix before public disclosure. The MCP server itself is open source and can be read at github.com/Vortitron/home-assistant-mcp.

Reviewed August 2026, covering the hosted MCP endpoint and the brokered agent API. Updated as controls change.