mirror of
https://github.com/ruvnet/RuView
synced 2026-07-24 17:43:20 +00:00
3347e258e6
The previous commit claimed WebSocket upgrades were gated. They were not. Found
by an adversarial cross-vendor review, reproduced, and fixed here.
MEASURED BEFORE (auth ON via RUVIEW_API_TOKEN, no credential presented):
HTTP port :3991 /ws/sensing -> 401 (what the previous fix covered)
HTTP port :3991 /ws/field -> 101 HOLE
WS port :3990 /ws/sensing -> 101 HOLE
WS port :3990 /ws/field -> 101 HOLE
control :3991 /api/v1/models -> 401
Two independent defects, both from the same root cause — routes registered
AFTER an axum `.layer()` are silently exempt from it:
1. The dedicated WebSocket server on `--ws-port` was built with ONLY
`host_validation::require_allowed_host`. `require_bearer` was never applied
to it at all. My earlier verification only ever probed the HTTP port and I
generalised from it.
This is the worse of the two, because it is the port the UI actually uses:
`ui/services/sensing.service.js` maps HTTP 8080 -> WS 8765. So the previous
fix protected a path the browser never takes, while the path it does take
stayed open.
2. On the HTTP router, `/ws/field` (ADR-262) was `.merge()`d AFTER the
`require_bearer` layer, so it bypassed authentication entirely. The auth
layer is now applied after the merge, and the comment says why the ordering
is load-bearing.
MEASURED AFTER, same conditions:
:3989 /ws/sensing -> 401 · :3989 /ws/field -> 401
:3988 /ws/sensing -> 401 · :3988 /ws/field -> 401
with bearer on the upgrade: 101 on both ports
ticket minted at POST /api/v1/ws-ticket on the HTTP port and redeemed on the
WS port: 101 (AuthState shares its TicketStore via Arc)
auth OFF: 101 — unchanged, no regression
526 sensing-server tests still pass.
TEST GAP, stated rather than papered over: no automated test covers this. The
defect is in router WIRING in main.rs, not in logic a unit test reaches — both
holes were invisible to 526 green tests and to the ws_gate_tests suite, which
builds its own Router and therefore cannot see how the real one is assembled.
Catching this class needs an integration test that boots the binary and probes
BOTH ports; that is the honest follow-up.
Co-Authored-By: Ruflo & AQE