Repository navigation
Releases
v0.29.0 — nodered and neonplan namespaces, entities_remove_entity, tool output fixes
354 tools across 32 domains (was 330 / 30): new nodered and neonplan namespaces, entities_remove_entity.
Security: the Node-RED write tools are off by default. They appear only after you turn on the add-on option nodered_write_enabled — and turning it on means full trust: a deployed flow runs any code inside the Node-RED add-on, which holds a Supervisor token with the manager role (it could change nexus's own options, e.g. lift read_only) and can read Node-RED's stored node secrets. Reading flows works either way (ADR-0007).
Added
-
noderednamespace (12 new tools; the 5 write tools only with the new
add-on optionnodered_write_enabled, see below) for the
community Node-RED add-on (slug*_nodered). Nexus talks to
Node-RED's Admin API on127.0.0.1:46836, the loopback address the add-on
itself pins in itsconfig.js(withadminAuthoff — the add-on leaves
login to Home Assistant's ingress and its own nginx). Nexus reaches it
thanks tohost_network: true, so no Home Assistant password and no
new permission are needed. The Supervisor API is
only used to find the add-on and check that it is started.- Read:
nodered_list_flows(tabs and subflows with node counts and the
deployrev),nodered_get_flow(one tab with all its nodes),
nodered_search_nodes(by type and/or name),nodered_find_entity_usage
(which nodes and flows reference a Home Assistant entity — exact
selector, template/function text, substring and regex selectors),
nodered_get_info(Node-RED version, settings, run state, installed
palette modules),nodered_get_diagnostics(Node-RED's diagnostics
report),nodered_list_backups. - Write:
nodered_create_flow,nodered_update_flow(replaces the
flow's nodes; values hidden by the placeholder are restored from the
current flow),nodered_delete_flow,nodered_set_flow_enabled,
nodered_restore_backup. Each write first saves all flows to nexus's
private/data/nodered_backups/(newest 30 kept) and then deploys the
whole configuration with therevit read, so Node-RED refuses the
write (rev_conflict) when someone deployed in the meantime; an
optionalexpected_revchecks against therevyou read earlier. Only
changed flows restart. A failed backup stops the write. - Safety checks before anything is deployed: node types must be installed
in Node-RED (an unknown type would stop every flow), node ids must not
clash with other flows, and a new node must not contain the redaction
placeholder. All five write tools are destructive, soread_only
mode hides them too.nodered_restore_backupalso checks the backup's
node types and that it was taken from this add-on. - New add-on option
nodered_write_enabled(default off, ADR-0007):
until it is on, the five write tools are hidden and refused like in
read_only. Turning it on means full trust: a deployed flow runs
any code inside the Node-RED add-on, which holds a Supervisor token
with themanagerrole (it can change nexus's own options, e.g. lift
read_only) and can read Node-RED's stored node secrets
(credential_secret,flows_cred.json). Reading flows works either way. - Secrets: Node-RED never returns node credentials. Every answer
replaces with the placeholder: fields whose key looks secret, env and
HTTP header values whose name looks secret or isAuthorization,
Bearer/Basicvalues,user:pass@URLs, and whole JSON strings
holding any of these (header nameskeyType/keyValuestay visible).
nodered_update_flowrefuses (secret_redirect_blocked) to change the
type of a node whose hidden values it restores, or thehost/url/
broker/port/server/proxyof a node with hidden values or stored
credentials, unless the secret is given explicitly; a node with stored
credentials cannot change its type. A destination chosen at runtime
(msg.url, function code) is out of reach of this check. Secrets typed into
function code or free text are not detected. nodered_find_entity_usageskips selector regexes over 200 characters
or with nested quantifiers (skipped_patterns); thenodesargument
is limited to 4 MiB of JSON.
- Read:
-
neonplannamespace (11 new tools, 32 namespaces) for
the HACS integration NeonPlan 3D (neonplan3d, built against 3.0.6):
the AI can read, check and edit the 3D house plan through Home
Assistant's WebSocket API. No add-on option or permission is needed;
saving needs an admin token, as in the NeonPlan editor.- Read:
neonplan_get_status(installed/loaded, version, whether a
license is active, packs, plans, open NeonPlan screens),
neonplan_list_plans,neonplan_get_plan(whole building or a summary
of floors, rooms witharea_id, openings and bound entities),
neonplan_validate_plan,neonplan_list_restore_points,
neonplan_suggest_bindings(which entities of a room's area fit which
plan field: blinds, contacts, lights, climate sensors, TV, vacuum,
cameras, locks — a proposal, nothing is written). neonplan_validate_planchecks what NeonPlan's server does not: the
structure its schema requires, duplicate ids, an opening'sroom_id,
edgeand position on its wall, entity fields, items outside every
room, and against the live Home Assistant everyarea_idand entity
id. It warns aboutnullentity fields that NeonPlan binds
automatically from the area.- Write (destructive, hidden in
read_onlymode):neonplan_save_plan
(whole building),neonplan_patch_plan(JSON Patch operations — the
usual way to edit; an operation on path"", the whole plan, needs
overwrite=truelikesave_plan),neonplan_create_restore_point,
neonplan_restore_restore_point,neonplan_upload_background_image
(png/jpeg/webp from/configor base64, up to the 8 MiB limit; the
integration deletes an image no plan references at its next start). - NeonPlan's own save replaces the whole plan and has no conflict check.
Saves through nexus therefore validate first (errors block), refuse to
replace a plan with floors unlessoverwrite=true, check an optional
expected_revision, take a restore point, check the revision again
right before saving, and read the plan back. An editor left open with
unsaved changes can still overwrite a save:neonplan_get_statuslists
the open screens. - Without the integration every tool answers
neonplan_not_installed.
On a version other than 3.0.x reads add a warning and writes are
refused (neonplan_version_unsupported) unlessforce=true.
- Read:
-
entities_remove_entity(1 new tool, 354 in total) — removes an
entity's entry from the entity registry (config/entity_registry/remove).
An entity its integration still provides (a state that is not a restored
placeholder) is refused withentity_still_providedunlessforce=true,
because the integration would re-create the entry at its next reload.
Changed
devices_remove_devicenow removes the whole device
(config/device_registry/remove, Home Assistant 2026.x), not only its
link to one config entry as the description used to say. In practice the
old command (config_entries/remove_device) does not exist in Home
Assistant 2026.11, so the tool never worked there. It checks first that
the device belongs toconfig_entry_id, falls back to
config/device_registry/remove_config_entryon an older Home Assistant,
and returns{"removed": true, ...}ordevice_not_found/
config_entry_not_in_device/ws_error.
Fixed
history_get_state_historyandhistory_get_logbookwithhoursabove
24 returned only the first day: withoutend_timeHome Assistant ends
the window one day after its start. Both now sendend_time= now.areas_delete_areadeleted the area but then failed with
"structured_content must be a dict or None. Got str: 'success'": Home
Assistant answers that command with the plain string"success". It now
returns{"deleted": true, "area_id"}.- The same kind of failure after the work was done, found by a new
contract test:blueprints_delete_blueprint,voice_delete_pipelineand
voice_set_preferred_pipeline(Home Assistant answers with no payload,
which the MCP client rejected as missing structured output), and
system_restart_ha,system_stop_ha,system_create_backupand
themes_set_active_theme(a service call answers a list of changed
states). They now return{"deleted": true, ...},
{"preferred_pipeline"}or{"service", "changed_states"}. - New test
tests/test_tool_output_shapes.py: every tool declared to
return an object may pass a Home Assistant answer through unchanged only
when that answer is known to be an object.
v0.28.1 — hacs_* tools on the built-in Marketplace (HA 2026.11)
Fixed
hacs_*tools on Home Assistant 2026.11+. HA 2026.11 builds HACS into
core as the Marketplace and thehacs/*WebSocket commands are gone:
live on HA 2026.11.0hacs_list_hacs_critical_updatesanswered
WS error: {'code': 'unknown_command'}, and so did every otherhacs_*
tool. Each tool now sends the Marketplace command first
(marketplace/repositories/list,marketplace/repository/info,
marketplace/repository/install,marketplace/repository/uninstall,
marketplace/repository/refresh,marketplace/repositories/add) and
falls back to thehacs/*command only when HA answers
unknown_command, so HA before 2026.11 with HACS keeps working. Tool
names and parameters are unchanged.- Marketplace errors come back as
{"error", "code", "backend", "hint"}
instead of a raw WebSocket error. Until the Marketplace's first-run
warning is accepted (per user: the one that owns nexus's token),
installs, updates and new custom repositories answer
warning_not_acceptedwith the hint to open Settings → Marketplace and
accept it. Nexus never accepts it for you. - Add-on mode: nexus reaches HA through the Supervisor proxy as the
Supervisor system user, not as the owner, and the Marketplace keeps the
warning acceptance per user. That user cannot accept it in the UI, so
install, update and add custom repository through nexus answer
warning_not_acceptedeven after the owner accepted it. The hint says so
and names what works: doing it in Settings → Marketplace or Settings →
Updates, or, for an installed repository, an automation that runs
update.installon itsupdate.*entity (automations run without a
user, so the owner's acceptance counts). Nexus prepares or runs such an
automation only when the owner explicitly asks for it, never on its own
after awarning_not_acceptedanswer. Listing and reading work.
Standalone nexus with a user's token is not affected. Adding a custom repository
also needs a GitHub account connected to the Marketplace
(github_not_connected). A repository that replaces a built-in
integration (replaces_built_in) has to be installed from the UI. When
neither the Marketplace nor HACS answers, the tools say so instead of
returningunknown_command. hacs_install_hacs_repository,hacs_uninstall_hacs_repository,
hacs_update_hacs_repositoryandhacs_add_custom_repositoryreturn
{"status": "ok", "backend": "marketplace"|"hacs"}on success, on both
backends. The Marketplace answers these commands withresult: null, and
a barenullmade the MCP call fail output validation ("Invalid
structured content") after HA had already done the work. With HACS they
used to return{}.- A timeout or connection error is no longer reported as a rejection by
the Marketplace:codeis null,backendis null unless already
established, and the hint says to check the repository with
hacs_get_hacs_repositorybefore retrying (an install may still be
running in HA). A probe that gets no answer is never re-sent as a
hacs/*command. hacs_install_hacs_repositoryandhacs_update_hacs_repositorywait up
to 120 s for the install to finish (both backends) instead of the 10 s
every other WebSocket command gets. HA answers an install only after the
download (up to 100 MB in the Marketplace) is written and the integration
reloaded, so with 10 s they would usually have reported "No answer"
while HA was still installing.hacs_uninstall_hacs_repositorygets the
same 120 s: with GitHub connected the Marketplace first re-fetches the
repository. Otherhacs_*commands keep 10 s. An MCP client with a
shorter timeout of its own may still give up first; the tool
descriptions say to check withhacs_get_hacs_repositorythen.- Only connection, timeout and protocol failures (
RuntimeError,
OSError, websockets errors, a bad JSON frame) are reported as "No
answer from Home Assistant". Other exceptions, i.e. bugs in nexus, are
no longer hidden behind that message.
Changed
ha_client: a failed WebSocket command raisesHAWebSocketError, a
RuntimeErrorsubclass carrying HA's errorcodeandmessage. The
message text is unchanged.ha_client._ws_call_timeout(msg_type, timeout, /, **fields)—_ws_call
with its own time bound;timeoutis positional-only, so a command field
of that name still reaches HA.dashboards_add_dashboard_resourcedescription: Marketplace resources
live under/local/community/(/hacsfiles/was HACS).
v0.28.0 — areas_update_area (rename areas)
Added
areas_update_area(1 new tool, 330 in total) — renames an area (room)
or changes its icon, floor or voice aliases through HA's area registry.
Only the fields you pass are sent, so everything else keeps its current
value. The area_id does not change, so devices and entities assigned to
the area stay assigned. Calling it with no field to change is rejected
before anything reaches Home Assistant. The icon and floor cannot be
cleared with this tool.
Changed
areas_create_area"Not for" now points toareas_update_areainstead
of saying that areas cannot be updated.
v0.27.0 — adguard filter-list tools
Added
- Filter-list management in the
adguardnamespace (3 new tools, 329 in
total):adguard_add_filter— adds a blocklist or allowlist byhttps://URL
and waits up to 180 s while AdGuard downloads and parses it. HA's
adguard.add_urlservice gives up after 10 s, so large lists (for
example a 12 MB / 560k-rule list) were never added. A URL that is
already on either list returnsalready_existswithout touching
AdGuard.http://URLs, local file paths and URLs with credentials are
rejected.adguard_remove_filter— removes one list by its exact URL; an unknown
URL returnsnot_found(AdGuard itself would answer OK and do nothing).adguard_set_filter_enabled— turns one list on or off, keeping its
name and URL; enabling also waits up to 180 s for the download.- All three return the list after the change and the sum of rules in the
enabled lists. They are write tools, soread_onlymode hides them.
Changed
adguard_set_dns_config"Not for" now points to the new filter tools
instead of HA'sadguardservices.
v0.26.0 — adguard namespace
Added
- New
adguardnamespace (3 tools) for the AdGuard Home add-on
(hassio-addons, slug*_adguard). HA's ownadguardintegration only
offers filter-list services and switches; these tools reach the DNS
settings too:adguard_get_config— status, DNS settings (dns_info), filter lists
(custom rules as a count plus the first 20), safe search, access lists
and dhcp on/off + interface (no leases or MAC addresses).adguard_set_dns_config— partial update of an allowlist of DNS fields
(upstream/fallback/bootstrap/PTR resolvers, upstream mode, rate limit,
cache,dnssec_enabled, blocking mode, upstream timeout). Any other key
is rejected. When resolvers change, AdGuard's own upstream test runs
first and nothing is written unless every server answersOK. Returns
the settings before and after the write.adguard_check_host— whether a domain is filtered, and by which rule
and list.
- How it connects: the AdGuard add-on serves its API on
127.0.0.1:45158(host network), the same address HA Core'sadguard
integration uses. nexus already runs withhost_network: true
(ADR-0004), so this needs no new add-on permission or role. The
Supervisor is only asked where the add-on is and whether it is running.
v0.25.1
Fixed
- HACS write tools (
hacs_update_hacs_repository,hacs_install_hacs_repository,
hacs_uninstall_hacs_repository,hacs_add_custom_repository) were sending
WebSocket commands HACS doesn't implement (hacs/repository/install,
hacs/repository/update,hacs/repository/uninstall— all answered
{"code": "unknown_command"}when called live against HA 2026.9.3). They now
use HACS's real API:hacs/repository/download(install and update — HACS
has no separate command for either),hacs/repository/remove, and
hacs/repositories/addfor custom repositories.hacs_get_hacs_repository
now sends therepository_idfieldhacs/repository/infoactually expects
instead ofrepository. Update is nowhacs/repository/refresh(re-fetch)
followed byhacs/repository/info(readavailable_version) and
hacs/repository/downloadwith that version. Tool names and parameters are
unchanged.
v0.25.0
Added
- Pre-built, cosign-signed OCI image on ghcr.io (ADR-0005) —
config.yaml
now declaresimage: "ghcr.io/fistacho/nexus-agent"(single multi-arch
manifest tag, no{arch}placeholder), so Supervisor pulls the add-on
instead of building it locally..github/workflows/publish-image.yml
builds, pushes and cosign-signs both the per-arch source images
({arch}-nexus-agent) and the combined manifest on everyvX.Y.Ztag,
then verifies every one of them is actually pullable before the run is
allowed to go green. See Docker Image for the
docker run/cosign verifycommands. server.json— an MCP Registry server descriptor (schema-validated by
.github/workflows/mcp-registry.ymlon every tagged release from day
one; actual publication to the registry is deliberately held back until
nexus 1.0.0, since the registry itself is still in preview). Its OCI
package declares the same ghcr.io image and tag asconfig.yaml.DOCS.md— full operational reference (add-on options, security posture,
ESPHome) split out of README so the README itself stays focused on
getting started.apparmor.txt.draft— a draft AppArmor profile for the add-on; not yet
active (noapparmor: trueinconfig.yaml), kept as a draft until it's
been exercised against a real install.- A literal
LABEL io.modelcontextprotocol.server.namein the Dockerfile —
the MCP Registry's ownership-verification mechanism for the pushed image,
matchingserver.json's ownname.
Changed
services_call_servicegains aconfirmparameter (defaultFalse,
ADR-0006): required to actually run one of eight guarded HA/Supervisor
services —homeassistant.restart/stop,hassio.host_reboot/
host_shutdown/restore_full/restore_partial,update.install,
group.remove— closing the gap where a model told
confirmation_requiredby a dedicated tool (e.g.system_restart_ha)
could simply reach the same effect through the generic dispatcher
instead. Withoutconfirm=True, the call performs no I/O and returns a
confirmation prompt naming the dedicated tool to prefer, when one exists.
Ignored for every other service.ws_call_servicenow outright refuses those same eight guarded services
({"error": "guarded_service_refused", ..., "use": "services_call_service"})
instead of acceptingconfirmitself — none of them return a response
payload underreturn_response=Trueanyway (HA's ownServiceRegistry
rejects that combination before the handler runs).domain/serviceinservices_call_serviceandws_call_servicemust
now match[A-Za-z0-9_]+— refused with{"error": "invalid_service_name"}and zero I/O otherwise. Closes a case-folding/
path-injection gap (ADR-0006 F2–F4) where"HASSIO"/"ADDON_STOP", or a
servicevalue containing?,#or a..segment, could reach a
different Supervisor operation than nexus's ownself_protectionguard
was comparing against.areas_control_area'sactionis now restricted toturn_on/
turn_off/toggle(previously passed through unchecked) and
services_send_notification's dottedtargetmust now name the
notifydomain — both were side doors that could reach an arbitrary
domain.servicecall under cover of a narrower-looking tool (ADR-0006
D6).- Breaking: the add-on now ships as a pre-built image for
aarch64and
amd64only.armhf/armv7/i386have no supported, CI-buildable path
to a signed image (the build tooling used for ghcr.io only has native
runners for the two architectures above) — users on those architectures
keep working on 0.24.x, which still builds locally via the Dockerfile,
but cannot update past it.
Security
- Every path segment
ha_client.pysplices into an HA REST URL from a
caller-supplied value (entity_id, automation/script config IDs, event
types, history/logbook timestamps) is now validated or percent-encoded
before the request is built —entity_idagainst HA Core's own
valid_entity_idpattern, everything else against a generic
disallow-list (/,\,?,#, literal.., checked both raw and
percent-decoded once). Same treatment for the add-on/backupslug
argument on all 12supervisor_*tools that take one, checked before
self_protection/confirm. Closes the same class of bug as the
services_call_servicename-validation fix below (a value containing
?,#or..could otherwise resolve to a different endpoint than the
one named). self_protection.blocked_hassio_service_callnow comparesdomain/
servicecase-insensitively, closing the self-protection bypass where
"HASSIO"/"ADDON_STOP"reached the same Supervisor operation as
"hassio"/"addon_stop"without tripping the guard.- The add-on's HTTP transport now validates the
Originheader on every
request per the MCP spec's Streamable HTTP security requirements: an
Origin that doesn't match nexus's own known LAN origins (or the
operator'sNEXUS_ALLOWED_ORIGINSallow-list) gets403 Forbidden, as
does a missing-but-nullor repeated Origin header. A request with no
Origin header at all is allowed (most non-browser MCP clients never send
one), and both HA's ingress proxy andGET /healthare exempt (ingress
is already a trusted, peer-verified channel;/healthis the Supervisor
watchdog's unauthenticated probe).
Fixed
run.sh's standalone mode (no Supervisor, e.g. the ghcr.io image run via
plaindocker run) now honors a caller-supplied-e HA_URL/
-e HA_CONFIG_PATHinstead of silently discarding them — every release
through 0.24.0 unconditionally forcedHA_URL=http://supervisor/core
regardless of mode.- Several WebSocket-level ESPHome Device Builder tests that drove a real
localwebsockets.serveserver against the client on a worker thread
(a source of intermittent CI flakiness) now run against an in-process
fake connection instead, for every case that doesn't specifically need
real concurrent client/server behaviour (a genuine timeout, an actual
TCP refusal).
v0.24.0
Added
- Optional
tool_modeadd-on option (fulldefault,search; env
NEXUS_TOOL_MODE) — ADR-0003 P3.searchreplacestools/listwith
discover's three lookup tools (discover_tool_search,
discover_get_tool_doc,discover_list_namespaces) plus one call-by-name
proxy per ADR-0002 annotation class (discover_call_read_tool/
_write_tool/_destructive_tool) — a much smaller catalogue for
context-constrained clients. Every hidden tool still works if called
directly by name, and stays subject toread_only/disabled_namespaces
exactly as infullmode.discovercan no longer be listed in
disabled_namespaceswhiletool_mode=search— the add-on refuses to
start rather than leavetools/listempty. Requires a restart to take
effect. discover_tool_search/discover_get_tool_docnow also return an
"access"field ("read"/"write"/"destructive", from the tool's own
MCP annotations) alongside the existing fields.
Security
supervisor_get_addonnow redacts secrets inoptionsinstead of
returning them in full to any caller with themanagerhassio_role
(nexus's own): schema-declaredpassword-typed fields, a key-name
heuristic (password/passwd/pass/pwd/secret/token/api_key/
credential/private/key— plural forms included, e.g.credentials,
secrets,tokens,passwords,api_keys— exact-token matched so
passenger/bypass_cacheare not flagged), and an in-value scan for URLs
with inline credentials. Redacted fields are listed in a new
redacted_fields: [{"path", "reason"}]on the result.supervisor_set_addon_optionspreserves previously-stored secrets when
the caller echoes the**REDACTED**placeholder back for a field, via a
read-modify-write round trip; a placeholder inside a list whose length no
longer matches the stored list is refused outright rather than guessed,
and a subsequent Supervisor error'sdetailis sanitized once any
placeholder was resolved this way (Supervisor can otherwise echo the
just-restored secret straight back in a rejected payload'sdetail).- Self-protection of the add-on (
self_protection.py, ADR-0004 D-2):
supervisor_set_addon_options,supervisor_uninstall_addonand
supervisor_stop_addonrefuse to target nexus's own add-on (fail-closed,
by the real slug resolved at startup, or the literal"self"alias
Supervisor itself resolves to the calling add-on). The same guard blocks
hassio.addon_stop/app_stop/addon_stdin/app_stdinin
services_call_serviceandws_call_service, which reach the identical
Supervisor operations without ever callingtools/supervisor.py.
supervisor_restart_addon(andhassio.addon_restart/app_restart) stay
unblocked — restarting nexus with unchanged options is not privilege
escalation. This guard only covers direct invocation; a script,
automation, YAML file, blueprint or Jinja template that reaches the same
hassio.*service indirectly through HA Core's own engine is an accepted
residual risk (owner sign-off 2026-09-27) — the actual security boundary
is the add-on's ownread_only/disabled_namespacesoptions, which none
of those indirect paths can change. See ADR-0004 "Zaakceptowane ryzyko
szczątkowe (0.24.0)".
Fixed
websockets.connect(...)'s default 10sclose_timeoutno longer adds an
extra ~10s on top of a WebSocket call's own timeout when the peer never
completes the closing handshake — bothha_client.pyand
tools/websocket.pynow cap it at 1s.- The HA WebSocket auth handshake (
auth_required->auth->auth_ok)
inha_client.pyis now bounded by the same deadline as the rest of the
call, instead of blocking forever against a peer that never completes it. - stdio mode no longer writes its startup log lines to stdout — only valid
MCP JSON-RPC messages belong there per the MCP transports spec; nexus's
own startup lines now go to stderr, same as its existing startup-abort
messages.
Changed
- E2E (
e2e.yml) is now a required status check, promoted out of
"informational" after being observed green for several releases in a row. hacs.jsonremoved. Nexus is not, and was never validly, a HACS
integration (nocustom_components/, would not pass HACS's "one
integration per repository" validation) — see ADR-0005: the add-on
channel plus a future OCI image and MCP Registry entry replace the
previously assumed HACS channel.
Internal
- CI's test job now runs under
pytest-xdist(-n auto);tests/ test_ws_timeouts.pymarks its real-socket casesslowso the fast dev
loop can exclude them.
v0.23.0
Changed (important for operators)
- The add-on now uses
host_network: true(ADR-0004). ESPHome
compile/validate/OTA upload only work through the Device Builder add-on's
trusted-peer ingress site, reachable at127.0.0.1:<its ingress_port>— the
same path Home Assistant Core itself uses — and that address is only
reachable this way when nexus shares the host's network namespace.
Consequence: the add-on's security rating drops by one point, and Docker no
longer mapsports:fromconfig.yaml— nexus now reconstructs the Network
tab's LAN-port semantics itself at startup (default0.0.0.0:7123; pick a
different port in the Network tab and nexus opens the LAN on that port
instead, ingress stays fixed at 7123 either way; disable the port there and
only ingress remains reachable, with no LAN socket at all). Theport
option itself is unchanged and still scheduled for removal in 1.0.0 — do not
work around any of this by editing it. - The ESPHome Device Builder's ingress URL is now discovered automatically
through the Supervisor (ingress_port) instead of a fixed guess. The
previous defaultESPHOME_DASHBOARD_URL=http://homeassistant.local:6052has
been removed — standalone (non-add-on) installs must now set it explicitly
foresphome_*compile/validate/upload tools to work. esphome_*tools now return stable error codes instead of ad-hoc messages:
esphome_not_configured,esphome_unreachable,esphome_auth_required,
esphome_protocol_error,esphome_command_failed,esphome_unsupported_option,
timeout.esphome_unreachable/esphome_auth_requirednow carry a
diagnosisblock (mode, dashboard URL, slug, advice) instead of leaving the
caller to guess what to check next.esphome_upload_devicenow requiresconfirm=True(newconfirmparameter,
defaultfalse) — OTA-flashing a device is not undoable the way a config
edit is. Without it, the tool performs no I/O and returns
{"error": "confirmation_required", "message", "action"}.esphome_compile_device(only_generate=True)now returns
{"error": "esphome_unsupported_option", ...}instead of silently compiling
anyway — the Device Builder's ingress site has no equivalent of ESPHome
CLI's--only-generate.- A compile/upload call that times out now returns
job_may_still_be_running: true— the underlying ESPHome job is not
cancelled by the timeout, so retrying blind can race a job already running;
checkesphome_get_addon_logsfirst.
Added
esphome_write_confignow returnswarning+external_sourceswhen the
written config references remoteexternal_components/packages— those
are fetched and executed at compile time, so review them before calling
esphome_compile_device.
Security
- New SSRF guard on
card_builder_upload_image_from_url: resolves the target
hostname once, rejects loopback/link-local/unspecified/multicast/reserved
destinations, and connects to the resolved IP literal (not the hostname
again) on every redirect hop, closing the DNS-rebinding window. Relevant
because underhost_network, a request to loopback from this add-on now
has the same peer identity Home Assistant Core has — exactly what some
ingress-fronted add-ons (e.g. the Device Builder) trust without
authentication. esphome_*tools refuse to read or writesecrets.yaml/secretsas a
device name — ESPHome's per-device secrets file is not a device.- In add-on mode nexus no longer trusts the loopback peer or the
host_networkbridge gateway (172.30.32.1) as if it were Supervisor's
ingress proxy:/mcpstill requires the API key, and the Setup UI still
only unlocks over genuine ingress (verified peer IP +X-Ingress-Path) or
a validAuthorization: Bearertoken.
Internal
- New
esphome_dashboard.py(Device Builder ingress client) and
addon_network.py(ListenPlan— reconstructs the Network tab's LAN-port
semantics from the Supervisor's own add-on info underhost_network), plus
their test suites. - ADR-0003 P3 (tool-search mode) is reserved for 0.24.0, not this release —
policy.py'stool_modestays fixed at"full".
v0.22.2
Fixed
esphome_ping_dashboardreportedport_6052_mapped: true(and advised checking
the firewall) when the Supervisor's add-on info returned"network": {"6052/tcp": null}— port 6052 declared but not published. The check now only counts the port
as mapped when its value is anint, and when it isn't (ingress-only), the advice
points at publishing port 6052 in the add-on's own Network settings with
authentication enabled. Found on a live HA instance, 2026-09-27.