Skip to content

OTAmpy v4.6.0

Choose a tag to compare

@simonl65 simonl65 released this 03 Sep 20:18
· 166 commits to main since this release
Immutable release. Only release title and notes can be modified.

OTAmpy 4.6.0 — 2026-09-03

Major (breaking changes)

  • None. Every new setting below is opt-in: an existing device with an unchanged configota.py, driven by a CLI with no OTAMPY_COMMAND_AUTH_KEY, behaves exactly as it did in 4.5.0.

Minor (functionality added in a backward compatible manner)

  • Optional authentication for the runtime command surface. Until now, any station that could reach the device's UART could read, overwrite, delete or reboot it with no credential. On a wired link that is the same trust boundary as physical access; on a radio link — where OTAmpy is most useful — it means anyone in range.
    • Setting OTA_REQUIRE_AUTH = True on the device makes it require every command (RB, SR, UPDATE_REQUEST, RM, CAT, LS, MEM, RTC*, CP_*) to arrive as AUTH:<counter>:<hex-mac>:<command>. The host signs when OTAMPY_COMMAND_AUTH_KEY is set.
    • The MAC is HMAC-SHA256 truncated to 8 bytes over a domain-separated, counter-prefixed copy of the command. The inner command is dispatched through the existing parser unchanged, so no command grammar moves.
    • Counters are strictly increasing and seeded from the wall clock, so a command captured off the air cannot be replayed — including after a reboot, since an accepted RB/SR/UPDATE_REQUEST saves the counter as a boot floor before resetting.
    • The MAC sits above URST, so URST's retransmission is unchanged and unaware of it: a legitimate retry is never mistaken for a replay.
    • New device settings: OTA_REQUIRE_AUTH, COMMAND_AUTH_KEY, OTA_REPLAY_FLOOR_FILE. New host environment variables: OTAMPY_COMMAND_AUTH_KEY, OTAMPY_COUNTER_FILE.
    • Documented in docs/protocol.md section 1.2, with all five settings shown commented out in configota.example.py.
  • OTA_ALLOWED_PATH_PREFIXES optionally restricts which paths CP_START, CAT and RM may target. Unset, any non-traversing path is still allowed.

Patch (backward compatible bug fixes)

  • Fixed an arbitrary file write in CP_START (path traversal / persistent remote code execution). CP_START:<target>:<size>:<sha256> took its target straight from the command with no directory restriction and no .. rejection — and since the SHA-256 field is attacker-supplied too, the checksum gate constrained nothing. A crafted CP_START:main.py:… (or boot.py, or any lib/ path) overwrote device code, which then ran on the next reboot.
    • .. is now always refused for CP_START, CAT and RM, whether or not an allowlist is configured. There is no legitimate traversal on a filesystem rooted at /.
    • Prefix matching respects path-segment boundaries, so an allowed prefix of data does not also admit database.db.
    • The refusal happens before any directory or staging file is created, so a rejected copy leaves nothing behind.