Commit Graph

8 Commits

Author SHA1 Message Date
rUv 249d6c327f ADR-115: Home Assistant + Matter integration (#778)
Closes ADR-115's MQTT track (HA-DISCO + HA-MIND + HA-FABRIC scaffolding).

Headline:
- 21 entity kinds per node (11 raw + 10 semantic primitives)
- MQTT auto-discovery with HA conventions
- Matter Bridge scaffolding (SDK wiring deferred to v0.7.1 per ADR §9.10)
- Privacy mode strips biometrics at the wire, semantic primitives keep working
- 420+ lib tests, mosquitto-backed integration tests, property-based fuzzing
- 8 starter HA Blueprints + 3 Lovelace dashboards shipped

Tracking issue: #776
2026-05-23 16:13:28 -04:00
rUv 004a63e82d fix(security): audit — fix RUSTSEC vulns, clippy warnings, dead code (#769)
- Upgrade openssl to 0.10.78 (CVE-2026-41676), jsonwebtoken to 9.4
- Suppress unmaintained-only/no-CVE advisories in .cargo/audit.toml
  with per-entry rationale
- Fix all `cargo clippy --all-targets -- -D warnings` errors across
  35 crates: derivable_impls, needless_range_loop, map_or→is_some_and/
  is_none_or, await_holding_lock (drop MutexGuard before .await),
  ptr_arg (&mut Vec→&mut [T]), useless_conversion, approximate_constant
  (2.718→E, 3.14→PI), field_reassign_with_default, manual_inspect,
  useless_vec, lines_filter_map_ok, print_literal, dead_code
- Apply `cargo fmt --all`
- Pre-existing test failure in wifi-densepose-signal
  (test_estimate_occupancy_noise_only) is not introduced by this PR
2026-05-23 05:36:13 -04:00
rUv 67fec45e61 feat(edge-registry): ADR-102 — surface Cognitum cog catalog via /api/v1/edge/registry (#648)
* feat(edge-registry): ADR-102 — surface Cognitum cog catalog via /api/v1/edge/registry

Adds a new sensing-server endpoint that fetches and caches the canonical
Cognitum app registry at
https://storage.googleapis.com/cognitum-apps/app-registry.json (105 cogs
across 11 categories as of v2.1.0). RuView previously had no live
awareness of the catalog — the README's capability table was hand-
curated and went stale as Cognitum shipped new cogs (the registry was
last updated 6 days ago).

ADR:
* docs/adr/ADR-102-edge-module-registry.md — full design, response
  shape, configuration flags, failure modes, and a 12-row security
  review covering SSRF, response inflation, ?refresh abuse, stale-serve
  semantics, TLS, cache poisoning, JSON-panic resistance, etc.

Code:
* v2/.../edge_registry.rs — EdgeRegistry struct + UreqFetcher +
  MockFetcher trait + 7 unit tests. RwLock<Option<CachedEntry>> with
  stale-on-error fallback. MAX_PAYLOAD_BYTES=8 MiB, 10s wire timeout.
* v2/.../main.rs — constructs Option<Arc<EdgeRegistry>> at startup,
  registers GET /api/v1/edge/registry handler, wires Extension layer.
  Handler runs the blocking ureq fetch via tokio::task::spawn_blocking
  so the async runtime stays free.
* v2/.../cli.rs / main.rs Args — three new flags (per user request to
  "allow the registry to be disabled or changed"):
    --edge-registry-url <URL>       (env RUVIEW_EDGE_REGISTRY_URL)
    --edge-registry-ttl-secs <N>    (env RUVIEW_EDGE_REGISTRY_TTL_SECS)
    --no-edge-registry              (env RUVIEW_NO_EDGE_REGISTRY)
  When --no-edge-registry is set or the URL is empty, the endpoint
  returns 404.

Cargo.toml: adds ureq (rustls), sha2, thiserror as direct deps.

README:
* New collapsed "🧩 Edge Module Catalog" section with the full 105-cog
  table generated from the registry, grouped by category with practical
  one-line descriptions (e.g. "Spots irregular heartbeats and abnormal
  heart rhythms", "Detects walking problems and scores fall risk").
  Links to https://seed.cognitum.one/store and the local appliance
  /cogs page. Sits between the HF model section and How It Works.

Tests (7/7 pass):
  first_call_hits_upstream_and_caches
  ttl_expiry_triggers_refetch
  force_refresh_bypasses_fresh_cache
  stale_serve_on_upstream_failure_after_cached_success
  no_cache_no_upstream_returns_error
  upstream_invalid_json_is_treated_as_error
  upstream_sha256_is_deterministic

Security highlights (full review in ADR-102 §"Security review"):
- The registry is metadata-only; per-cog binary signatures (ADR-100)
  remain the trust root for installs. A compromised registry can
  mislead a human reader but cannot ship malicious binaries.
- 8 MiB cap + 10s timeout + Option<Arc<...>> via Extension layer means
  the endpoint can't be used to exhaust memory or pin tokio threads.
- Stale-on-error responses carry an explicit `stale: true` field so
  upstream outages are visible to consumers rather than silently
  masked.
- Endpoint sits behind the existing RUVIEW_API_TOKEN bearer gate when
  set, otherwise unauthenticated (registry contents are public anyway).

* chore: refresh Cargo.lock for ureq/sha2/thiserror deps added by ADR-102
2026-05-19 18:08:43 -04:00
rUv 72bbd256e7 fix(security): path-traversal guard on 5 sensing-server endpoints (closes #615) (#616)
Reported by @bannned-bit. Five endpoints in
v2/crates/wifi-densepose-sensing-server embedded user-controlled
identifiers in format!() paths with no sanitization:

  recording.rs       POST   /api/v1/recording/start       (session_name)
  recording.rs       GET    /api/v1/recording/download/:id (id)
  recording.rs       DELETE /api/v1/recording/delete/:id   (id)
  model_manager.rs   POST   /api/v1/models/load           (model_id)
  training_api.rs    load_recording_frames                (dataset_ids[])

Each unauthenticated caller could:
- READ arbitrary files via ../../etc/passwd, ../../.env, etc.
- WRITE attacker-controlled JSONL via recording/start
- LOAD attacker-controlled .rvf model files
- DELETE arbitrary files the server process can touch

New `path_safety` module exports `safe_id(&str) -> Result<&str, PathSafetyError>`
that enforces the rejection envelope BEFORE any user input reaches a
format!() that builds a path:

  - Allowed character set: [A-Za-z0-9._-]
  - Reject leading '.' (rules out '.', '..', '.env', hidden files)
  - Reject empty strings
  - Reject anything > 64 bytes
  - Reject all whitespace, path separators, null bytes, non-ASCII

Applied at all 5 sites. Errors return 400 Bad Request (download) /
status:"error" JSON (others) — not panics.

9 unit tests in path_safety::tests cover:
  - accepts simple alphanumeric / hyphen / underscore / dot
  - rejects empty, leading dot, path separators ('/', '\'),
    null byte, whitespace, shell specials, non-ASCII (including
    fullwidth slash U+FF0F), too-long, boundary at MAX_ID_LEN

  test result: ok. 9 passed; 0 failed
  cargo build -p wifi-densepose-sensing-server --no-default-features: 33s

Fix-marker RuView#615 in scripts/fix-markers.json prevents removing the
guard at any of the 5 call sites. CHANGELOG entry under [Unreleased] /
Security documents the patched endpoints and the rejection envelope.

Severity: critical per reporter — five remotely-reachable paths to read,
write, or delete arbitrary files. Hot per-request paths, not edge cases.
2026-05-17 19:59:20 -04:00
@aaronjmars 3685d16a49 fix(security): host-header allowlist on sensing-server HTTP + WS — DNS rebinding (#580)
The sensing-server binds to 127.0.0.1 by default with no `Host` header
validation on either router. A foreign page can lower its DNS TTL,
re-resolve to 127.0.0.1 after the browser has accepted the origin, and
then read live pose + vital signs from /api/v1/* + /ws/sensing as
same-origin against the attacker's hostname. When `RUVIEW_API_TOKEN` is
unset (the documented LAN-mode default from #443/#547) the attacker
can also drive state-mutating POSTs (recording/start, models/load,
adaptive/train, calibration/start, sona/activate).

Defense: a small `host_validation` axum middleware that pins the `Host`
header to a configurable allowlist. The loopback names (`localhost`,
`127.0.0.1`, `[::1]`, each with or without a port) are always in the
set, so default 127.0.0.1 deployments keep working from the local
browser without any configuration change. Operators who bind to a
routable address extend the set with one or more `--allowed-host`
flags or a comma-separated `SENSING_ALLOWED_HOSTS` env var.
Reverse-proxy deployments that already canonicalise `Host` opt out
with `--disable-host-validation`.

The layer is wired into both the dedicated WebSocket router on
`--ws-port` (8765) and the main HTTP router on `--http-port` (8080),
so /ws/sensing on either listener is covered. Rejection responses are
`421 Misdirected Request` (the correct status for a request that
arrived at a server that does not consider the supplied `Host`
authoritative); missing `Host` is `400 Bad Request`.

CWE-346 (Origin Validation Error), CWE-350 (Reliance on Reverse DNS).
Severity: high.

Tests: 13 new unit tests on the middleware (loopback defaults,
case-insensitivity, IPv6 bracketing, port stripping, env-var/CLI
merge, foreign-host rejection on /health + /ws/*, disabled-allowlist
escape hatch). Full suite: 220/220 pass under
`cargo test -p wifi-densepose-sensing-server --no-default-features`.

Co-authored-by: Aeon <aeon@aaronjmars.com>
2026-05-17 17:27:00 -04:00
ruv 94ef125240 feat(sensing-server): introspection module skeleton (ADR-099 D1+D7+D8)
Adds the per-frame introspection state that ADR-099 specifies, plus the two
midstream dependencies. Pure addition — no other code touched.

  v2/crates/wifi-densepose-sensing-server/Cargo.toml
    + midstreamer-temporal-compare = "0.2"
    + midstreamer-attractor        = "0.2"

  v2/crates/wifi-densepose-sensing-server/src/introspection.rs (new, 530 lines)
    pub struct IntrospectionState
      ├─ midstreamer-attractor's AttractorAnalyzer (regime + Lyapunov)
      ├─ SignatureLibrary (JSON-loaded labelled segments)
      ├─ VecDeque<f64> sliding amplitude buffer (default 128 points)
      └─ update(timestamp_ns, derived_feature) — never window-blocked
         + snapshot() -> IntrospectionSnapshot
            { timestamp_ns, frame_count, regime, lyapunov_exponent,
              attractor_dim, attractor_confidence, top_k_similarity }
    pub enum Regime { Idle, Periodic, Transient, Chaotic, Unknown }
    pub struct Signature { id, label, vectors, dtw, promotion_threshold }
    pub struct SimilarityMatch { signature_id, score, above_threshold }

DTW path is currently a host-side stand-in (length-normalised L1 with the
real DTW call deferred to I3/I5 once vec128 embeddings exist — ADR-099 P1).
The attractor path is wired to midstream directly. The analyze() step only
runs every N frames (default 8) to stay under the per-frame ms budget.

8 unit tests (snapshot defaults, frame-count + timestamp advance, empty
library, scoring + ordering invariants, threshold gating, empty-signature
fault-tolerance, regime classification after 200 frames). 199 → 207 lib tests,
0 failures. cargo build clean (only pre-existing warnings).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-13 22:50:58 -04:00
ruv c641fc44ae feat(docker+sensing-server): refresh Docker publish + opt-in bearer-token API auth
Closes #520, #514, #443.

## #520 / #514 — stale Docker image, missing UI assets

`ruvnet/wifi-densepose:latest` was published before `ui/observatory*` and
`ui/pose-fusion*` were added; users see /app/ui missing those files and the
v0.6+ packet format doesn't reach the server. Two fixes:

1. `docker/Dockerfile.rust` now `RUN`s a build-time guard after `COPY ui/`
   that fails the build if `index.html` / `observatory.html` / `pose-fusion.html`
   / `viz.html` (or the `observatory/` / `pose-fusion/` / `components/` /
   `services/` directories) are missing, plus an exec-bit check on
   `/app/sensing-server`. A stale image can never be silently produced again.

2. New `.github/workflows/sensing-server-docker.yml` rebuilds + pushes on
   every change to the Dockerfile, the server crate, the signal/vitals/
   wifiscan crates, the workspace manifests, the `ui/` tree, or itself —
   plus `v*` tags and manual dispatch. Pushes to both `docker.io/ruvnet/
   wifi-densepose` AND `ghcr.io/ruvnet/wifi-densepose` with `latest` +
   `vX.Y.Z` + `sha-<short>` tags, then post-push smoke-tests the artifact:
   /health, /api/v1/info, the observatory + pose-fusion HTML, AND the
   bearer-auth path (no token → 401, wrong → 401, correct → 200). Uses the
   `DOCKERHUB_USERNAME`/`DOCKERHUB_TOKEN` repo secrets; ghcr.io rides on
   the workflow's GITHUB_TOKEN.

## #443 — sensing-server REST API auth model

QE security audit raised that 40+ /api/v1/* routes have no auth layer with
a default `0.0.0.0` bind. New `wifi_densepose_sensing_server::bearer_auth`
module + middleware:

  - Env-var-gated: `RUVIEW_API_TOKEN` unset/empty ⇒ middleware is a no-op
    (current LAN-mode behaviour preserved — **no default change**); set ⇒
    every `/api/v1/*` request must carry `Authorization: Bearer <token>`
    or the server returns 401.
  - Constant-time byte compare via local `ct_eq` (no new dep).
  - `/health*`, `/ws/sensing`, and `/ui/*` are intentionally never gated
    (orchestrator probes + local browsers).
  - Startup logs which mode is active and warns when auth is ON with a
    `0.0.0.0` bind.
  - 8 unit tests on the middleware via `tower::ServiceExt::oneshot`
    (sensing-server lib tests 191 → 199, 0 failures).

Verified locally: `cargo build --workspace --no-default-features` ✓,
`cargo test -p wifi-densepose-sensing-server --no-default-features` ✓.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-13 08:52:25 -04:00
rUv f49c722764 chore(repo): rename rust-port/wifi-densepose-rs → v2/ (flatten to one level) (#427)
The Rust port lived two directories deep (rust-port/wifi-densepose-rs/)
without any sibling under rust-port/ that warranted the extra level.
Move the whole workspace up to v2/ to match v1/ (Python) at the same
depth and shorten every cd / build command across the repo.

git mv preserves history for all tracked files. 60 files updated for
path references (CI workflows, ADRs, docs, scripts, READMEs, internal
.claude-flow state). Two manual fixes for relative-cd paths in
CLAUDE.md and ADR-043 that became wrong after the depth change
(cd ../.. → cd ..).

Validated:
- cargo check --workspace --no-default-features → clean (after target/
  nuke; the gitignored target/ was carried by the OS rename and had
  hard-coded old paths in build scripts)
- cargo test --workspace --no-default-features → 1,539 passed, 0 failed,
  8 ignored (same totals as pre-rename)
- ESP32-S3 on COM7 → still streaming live CSI (cb #40300, RSSI -64 dBm)

After-merge follow-up: contributors should `rm -rf v2/target` once and
let cargo regenerate from the new path.
2026-04-25 21:28:13 -04:00