On 16 October 2026 ESPHome merged a change to the web_server component that closes a quiet way around internal: true. Until now the device's REST endpoints answered requests for internal entities by name, even though the web page and the /events stream hide them. With the fix, those URLs return 404 unless include_internal: true is set under web_server:. The pull request comes from sebo-b and was reviewed and merged by bdraco. It is on the dev branch for ESPHome 2026.11 and carries the breaking-change label.

What was reachable

Many configs use internal: true to keep a low-level part out of sight behind a safer wrapper. A typical case is a relay on a raw GPIO, hidden behind a template switch that adds a timer, an interlock or an automatic switch-off. Home Assistant only sees the wrapper, and so does the web page. The REST API did not follow the same rule: a request such as curl -d '' http://<device>/switch/<internal_id>/turn_on could still switch the raw relay and skip the wrapper's safety logic.

The URL is often easy to guess. An entity that has only an id: is made internal and takes its id as its name, so the id from the YAML file is enough.

According to the pull request, the per-handler is_internal() checks were lost in ESPHome 2021.12, when the include_internal option was added (#2627). The new code restores the earlier behaviour rather than adding a new rule. The check now sits in one place, UrlMatch::match_entity(), so all 23 entity handlers get it. On an ESP8266 it costs 48 bytes of flash and no RAM.

Not a vulnerability by ESPHome's rules

The author notes that under ESPHome's threat model this does not count as a vulnerability, because a web server without auth: is an open API by design. What the change does is make internal mean the same thing in the web server as in the native API and MQTT, which already apply this rule.

What to check before updating

If a script, a dashboard or another device calls an internal entity through its web server URL, it will get 404 after the update to 2026.11. There are two ways to keep it working: set include_internal: true on web_server: (internal entities then also appear on the page), or remove internal: true from that entity. The matching documentation change on esphome.io (#7756) was still open on 17 October.

If you run web_server without auth:, this is a good moment to add a username and password anyway. The fix narrows what a client without a login can reach, it does not lock that client out.