Viewer requests trigger GitHub lookups and host log reads (plugin updates, chat) #98

Closed
opened 2026-10-09 15:31:09 -04:00 by goddard · 1 comment
Collaborator

Two GET endpoints that any viewer can call make the controller do outside work on every request. They are in addition to the ones in #72.

  • plugins/updates/+server.ts:18-23 runs a GitHub release lookup for every tracked plugin (or one with ?pluginId=). There's a five-minute cache.
  • chat/+server.ts:16 calls collectServerChat, which reads the log through readLogChunk (chat.ts:139, logs.ts:63) and queues a host-agent operation (native.ts:73). Calls only merge while one is already in flight (chat.ts:130-131).
  • ChatPanel.svelte:67 polls chat every 5 s for each open tab, even when the server is empty.

The #62 fix made the worker skip empty servers and unchanged logs, but this API path still reads every time. GETs also skip the same-origin check, so a cross-site link can trigger them.

Fix

  • Restrict /api/plugins/updates to operators
  • Serve chat from the worker's saved reader state, and start a new agent read only when the last one for that server is older than a minimum age
Two GET endpoints that any viewer can call make the controller do outside work on every request. They are in addition to the ones in #72. - `plugins/updates/+server.ts:18-23` runs a GitHub release lookup for every tracked plugin (or one with `?pluginId=`). There's a five-minute cache. - `chat/+server.ts:16` calls `collectServerChat`, which reads the log through `readLogChunk` (`chat.ts:139`, `logs.ts:63`) and queues a host-agent operation (`native.ts:73`). Calls only merge while one is already in flight (`chat.ts:130-131`). - `ChatPanel.svelte:67` polls chat every 5 s for each open tab, even when the server is empty. The #62 fix made the worker skip empty servers and unchanged logs, but this API path still reads every time. GETs also skip the same-origin check, so a cross-site link can trigger them. **Fix** - [x] Restrict `/api/plugins/updates` to operators - [x] Serve chat from the worker's saved reader state, and start a new agent read only when the last one for that server is older than a minimum age
goddard added the
priority
low
type/hardening
labels 2026-10-09 15:31:09 -04:00
josh was assigned by goddard 2026-10-09 15:31:09 -04:00
Author
Collaborator

Reviewed f3b9699317 against current HEAD 4501eba873. Both fix items are done, so I'm closing this.

Checklist

  • Operator-only plugin update checks. The resolved /api/plugins/updates route now requires operator permission for GET and HEAD. Guard tests verify anonymous requests stop before the handler, viewer/super-viewer/moderator requests are denied, and operator/admin requests remain allowed. Query parameters cannot change this route classification. The Plugins UI already gates its update controls on operational access.
  • Share and age-limit chat reads. The chat API now calls readServerChat, which reuses the worker's reader state and existing in-flight work. New on-demand reads wait until the previous read completed at least 15 seconds ago; missing logs and errors observe the same cooldown. Cache keys include server registration, native instance and generation, preventing a replacement from inheriting stale state. Cached responses still filter against the current match, while background collection retains the final read after players leave.

Review notes

  • The chat regression tests cover repeated viewers, the exact 15-second boundary, slow worker reads, missing/error cooldowns, replacement identities, late old-generation results, match boundaries and final collection after departure.
  • Combined validation for #63/#98/#112 at the stated HEAD passed all 403 tests across 17 relevant files after the sandbox-blocked recovery subprocess test was rerun successfully. Type/style checks and lint passed.
  • Native-host documentation describes the shared cooldown. Reviewed current route, guard, worker, cache and UI callers; no blocking regressions found.
Reviewed f3b9699317fe464f29cfe78e0174b3220bbf0fa4 against current HEAD 4501eba8735ceacccc924138502ed424b3310020. Both fix items are done, so I'm closing this. **Checklist** - [x] **Operator-only plugin update checks.** The resolved `/api/plugins/updates` route now requires operator permission for GET and HEAD. Guard tests verify anonymous requests stop before the handler, viewer/super-viewer/moderator requests are denied, and operator/admin requests remain allowed. Query parameters cannot change this route classification. The Plugins UI already gates its update controls on operational access. - [x] **Share and age-limit chat reads.** The chat API now calls `readServerChat`, which reuses the worker's reader state and existing in-flight work. New on-demand reads wait until the previous read completed at least 15 seconds ago; missing logs and errors observe the same cooldown. Cache keys include server registration, native instance and generation, preventing a replacement from inheriting stale state. Cached responses still filter against the current match, while background collection retains the final read after players leave. **Review notes** - The chat regression tests cover repeated viewers, the exact 15-second boundary, slow worker reads, missing/error cooldowns, replacement identities, late old-generation results, match boundaries and final collection after departure. - Combined validation for #63/#98/#112 at the stated HEAD passed all 403 tests across 17 relevant files after the sandbox-blocked recovery subprocess test was rerun successfully. Type/style checks and lint passed. - Native-host documentation describes the shared cooldown. Reviewed current route, guard, worker, cache and UI callers; no blocking regressions found.
Sign in to join this conversation.