fix: address critical security issues discovered during code review
All checks were successful
CI / build (pull_request) Successful in 1m16s

This commit is contained in:
eddy 2026-08-04 21:56:05 +02:00
commit 671da31605
7 changed files with 417 additions and 0 deletions

View file

@ -0,0 +1,53 @@
<h2>Voorgestelde Flow: De "Server-as-Broker" methode</h2>
<p class="subtitle">Gebruik de SylDa_Server als ontmoetingsplaats voor versie-informatie</p>
<div class="mockup">
<div class="mockup-header">Interactie Diagram</div>
<div class="mockup-body" style="background: #1e1e1e; color: #d4d4d4; padding: 20px; font-family: monospace; line-height: 1.4;">
<div style="margin-bottom: 10px; color: #569cd6;">// 1. Client meldt zich aan</div>
[Client] --(Write version: "5")--> [Server: ClientStatusChar]
[Client] --(Subscribe)------------> [Server: ClientControlChar]
<div style="margin-top: 20px; margin-bottom: 10px; color: #569cd6;">// 2. App checkt status</div>
[App] --(Read ClientStatus)----> [Server]
<span style="color: #ce9178;">App denkt: "Client heeft v5, ik heb v6. Update nodig!"</span>
<div style="margin-top: 20px; margin-bottom: 10px; color: #569cd6;">// 3. App start de dans</div>
[App] --(Write: "START_OTA")---> [Server: ClientControlChar]
[Server] --(Notify)---------------> [Client]
<div style="margin-top: 20px; margin-bottom: 10px; color: #569cd6;">// 4. De wissel</div>
<span style="color: #4ec9b0;">Client disconnect van Server -> Start Advertising</span>
<span style="color: #4ec9b0;">App disconnect van Server -> Scant naar Client OTA</span>
<div style="margin-top: 20px; margin-bottom: 10px; color: #569cd6;">// 5. De eigenlijke OTA</div>
[App] --(Direct BLE OTA)-------> [Client (nu in Server mode)]
</div>
</div>
<div class="section">
<h3>Technische details om te bespreken:</h3>
<div class="options" data-multiselect>
<div class="option" data-choice="uuid-tracking" onclick="toggleSelect(this)">
<div class="letter">1</div>
<div class="content">
<h3>UUID Tracking</h3>
<p>Aangezien de Server 2 clients ondersteunt, moet de Server de versie per UUID opslaan zodat de App weet welke client welke versie heeft.</p>
</div>
</div>
<div class="option" data-choice="auth" onclick="toggleSelect(this)">
<div class="letter">2</div>
<div class="content">
<h3>Beveiliging (HMAC/AES)</h3>
<p>Moet de "START_OTA" opdracht ook versleuteld zijn? (Waarschijnlijk wel, om te voorkomen dat een vreemde de clients platlegt).</p>
</div>
</div>
<div class="option" data-choice="reconnect" onclick="toggleSelect(this)">
<div class="letter">3</div>
<div class="content">
<h3>Auto-reconnect</h3>
<p>Na de update (of bij falen/timeout) moet de Client weer terug in zijn normale rol vallen en de Server zoeken.</p>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,48 @@
<h2>Ontwerpkeuzes BLE OTA voor de Client</h2>
<p class="subtitle">Hoe krijgt de Client (ESP32) de nieuwe firmware?</p>
<div class="options">
<div class="option" data-choice="android-direct" onclick="toggleSelect(this)">
<div class="letter">A</div>
<div class="content">
<h3>Android App Direct</h3>
<p>De Client ESP32 start een tijdelijke BLE Server (OTA Service). De Android app verbindt direct met de Client om de firmware te pushen.</p>
<div class="label">Voordeel: Simpel, hergebruikt Android OTA code.</div>
<div class="label">Nadeel: Handmatige actie nodig op Client (bijv. knop indrukken).</div>
</div>
</div>
<div class="option" data-choice="server-proxy" onclick="toggleSelect(this)">
<div class="letter">B</div>
<div class="content">
<h3>Server Proxy / Relay</h3>
<p>De Android app stuurt de firmware naar de SylDa_Server. De Server stuurt het door naar de Client via de bestaande BLE verbinding.</p>
<div class="label">Voordeel: Geen directe verbinding Android-Client nodig.</div>
<div class="label">Nadeel: Complexer, Server moet grote buffers hebben of flow control.</div>
</div>
</div>
<div class="option" data-choice="client-pull" onclick="toggleSelect(this)">
<div class="letter">C</div>
<div class="content">
<h3>Client Pull (via Server)</h3>
<p>De Server kondigt een update aan. De Client haalt de firmware "chunks" op bij de Server via een nieuwe OTA service op de Server.</p>
<div class="label">Voordeel: Client heeft controle over tempo.</div>
<div class="label">Nadeel: Server moet de firmware ergens opslaan (Flash/NVS/SPIFFS?) voordat hij het kan doorgeven.</div>
</div>
</div>
</div>
<div class="section">
<h3>Huidige Situatie</h3>
<div class="mockup">
<div class="mockup-header">Architectuur</div>
<div class="mockup-body">
<pre>
[Android App] --(BLE OTA)--> [SylDa_Server (ESP32)]
|
? --(BLE OTA?)--> [SylDa_Client (ESP32)]
</pre>
</div>
</div>
</div>

