fix(reporter,docs): save config before applying; correct README trust-model

Reorder reporter_heartbeat so NVS persistence commits before in-RAM apply.
If save fails, log and return without touching runtime state; RAM and NVS
stay consistent on the prior version instead of diverging until reboot.

Rewrite README "Trust model" to state reality: reporting is plain HTTP and
HMAC signs only requests, not responses. A LAN-local MITM can push any
config that passes the device range validator. Add roadmap entry for
authenticated config push (HTTPS or signed envelope).
This commit is contained in:
2026-04-16 17:47:41 -07:00
parent bcb02b6d73
commit e5eeea2b47
2 changed files with 10 additions and 6 deletions

View File

@@ -131,14 +131,18 @@ Tuning is stored in the `doorcounter` NVS namespace and survives reboot. On boot
### Trust model
The device enforces validation defensively, but the backend is the source of truth and SHOULD validate bounds before pushing. Integrity of pushed config rides on the HMAC trust boundary that already authenticates the backend. Updates are keyed per `device_id`, so operators can tune one customer without affecting others.
The reporting channel is plain HTTP today. The HMAC scheme signs only outbound **requests** (method + path + timestamp + sha256(body)) — it does not authenticate response bodies. A network attacker with access to the customer LAN can rewrite a heartbeat response and push any config that passes the device's range validator (`diff_thresh` 5120, `min_blob_px` 164096, `max_move` 2.050.0, `max_missed` 160, `line_offset` 0100). The validator is the last line of defense: malicious-but-in-range pushes can still degrade counting (e.g., `min_blob_px = 16` makes the detector noisy).
No inbound HTTP surface is exposed on customer LANs by design — the device only makes outbound requests.
Per-device targeting (keyed by `device_id`) still works correctly and is unaffected by the integrity gap — each device only applies updates addressed to itself.
Operators should treat customer LANs as untrusted and rely on monitoring heartbeat cadence and count anomalies to detect tampering. No inbound HTTP surface is exposed on customer LANs by design — the device only makes outbound requests.
## Roadmap
**Gated local config portal.** Holding the BOOT button for ~3 seconds would raise a WiFiManager-style captive portal on the local network for ~5 minutes, exposing a tuning page for field techs operating without backend connectivity. Deferred because (a) the server-push mechanism above covers routine tuning, (b) an always-on HTTP server on customer LANs is an undesirable attack surface, and (c) the gated-by-physical-access model needs additional auth design to be safe.
**Authenticated config push.** Move reporting to HTTPS, or include a signed envelope on pushed config (e.g., `config_sig = HMAC(secret, cfg_version || canonical_json(config))` verified on device) so pushed tuning is tamper-evident over plain HTTP.
## Project Structure
```

View File

@@ -236,12 +236,12 @@ void reporter_heartbeat(const DeviceConfig& cfg, uint32_t uptime_s, int wifi_rss
return;
}
cv_apply_tuning(candidate);
if (!config_save_tuning(candidate)) {
Serial.printf("[CFG] applied v=%u but NVS save failed\n", (unsigned)new_ver);
} else {
Serial.printf("[CFG] applied v=%u\n", (unsigned)new_ver);
Serial.printf("[CFG] rejected v=%u: NVS save failed\n", (unsigned)new_ver);
return;
}
cv_apply_tuning(candidate);
Serial.printf("[CFG] applied v=%u\n", (unsigned)new_ver);
}
void reporter_flush(const DeviceConfig& cfg) {