Problem
The TCP bridge (tcp_bridge.c, port 7373) requires no authentication and is the sharpest open surface on the device. The moment any LAN peer connects it:
- streams every device log to the socket (via the
esp_log_set_vprintf hook) — including info like the WiFi SSID — i.e. just connecting leaks data, and
- accepts hex-string commands that write arbitrary frames to the pool bus.
It's also advertised over mDNS as _pool-bridge._tcp, so it's discoverable.
It is fundamentally a debug/diagnostics console used interactively (nc/telnet) or by scripts, so the goal is to secure it without breaking nc — no TLS, no per-session browser login.
Recommended approach
1. Default-off + enable from the (authenticated) web UI — baseline
Biggest win for ~zero friction, because of who uses it:
- MQTT/Home Assistant users never touch 7373 → default-off costs them nothing.
- Power users flip one toggle on the web config page; once on,
nc poolcontrol-xxxx.local 7373 works as today.
- Don't advertise
_pool-bridge._tcp over mDNS when disabled (removes it from casual discovery/recon).
2. Token-gate the session when enabled
Low-friction static shared token sent as the first line (not a challenge/response handshake):
- Generate a random token with
esp_random() on first enable, store in NVS, display it on the authenticated web config page with a "rotate" button. Keep it separate from the admin password so it's safe to show and independently revocable.
- On connect, prompt
auth required and stream nothing / accept no commands until the matching token arrives. Constant-time compare; drop the connection on mismatch.
- Crucially, do not enable log forwarding to the socket until after the token passes — this closes the connect-time log leak (today, connecting alone turns on the log stream).
Friction for genuine use: one paste for an interactive user (once per session), one extra line for a script:
printf '%s\n' "$BRIDGE_TOKEN" | cat - commands.txt | nc poolcontrol.local 7373
3. Optional: timed enable ("open for 15 min")
Make the web-UI enable a timed unlock that auto-disables, to minimise standing exposure. Nice-to-have on top of #1+#2, not a replacement.
Explicitly rejected (too much friction)
- TLS on 7373 — kills
nc.
- Source-IP allowlist — brittle with DHCP.
- Per-command auth / challenge-response — overkill on a LAN; first-line token is enough.
Recommended combination
Default-off + don't-advertise-when-off (#1) plus first-line token + no log stream until authenticated (#2). Zero friction for the MQTT majority, one-paste friction for the debug minority; closes both the bus-write and log-leak vectors. Add timed-enable (#3) later if desired.
Dependency
Requires the web-UI auth work (#52) to land first — the bridge token must be shown/rotated on a trusted (authenticated) config page, otherwise the gate is meaningless. Natural order: web auth → bridge default-off + token, reusing the same NVS-config and config-page plumbing.
Decisions to make
- Token storage format and rotation UX.
- Whether to also split log-forwarding into its own opt-in (it's the main leak vector even post-token).
- Include timed-enable now or defer.
Problem
The TCP bridge (
tcp_bridge.c, port 7373) requires no authentication and is the sharpest open surface on the device. The moment any LAN peer connects it:esp_log_set_vprintfhook) — including info like the WiFi SSID — i.e. just connecting leaks data, andIt's also advertised over mDNS as
_pool-bridge._tcp, so it's discoverable.It is fundamentally a debug/diagnostics console used interactively (
nc/telnet) or by scripts, so the goal is to secure it without breakingnc— no TLS, no per-session browser login.Recommended approach
1. Default-off + enable from the (authenticated) web UI — baseline
Biggest win for ~zero friction, because of who uses it:
nc poolcontrol-xxxx.local 7373works as today._pool-bridge._tcpover mDNS when disabled (removes it from casual discovery/recon).2. Token-gate the session when enabled
Low-friction static shared token sent as the first line (not a challenge/response handshake):
esp_random()on first enable, store in NVS, display it on the authenticated web config page with a "rotate" button. Keep it separate from the admin password so it's safe to show and independently revocable.auth requiredand stream nothing / accept no commands until the matching token arrives. Constant-time compare; drop the connection on mismatch.Friction for genuine use: one paste for an interactive user (once per session), one extra line for a script:
3. Optional: timed enable ("open for 15 min")
Make the web-UI enable a timed unlock that auto-disables, to minimise standing exposure. Nice-to-have on top of #1+#2, not a replacement.
Explicitly rejected (too much friction)
nc.Recommended combination
Default-off + don't-advertise-when-off (#1) plus first-line token + no log stream until authenticated (#2). Zero friction for the MQTT majority, one-paste friction for the debug minority; closes both the bus-write and log-leak vectors. Add timed-enable (#3) later if desired.
Dependency
Requires the web-UI auth work (#52) to land first — the bridge token must be shown/rotated on a trusted (authenticated) config page, otherwise the gate is meaningless. Natural order: web auth → bridge default-off + token, reusing the same NVS-config and config-page plumbing.
Decisions to make