View file

@ -0,0 +1,2 @@
{"type":"click","text":"3\n \n Auto-reconnect\n Na de update (of bij falen/timeout) moet de Client weer terug in zijn normale rol vallen en de Server zoeken.","choice":"reconnect","id":null,"timestamp":1785870849065}
{"type":"click","text":"3\n \n Auto-reconnect\n Na de update (of bij falen/timeout) moet de Client weer terug in zijn normale rol vallen en de Server zoeken.","choice":"reconnect","id":null,"timestamp":1785870849785}

View file

@ -0,0 +1 @@
{"reason":"idle timeout","timestamp":1785872682901}

View file

@ -0,0 +1,7 @@
{"type":"server-started","port":56787,"host":"0.0.0.0","url_host":"100.97.199.84","url":"http://100.97.199.84:56787","screen_dir":"/home/eddy/src/Project_SylDa/.superpowers/brainstorm/124687-1785870282/content","state_dir":"/home/eddy/src/Project_SylDa/.superpowers/brainstorm/124687-1785870282/state"}
{"type":"screen-added","file":"/home/eddy/src/Project_SylDa/.superpowers/brainstorm/124687-1785870282/content/ota_options.html"}
{"source":"user-event","type":"click","text":"A\n \n Android App Direct\n De Client ESP32 start een tijdelijke BLE Server (OTA Service). De Android app verbindt direct met de Client om de firmware te pushen.\n Voordeel: Simpel, hergebruikt Android OTA code.\n Nadeel: Handmatige actie nodig op Client (bijv. knop indrukken).","choice":"android-direct","id":null,"timestamp":1785870382036}
{"type":"screen-added","file":"/home/eddy/src/Project_SylDa/.superpowers/brainstorm/124687-1785870282/content/broker_flow.html"}
{"source":"user-event","type":"click","text":"3\n \n Auto-reconnect\n Na de update (of bij falen/timeout) moet de Client weer terug in zijn normale rol vallen en de Server zoeken.","choice":"reconnect","id":null,"timestamp":1785870849065}
{"source":"user-event","type":"click","text":"3\n \n Auto-reconnect\n Na de update (of bij falen/timeout) moet de Client weer terug in zijn normale rol vallen en de Server zoeken.","choice":"reconnect","id":null,"timestamp":1785870849785}
{"type":"server-stopped","reason":"idle timeout"}

View file

@ -0,0 +1 @@
124706

View file

