fix(security,firmware): secure-by-default Docker auth (#864) + CSI yield recovery (#866)

#864 — Docker no longer exposes the sensing API/stream unauthenticated:
- Add `require_ws_token` middleware gating `/ws/*` (sensing + introspection)
  with the API token via `?token=` (browser) or `Authorization: Bearer`
  (programmatic). Previously /ws/sensing was ungated even with a token set.
- docker-entrypoint.sh now fails closed: auto-generates a strong
  RUVIEW_API_TOKEN when none is supplied and prints it; explicit
  RUVIEW_ALLOW_UNAUTHENTICATED=1 restores the open LAN posture.
- compose/Dockerfile wire the env vars; startup logs + CI smoke test updated
  to assert secure-by-default (401 with no token) and the opt-out path.
- 7 new bearer_auth unit tests (15 total pass).

#866 — CSI callbacks were starving (~3 in 70s, 0pps) under the MGMT-only
promiscuous filter:
- The documented "10 Hz probe injection" never existed — implement it for
  real (csi_inject_probe_request + 10 Hz timer). Validated on ESP32-C6 (COM9):
  probe TX succeeds at 10 Hz, but management-frame CSI stays sparse.
- Re-admit DATA frames (MGMT+DATA) now that the original wDev_ProcessFiq
  SPI-cache crash is mitigated by WiFi RX/TX IRAM opts + the existing 50 Hz
  rate gate. Kconfig CSI_PROMISC_MGMT_ONLY falls back if needed.
- Hardware-validated on COM9: yield 0 -> ~9pps avg (peak 19), presence/motion
  sensing restored, 0 panics over 35s.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-05-30 11:37:07 -04:00
parent 9ad550d95f
commit f02b431b59
8 changed files with 425 additions and 24 deletions
+6 -3
View File
@@ -60,9 +60,12 @@ RUN set -e; \
test -x /app/homecore-server || { echo "FATAL: /app/homecore-server is not executable"; exit 1; }; \
echo "image assets OK"
# Optional bearer-token auth on /api/v1/*: leave unset for LAN-mode (default),
# set to enforce `Authorization: Bearer <token>` (see bearer_auth module, #443).
# docker run -e RUVIEW_API_TOKEN=$(openssl rand -hex 32) ...
# Bearer-token auth on /api/v1/* and /ws/* (#443, #864). Secure-by-default:
# when left unset the entrypoint GENERATES a random token at startup and prints
# it to the logs, so the sensing API + WebSocket stream are never anonymous out
# of the box. Pin a known token, or opt into the open LAN posture explicitly:
# docker run -e RUVIEW_API_TOKEN=$(openssl rand -hex 32) ... # pin a token
# docker run -e RUVIEW_ALLOW_UNAUTHENTICATED=1 ... # trusted LAN only
ENV RUVIEW_API_TOKEN=
# HTTP API
+9
View File
@@ -23,6 +23,15 @@ services:
- "5005:5005/udp"
environment:
- RUST_LOG=info
# Bearer-token auth (#864). Secure-by-default: if RUVIEW_API_TOKEN is
# unset the container generates a random token at startup — retrieve it
# with `docker compose logs sensing-server`. Pin a known token by exporting
# RUVIEW_API_TOKEN in your shell / .env, or run open on a trusted, isolated
# LAN with RUVIEW_ALLOW_UNAUTHENTICATED=1.
# REST: Authorization: Bearer <token>
# WS: ws://<host>:3001/ws/sensing?token=<token>
- RUVIEW_API_TOKEN=${RUVIEW_API_TOKEN:-}
- RUVIEW_ALLOW_UNAUTHENTICATED=${RUVIEW_ALLOW_UNAUTHENTICATED:-}
# CSI_SOURCE controls the data source for the sensing server.
# Options: auto (default) — probe for ESP32 UDP then fall back to simulation
# esp32 — receive real CSI frames from an ESP32 on UDP port 5005
+46
View File
@@ -38,6 +38,52 @@ case "${1:-}" in
;;
esac
# ── #864: secure-by-default API auth for the sensing server ──────────────────
#
# The sensing server publishes a live RF-sensing REST API and WebSocket stream.
# Historically the Docker image shipped with RUVIEW_API_TOKEN empty, which makes
# bearer auth a no-op and exposes `/api/v1/*` and `/ws/sensing` to anyone who can
# reach the published ports. We now fail closed: if no token is supplied we
# generate a strong random one and print it, so the stream is never anonymous by
# default. Operators on a trusted, isolated LAN can opt back into the open
# posture explicitly with RUVIEW_ALLOW_UNAUTHENTICATED=1.
generate_token() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 32
elif [ -r /proc/sys/kernel/random/uuid ]; then
# Two UUIDs (dashes stripped) → 64 hex chars of kernel randomness.
printf '%s%s' \
"$(cat /proc/sys/kernel/random/uuid)" \
"$(cat /proc/sys/kernel/random/uuid)" | tr -d '-'
else
head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n'
fi
}
if [ -z "${RUVIEW_API_TOKEN:-}" ]; then
case "${RUVIEW_ALLOW_UNAUTHENTICATED:-}" in
1|true|TRUE|yes|YES)
echo "WARNING: RUVIEW_ALLOW_UNAUTHENTICATED is set — the sensing API and" >&2
echo " /ws/sensing stream will run UNAUTHENTICATED. Only do this on a" >&2
echo " trusted, isolated network (issue #864)." >&2
;;
*)
RUVIEW_API_TOKEN="$(generate_token)"
export RUVIEW_API_TOKEN
echo "============================================================" >&2
echo " RuView: no RUVIEW_API_TOKEN supplied — generated one for you:" >&2
echo " RUVIEW_API_TOKEN=${RUVIEW_API_TOKEN}" >&2
echo "" >&2
echo " REST: Authorization: Bearer <token>" >&2
echo " WS: ws://<host>:3001/ws/sensing?token=<token>" >&2
echo "" >&2
echo " Pin your own with -e RUVIEW_API_TOKEN=..., or run open on a" >&2
echo " trusted LAN with -e RUVIEW_ALLOW_UNAUTHENTICATED=1 (issue #864)." >&2
echo "============================================================" >&2
;;
esac
fi
# If the first argument looks like a flag (starts with -), prepend the
# server binary so users can just pass flags:
# docker run <image> --source esp32 --tick-ms 500