ruvnet / ruvnet/RuView

fix(firmware): OTA PSK cannot be provisioned — documented `provision.py --ota-psk` does not exist

Open
#1,753 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug firmware security
Dominant language
Rust
Stars
94.4k
Forks
12.5k
Avg merge
21h 27m
Merged PRs (30d)
43

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.py can write security/ota_psk to device NVS over USB-CDC without a reflash,
    matching the behaviour ota_update.c:39 already documents; or
  • the firmware comment is corrected to name whatever the supported path actually is.

Work items

  • Add --ota-psk <hex> to provision.py, emitting the security namespace alongside
    csi_cfg in the generated NVS CSV.
  • Validate the value (the firmware caps it at OTA_PSK_MAX_LEN 65, 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", since OTA=ready currently 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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with firmware/esp32-csi-node/provision.py and main/ota_update.c, comparing the existing csi_cfg NVS generation with the documented security/ota_psk path. Run provision.py --help and inspect the OTA PSK length constraint and state-file handling. Done means the supported workflow can provision the device-side PSK over USB-CDC without a reflash, or the firmware comment names the actual supported path.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, python
Domain
embedded-iot, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.