Add Linux QA-mode harness for BLE + WiFi (normal + attack)
Introduce a reusable Linux QA harness under scripts/qa/linux-harness for
exercising the Kleidos DUT over BLE-HID and the WiFi admin SoftAP, both
normal and attack paths, with machine-greppable *_OK/_FAIL/_SKIP output.
Runner and modules:
* run.py QA-mode entry point: SSHes to the configured QA server, syncs
the harness (tar-over-ssh), and dispatches a suite; or --local.
* setup.sh idempotent provisioning (venv + bleak, apt radio/capture tools,
pinned qa-env), --system-site-packages for dbus/gi/requests.
* BLE: nc_agent.py (BlueZ numeric-comparison auto-confirm agent), ble_normal.py
(bleak scan/connect/pair/HOGP/subscribe/reconnect), ble_attack.sh
(E1-E6: SC-only reject, IO-cap/NC-only, accept-list, RPA/IRK, BLERP).
* WiFi: wifi_net.sh (static-IP bring-up, no default route), wifi_normal.sh
(WPA2/SAE station + portal checks), portal_checks.py (auth + fuzz),
wifi_attack.sh (monitor capture, evil-twin, empirical injection probe
with auto-skip when the adapter cannot inject).
Safety model (production server): interface pinning, a preflight that asserts
the default route stays on the Ethernet iface and never via WiFi, static IP
with no DHCP/default route, WiFi left DOWN when idle, and controller knobs
restored. The BLERP public-address spoof is opt-in (default SKIP) because the
Intel vendor command tx-timeouts and wedges the controller.
Config hygiene: all coordinates (SSH host/port/user, interface names, SoftAP
subnet, DUT addresses) load from a gitignored .env; only .env.example with
placeholders is tracked. No host/IP/interface literals in tracked code.
Claude-Session: https://claude.ai/code/session_01Y7Cy1HetRp6TZAUAeekN8X
Remediate WiFi audit findings, add resource telemetry and PMIC current
Implement the WiFi subsystem defensive-audit remediations (dossier in
docs/research/wifi-audit-dossier.md) plus a resource-consumption monitor and
per-chip power telemetry.
Security (audit findings):
- OTA: real semver anti-downgrade (running vs candidate partition), rollback
enabled on shipping open/_release builds, size/BLE-state guards.
- Lifecycle: keyboard variants run the real goToSleep() teardown+wipe (LIFEC-001);
exit() always tears down the portal (LIFEC-003); loop task subscribed to the
TWDT (LIFEC-004); symmetric BLE/WiFi start guard (LIFEC-002); master key wiped
before the handover reboot (LIFEC-008); on-device idle warning + panel dimming.
- HTTP: DNS-rebinding Host guard with captive-portal exemption (HTTP-003, incl.
the 403 reason-phrase fix found on hardware); idle-based session TTL (HTTP-001);
sliding-window rate limiter (HTTP-006, native test); /stream token no longer
logged (HTTP-005).
- Radio: H2E-only SAE, TX-power cap, stack wifi_config_t scrub, WPA2 fail-closed,
bootloader-random AP password, DHCP-DNS offer.
- Web UI: stored-XSS removal via safe DOM builders, autocomplete controls, CSP
meta, deterministic gzip; SPA gz regenerated.
- Optimization: nothrow HTTP body alloc, std::move export responses, CPU boost
around the export KDF, ~1 Hz AP snapshot poll, early never-connected AP shutdown.
Telemetry / consumption:
- Per-tick UI-loop latency gauge (loop_dt_us / loop_max_us) via diag helpers.
- RES? extended with heap fragmentation, SoC die temperature, battery mV/%/current
and VBUS (USB) mV/current.
- PowerManager / IPmic gain optional current + VBUS readouts; implemented for the
AXP192 (validated on a Plus1: batt 4.1 V, VBUS ~5.0 V, ~81 mA USB draw) and
"n/a" on the M5PM1 / AXP2101 fleet. See docs/research/pmic-consumption-telemetry.md.
QA harness (mastodon SSH rig):
- portal_checks.py adds Host-guard / security-header / idle plumbing / Core-0
stress-flood checks; wifi_net.sh requests SAE H2E; run.py forwards the stress
env; new pytest tests for admin lifecycle, WiFi stress-resource monitoring and
AXP192 current.
Verified: native 1472/1472; -Werror builds for sticks3, cardputer, sticks3_debug
and m5stickc_plus1_debug; clang-format, check_variants and check_conditionals
clean; HIL passes on sticks3 (via mastodon) and the AXP192 current path on the
Plus1. Known limitation in plan/plus1-softap-nomem.md: the admin SoftAP hits
ESP_ERR_NO_MEM on the no-PSRAM Plus1 (pre-existing DRAM exhaustion).
Claude-Session: https://claude.ai/code/session_01NYGJirRyAokBs9idYmGLxi