Repository navigation
fix(firmware): OTA PSK cannot be provisioned — documented provision.py --ota-psk does not exist #1753
Description
Summary
OTA firmware upload cannot be enabled on any node built from the shipped source, because the
only documented way to provision the device-side PSK does not exist.
main/ota_update.c:29-31 reads the pre-shared key from NVS namespace security, key
ota_psk. When it is empty, ota_check_auth() rejects every upload (fail-closed, RuView#596).
The comment at main/ota_update.c:39 tells the operator how to set it:
The OTA server still starts so the operator can
provision.py --ota-psk <hex>over USB-CDC
without a reflash, but the upload endpoint will reject every request until the PSK is set.
firmware/esp32-csi-node/provision.py has no --ota-psk argument, and contains no reference
to ota_psk or the security namespace at all — it generates only the csi_cfg namespace
(from provision.py:177). The only other ota_psk in the tree is in
v2/crates/wifi-densepose-desktop (src/domain/config.rs:26,
ui/src/pages/Settings.tsx:138), which is the client-side token the desktop app sends,
not something written to device NVS.
Net effect: OTA=ready in the boot log means only that the HTTP server started
(main/main.c:371, :504); authentication can never be satisfied through any shipped
workflow.
Why now
Observed while testing v0.8.4 on hardware. Nodes are otherwise healthy and streaming, but the
only non-USB update path is unusable, so every firmware change requires physical access.
Reproduction
On a node flashed from the published v0.8.4 assets and joined to WiFi:
$ curl -X POST --data-binary @esp32-csi-node-v0.8.4-8mb.bin \
-H 'Content-Type: application/octet-stream' \
http://<node-ip>:8032/ota
HTTP 403 after ~135 KB uploaded
Authentication required. Use: Authorization: Bearer <psk>
$ python3 provision.py --help | grep -c ota-psk
0
$ grep -c 'ota_psk\|security' provision.py
0
GET /ota/status on the same node responds normally, so the server is up:
{"version":"0.8.4","running_partition":"ota_0","next_partition":"ota_1","max_size":921600}
Done when
provision.pycan writesecurity/ota_pskto device NVS over USB-CDC without a reflash,
matching the behaviourota_update.c:39already documents; or- the firmware comment is corrected to name whatever the supported path actually is.
Work items
- Add
--ota-psk <hex>toprovision.py, emitting thesecuritynamespace alongside
csi_cfgin the generated NVS CSV. - Validate the value (the firmware caps it at
OTA_PSK_MAX_LEN65, hex-encoded SHA-256). - Decide whether the PSK belongs in the per-port state file — if so it needs the same
protection as the WiFi password (see the state-file permissions issue). - Consider making the boot log distinguish "OTA server started" from "OTA can accept
uploads", sinceOTA=readycurrently implies the latter.
Secondary, untested
/ota/status advertises max_size: 921600 while the shipped v0.8.4 image is 1,126,624 bytes
and ota_1 is 2,097,152 bytes. Whether that gate would reject a valid image could not be
tested, because the auth gate rejects first. Flagging only; it may be a stale constant.
Activity
ruvnet commentedon Sep 30, 2026
provision.pyhas no--ota-pskflag and no reference to thesecurity/ota_pskNVS key, making the OTA security feature unreachable without direct NVS manipulation. This means every firmware change currently requires physical USB access.The fix is to add to
provision.py:And in the write path, write to the
securitynamespace:On the boot log:
OTA=readyshould be split into two distinct states —OTA=listening(HTTP server started) vsOTA=armed(PSK loaded from NVS and endpoint is authenticated). A node with no PSK in NVS should logOTA=unarmedso operators know uploads will be rejected before attempting a flash.Generated by Claude Code
aepod commentedon Oct 2, 2026
Confirmed on 0.8.12, on an ESP32-S3 and an ESP32-C6. With no
security/ota_pskin NVS,POST /otafails closed as intended (#623), and the firmware log tells the operator to runprovision.py --ota-psk, which main'sprovision.pydoesn't accept. MEASURED. The missing flag on main is CODE-DERIVED at03a89793(provision.pyunchanged).security/ota_pskwith a local--ota-psk-filepatch made the node logOTA PSK loaded, and authenticatedPOST /otathen reached the upload (where firmware: POST /ota always fails — stack overflow in task httpd during esp_ota_end() image validation (ESP32-S3, v0.8.8) #1893 takes over until the stack fix).wasm_verify=1 but no wasm_pubkey in NVS, and the firmware suggests a--wasm-pubkeyprovision flag that doesn't exist either.Environment: S3 and C6 nodes, 0.8.12 from source; macOS host with esptool 5.4.0 and
esp-idf-nvs-partition-gen0.3.0.ruvnet commentedon Oct 10, 2026
The missing
--ota-pskflag is listed as open work in #2165 (follow-up tracking for the aepod community batch). No fix has landed yet — watch #2165 for progress.Generated by Claude Code