From 4317bfffa24f087d8e2df346a521e06593e8ef0e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Sep 2026 17:59:31 +0200 Subject: [PATCH] Keep the api preference slot on yaml key builds so flash preference layout does not shift --- THREAT_MODEL.md | 10 +++++----- esphome/components/api/api_server.cpp | 6 +++++- esphome/components/api/api_server.h | 2 +- esphome/components/esphome/ota/ota_esphome_noise.cpp | 2 ++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 963613966e..11656ff0b7 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -161,11 +161,11 @@ The following are **not** vulnerabilities, by design: encryption, so turning on `ota: encryption:` is itself an encrypted upload. Older firmware needs one last plaintext upload of an offering build, with the pre-existing plaintext exposure. -- The web OTA `/update` endpoint alongside encryption. The `web_server` OTA - platform keeps it always reachable and validation warns about that - combination; `captive_portal:` auto-loads that platform only for the - fallback AP window, which is the intended recovery path, so that alone is - not warned about. +- The web OTA `/update` endpoint alongside encryption. With the `web_server` + or `prometheus` component the shared listener is always up, so the endpoint + stays reachable and validation warns about that combination; + `captive_portal:` alone brings the listener up only for the fallback AP + window, which is the intended recovery path, so that is not warned about. - CLI retry behavior on transport or MAC failures; every attempt renegotiates a fresh handshake with fresh ephemerals, so retrying does not weaken authentication. diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 4f456cc9c7..78ebe5c38e 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -40,13 +40,17 @@ void APIServer::socket_failed_(const LogString *msg) { void APIServer::setup() { ControllerRegistry::register_controller(this); -#if defined(USE_API_NOISE) && !defined(USE_API_NOISE_PSK_FROM_YAML) +#ifdef USE_API_NOISE + // Always reserve the slot: flash preferences are positional on esp8266, so + // a yaml key build must keep the layout of a runtime key build uint32_t hash = 88491486UL; this->noise_pref_ = global_preferences->make_preference(hash, true); +#ifndef USE_API_NOISE_PSK_FROM_YAML // A cleared record loads fine but holds no key if (this->load_and_apply_noise_psk_() && this->noise_ctx_.has_psk()) { ESP_LOGD(TAG, "Loaded saved Noise PSK"); } +#endif #endif this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 128ba04428..618ea4eb11 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -367,8 +367,8 @@ class APIServer final : public Component, noise::NoiseContext noise_ctx_; #ifndef USE_API_NOISE_PSK_FROM_YAML SavedNoisePsk saved_psk_{}; // backs noise_ctx_ for a runtime provisioned key - ESPPreferenceObject noise_pref_; #endif + ESPPreferenceObject noise_pref_; #endif // USE_API_NOISE }; diff --git a/esphome/components/esphome/ota/ota_esphome_noise.cpp b/esphome/components/esphome/ota/ota_esphome_noise.cpp index 9375d94426..7401413d6d 100644 --- a/esphome/components/esphome/ota/ota_esphome_noise.cpp +++ b/esphome/components/esphome/ota/ota_esphome_noise.cpp @@ -69,6 +69,8 @@ bool ESPHomeOTAComponent::noise_start_session_(uint8_t server_feature_flags) { int err = this->noise_ == nullptr ? NOISE_ERROR_NO_MEMORY : this->noise_->handshake.init(this->noise_context_(), prologue, sizeof(prologue)); if (err != 0) { + // Raw noise codes throughout: the name table would cost flash in builds + // where only the OTA uses noise ESP_LOGW(TAG, "Session init: %d", err); this->cleanup_connection_(); return false;