webui: real speeds, honest actions, and hardening

Address the review of the webui plugin:

- Live download rates. A background sampler polls the daemon once per
  second, derives per-torrent dlspeed from successive byte counts (EWMA
  smoothed), and computes a real ETA. dl_info_speed now aggregates the
  fleet instead of reporting a hardcoded 0.

- Single shared snapshot. The sampler publishes one cached snapshot that
  /api/snapshot, /api/torrents and every SSE stream serve, so N browser
  tabs no longer each poll the engine and race the speed table. SSE
  waiters block on a condition and wake promptly on shutdown.

- Honest /api/action. The engine has no pause/resume/recheck/queue verbs,
  so the endpoint returns 501 with an explanatory message instead of
  claiming success.

- Reject oversized uploads with 413 instead of silently truncating a
  torrent into garbage.

- Auth hardening: constant-time credential comparison, CSPRNG-only token
  generation via getrandom (fail closed, no weak fallback), oldest-session
  eviction instead of clobbering slot 0, and a warning when bound to a
  non-loopback address.

- Cap concurrent connections (503 beyond the limit) so a client can't
  spawn unbounded threads.

- nautd: tear down plugins (joining the webui's threads) before freeing
  torrent tasks, closing a shutdown-time use-after-free window where an
  in-flight request could touch freed state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ookami125 2026-06-17 00:29:52 -04:00
parent 8dde48c05a
commit 41ed172272
2 changed files with 402 additions and 97 deletions

View file

@ -957,6 +957,11 @@ int main(int argc, char **argv) {
close(listener);
unlink(socket_path);
/* Tear down plugins first: a plugin like webui runs its own threads that
* call back into the daemon via RPC, so it must be stopped (and its
* threads joined) before we free the torrent tasks those calls touch. */
naut_plugin_manager_destroy(state.plugins);
state.plugins = NULL;
naut_script_destroy(state.script);
state.script = NULL;
stop_torrents(&state);
@ -966,7 +971,6 @@ int main(int argc, char **argv) {
for (size_t i = 0; i < state.subscriber_count; i++)
close(state.subscribers[i]);
pthread_mutex_unlock(&state.subscriber_lock);
naut_plugin_manager_destroy(state.plugins);
naut_rpc_registry_destroy(state.rpc);
naut_event_bus_destroy(state.events);
pthread_mutex_destroy(&state.subscriber_lock);