Skip to content

Security: ilia-ae/zignode

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Email code@ilia.ae (subject zignode security). Please do not open a public issue for a vulnerability. Include the board, firmware version, and how to reproduce. You'll get an acknowledgement; fixes for confirmed issues ship in a patch release with credit if you want it.

Threat model & what the firmware already does

This is a DIY, LAN-local Zigbee/Wi-Fi pump controller — not an internet-exposed device. The safety-critical property is the pump cannot run away or run dry, and that is defended in firmware regardless of the security posture:

  • Pump parked OFF before radios; every ON bounded by a hard max-runtime watchdog + fail-safe drive.
  • Dry-run gate, min-off cooldown, and an OTA maintenance lock (no update while pumping).
  • OTA images are health-gated with bootloader rollback (CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE).
  • The Wi-Fi/web panel is a self-closing maintenance window by default; its auth is optional.

Firmware integrity

Three independent layers, none of which require burning an eFuse:

  1. Image validity — esp_ota_end() verifies the ESP-IDF image structure and its appended SHA-256 before anything can be marked bootable, so a corrupt or truncated-mid-header upload is rejected outright.
  2. End-to-end digest (Wi-Fi OTA) — the panel hashes the .bin in the browser and sends it as an X-SHA-256 request header; the device hashes every byte it accepts and aborts before esp_ota_end()/esp_ota_set_boot_partition() on any mismatch, so a mismatched image never becomes bootable. This catches what layer 1 cannot: a valid image that simply isn't the one you meant to send. The digest is logged and echoed in the HTTP response either way. Caveat: crypto.subtle is only available in a secure context, so over plain http://zignode.local/ the browser cannot compute the digest — the upload still proceeds and the device still reports the hash it stored; you compare it manually. Supply the header yourself for an enforced check:
    curl -sS --data-binary @zignode.bin -H "X-SHA-256: $(shasum -a 256 zignode.bin | cut -d' ' -f1)" \
         http://zignode.local/api/ota
  3. Runtime attestation — GET /api/status reports app_sha (the running image's ELF SHA-256) and idf_ver, shown in the panel as Running image. A version string can't distinguish two builds of "2.0.0"; the hash can. Compare it against your build output to confirm exactly what is executing.

Plus the health-gated rollback: a new image must prove itself after reboot or the bootloader reverts to the previous one.

Not provided: authenticity. Any image with a matching digest is accepted — the digest proves integrity, not provenance. For a deployment that needs signed images without the eFuse risk of full Secure Boot, enable ESP-IDF's signed-app verification:

CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT=y
CONFIG_SECURE_SIGNED_ON_UPDATE_NO_SECURE_BOOT=y

This makes the OTA path verify an ECDSA signature against a key you control, and — unlike the options below — burns no eFuses and stays reversible. Note that the web panel's Basic auth is off by default (ZIGNODE_WEB_PASSWORD is empty), so on an untrusted LAN set a password and prefer the self-closing maintenance window.

Secure Boot & Flash Encryption — intentionally OFF (documented)

Secure Boot v2 and Flash Encryption are not enabled by default, on purpose:

  • They burn irreversible eFuses — a mistake bricks the board permanently.
  • They break the project's easy USB reflash and complicate the OTA + rollback flow that is the actual safety mechanism here.
  • For a DIY pump on a home LAN the threat they address (an attacker with physical flash access cloning/altering the image) is low, and the cost/risk is high.

If a deployment needs them (e.g. a commercial build), enable per the ESP-IDF guides and test OTA + rollback end-to-end first:

CONFIG_SECURE_BOOT=y
CONFIG_SECURE_BOOT_V2_ENABLED=y
CONFIG_SECURE_FLASH_ENC_ENABLED=y

Mind that signed OTA images and a matching signing key become mandatory, and rollback interacts with the anti-rollback secure-version counter.

Provenance

Releases are GPG-signed git tags; LICENSE/COPYRIGHT/CITATION.cff carry OpenTimestamps proofs.

There aren't any published security advisories