@ -0,0 +1,305 @@
# Code Review Fixes (Batch 2) Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task.
> Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Fix 6 issues found in code review: broken client OTA UI flow (SettingsActivity button), missing timeout in OTA polling, recomputeClientOta race condition, committed upload_port, wrong LED startup sequence, and noisy ESP_LOGI diagnostics.
**Architecture:** All fixes are isolated single-file edits. No new files. No architecture changes. Version bumps: server 21→22, client 11→12.
**Tech Stack:** C++ (Arduino/ESP-IDF), Kotlin (Android), PlatformIO
---
### Task 1: Fix SettingsActivity button — client+BLE now routes to startClientOtaUpdate
**Files:**
- Modify: `android/app/src/main/java/nl/eddydevink/sylda/SettingsActivity.kt:167-174`
- [ ] **Step 1: Replace button click handler**
In `setupButtons()` (or wherever the click listener is set), replace lines 167-174 with the corrected logic:
```kotlin
findViewById<Button>(R.id.updateModuleButton).setOnClickListener {
val mode = PrefsHelper.getDeviceMode(this)
val transportId = findViewById<RadioGroup>(R.id.radioTransport).checkedRadioButtonId
if (transportId == R.id.radioUsb) {
startFlashing()
} else if (mode == "client") {
startClientOtaUpdate(BleRelayManager.instance)
} else {
startServerOtaUpdate(BleRelayManager.instance)
}
}
```
- [ ] **Step 2: Verify the change compiles**
```bash
cd android && ./gradlew compileDebugKotlin 2>&1 | tail -5
```
- [ ] **Step 3: Commit**
```bash
git add android/app/src/main/java/nl/eddydevink/sylda/SettingsActivity.kt
git commit -m "fix(android): client+BLE button now routes to client OTA instead of USB flash"
```
---
### Task 2: Add timeout to waitForClientOtaReady polling loop
**Files:**
- Modify: `android/app/src/main/java/nl/eddydevink/sylda/SettingsActivity.kt:393-405`
- [ ] **Step 1: Add `import kotlinx.coroutines.withTimeoutOrNull`**
The import `kotlinx.coroutines.isActive` already exists at line 35. Just add the timeout:
```kotlin
import kotlinx.coroutines.withTimeoutOrNull
```
- [ ] **Step 2: Wrap polling loop in withTimeoutOrNull**
Replace `waitForClientOtaReady` (lines 393-405) with:
```kotlin
private suspend fun waitForClientOtaReady(serverManager: BleRelayManager): Boolean {
return withTimeoutOrNull(30_000L) {
var status = -1
serverManager.setClientOtaStatusListener { s -> status = s }
try {
while (status != 0x01) {
serverManager.readClientOtaStatus()
delay(2000)
}
} finally {
serverManager.setClientOtaStatusListener(null)
}
} ?: false
}
```
- [ ] **Step 3: Verify the change compiles**
```bash
cd android && ./gradlew compileDebugKotlin 2>&1 | tail -5
```
- [ ] **Step 4: Commit**
```bash
git add android/app/src/main/java/nl/eddydevink/sylda/SettingsActivity.kt
git commit -m "fix(android): add 30s timeout to client OTA ready polling"
```
---
### Task 3: Fix recomputeClientOta() race — preserve 0x01 status
**Files:**
- Modify: `src/main.cpp:251`
- [ ] **Step 1: Add 0x01 exclusion to recomputeClientOta**
Change line 251 from:
```cpp
if (!clientOtaAvailable && clientOtaStatus != 0x00 && clientOtaStatus != 0x02) {
```
to:
```cpp
if (!clientOtaAvailable && clientOtaStatus != 0x00 && clientOtaStatus != 0x01 && clientOtaStatus != 0x02) {
```
- [ ] **Step 2: Commit**
```bash
git add src/main.cpp
git commit -m "fix(server): avoid resetting CLIENT_OTA_STATUS 0x01 in recomputeClientOta"
```
---
### Task 4: Remove committed upload_port from platformio.ini
**Files:**
- Modify: `platformio.ini:16,38`
- [ ] **Step 1: Remove both upload_port lines**
Delete line 16:
```
upload_port = /dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_44:1B:F6:F4:CF:14-if00
```
Delete line 38:
```
upload_port = /dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_44:1B:F6:F5:D1:E0-if00
```
- [ ] **Step 2: Verify platformio.ini is still valid**
```bash
grep -n "upload_port" platformio.ini
```
Expected: no output.
- [ ] **Step 3: Commit**
```bash
git add platformio.ini
git commit -m "fix: remove committed upload_port from platformio.ini"
```
---
### Task 5: Restore LED startup sequence to Blue-Red-Blue
**Files:**
- Modify: `src/client.cpp:603-605`
- [ ] **Step 1: Change LED startup sequence**
Replace lines 603-605:
```cpp
leds[0] = CRGB::Red; FastLED.show(); delay(500);
leds[0] = CRGB::Green; FastLED.show(); delay(500);
leds[0] = CRGB::Red; FastLED.show(); delay(500);
leds[0] = CRGB::Black; FastLED.show();
```
with:
```cpp
leds[0] = CRGB::Blue; FastLED.show(); delay(500);
leds[0] = CRGB::Red; FastLED.show(); delay(500);
leds[0] = CRGB::Blue; FastLED.show(); delay(500);
leds[0] = CRGB::Black; FastLED.show();
```
- [ ] **Step 2: Commit**
```bash
git add src/client.cpp
git commit -m "fix(client): restore LED startup sequence to blue-red-blue"
```
---
### Task 6: Downgrade diagnostic ESP_LOGI to ESP_LOGD
**Files:**
- Modify: `src/client.cpp:907`
- Modify: `src/main.cpp:474`
- [ ] **Step 1: Change client.cpp diagnostic log level**
Change line 907 from:
```cpp
ESP_LOGI(TAG, "OTA: status read value=%d bytes empty=%d firstByte=0x%02X",
```
to:
```cpp
ESP_LOGD(TAG, "OTA: status read value=%d bytes empty=%d firstByte=0x%02X",
```
- [ ] **Step 2: Change server.cpp onRead diagnostic log level**
Change line 474 from:
```cpp
ESP_LOGI(TAG, "CLIENT_OTA_STATUS: read by conn=%d", connInfo.getConnHandle());
```
to:
```cpp
ESP_LOGD(TAG, "CLIENT_OTA_STATUS: read by conn=%d", connInfo.getConnHandle());
```
- [ ] **Step 3: Commit**
```bash
git add src/client.cpp src/main.cpp
git commit -m "chore: downgrade diagnostic OTA logs from INFO to DEBUG"
```
---
### Task 7: Bump versions and rebuild
**Files:**
- Modify: `version.txt`
- [ ] **Step 1: Bump versions**
```
server=22
client=12
```
- [ ] **Step 2: Commit version bump**
```bash
git add version.txt
git commit -m "chore: bump versions server 21->22, client 11->12"
```
- [ ] **Step 3: Rebuild both firmware variants**
```bash
pio run
```
Verify both variants compile: `[env:s3lite-server] SUCCESS` and `[env:s3lite-client] SUCCESS`.
- [ ] **Step 4: Copy built firmware to Android assets**
```bash
cp .pio/build/s3lite-server/firmware.bin android/app/src/main/assets/firmware/server/firmware.bin
cp .pio/build/s3lite-client/firmware.bin android/app/src/main/assets/firmware/client/firmware.bin
```
- [ ] **Step 5: Commit firmware binaries**
```bash
git add android/app/src/main/assets/firmware/
git commit -m "chore: bundle server v22 and client v12 firmware in APK"
```
---
### Verification
After all tasks are complete:
1. **Check git log:**
```bash
git log --oneline -8
```
Expected: 7 new commits on top of b8facac.
2. **Flash server v22:**
```bash
# Find ESP32 server port
ls /dev/serial/by-id/ | grep -i espressif
# Update upload_port in platformio.ini (or use override), then:
pio run -t upload -e s3lite-server
```
3. **Flash client v12:**
```bash
pio run -t upload -e s3lite-client
```
4. **Verification test — Server serial:**
Connect to server serial. Confirm startup message shows v22. Confirm no "CLIENT_OTA_STATUS: read by conn=..." spam (only on CORE_DEBUG_LEVEL=4).
5. **Verification test — Client LED startup:**
Power cycle client. Confirm LED shows Blue → Red → Blue → off.
6. **Verification test — Client OTA flow via BLE:**
- Build and install APK: `cd android && ./gradlew assembleDebug`
- Deploy APK to phone via ADB
- In MainActivity, verify client OTA available notification
- Tap notification → SettingsActivity opens with client+BLE pre-selected
- Tap "Update module" → must show "Wachten op client..." progress dialog (NOT USB dialog)
- If client has older version, OTA should proceed; if same version, toast "Firmware is up-to-date"