v3.3.0 — Hardening: bugs & security #131
Loading…
Reference in a new issue
No description provided.
Delete branch "hardening/v3.3.0"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Cuts the v3.3.0 release: all 13 issues from the v3.3.0 milestone (July 2026 full-project review), plus reference-doc corrections and a changelog reconciliation.
Included
/askerror hygiene (#109), bot loopback bind + body cap (#111)./record startguard leak (#83), audio-cleanup data loss (#85), duplicate reminders / retire legacy ETA path (#90), deterministic lore-match index (#88), Discord embed size budget (#94), public analytics share-page proxy (#92).CLAUDE.md,ARCHITECTURE.md,docs/API.md,docs/DEVELOPMENT.md(#113); reconciled the changelog with the actual v0.10.0–v3.2.0 releases.⚠️ Breaking for existing installs
POSTGRES_APP_PASSWORD, setPOSTGRES_USER=questboard_admin, and run the one-time role migration indocs/OPERATIONS.md(§Existing Installs). Init scripts only run on an empty data dir, so existing databases need the manual migration..env.exampleplaceholder secrets or aSECRET_KEYshorter than 32 chars — verify each environment's.envbefore deploying.Verified locally: bot 158 passed, backend 358 passed, ruff clean, nginx config valid, DB privilege +
pg_dumpchain proven end-to-end.🤖 Generated with Claude Code
Neither nginx (prod) nor the Vite dev proxy forwarded /public/*, so GET /public/analytics/{token} fell through to the SPA catch-all and returned index.html, breaking the unauthenticated share-link feature. Mirror the existing /api/ proxy block/entry for /public/ in both. Closes #92 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Root CLAUDE.md's "Recording architecture" section described a not-yet-implemented, single mixed-mono-MP3 design; the shipped flow (webapp/backend/app/routers/bot.py, bot/questboard_bot/cogs/recording.py) actually records per-speaker 16 kHz WAVs + speakers.json to a per-session directory on audio_temp, POSTs the dir path to /api/bot/sessions/{id}/audio, and the process_audio Celery task transcribes each speaker independently and merges an attributed transcript before summarising. Rewrote the section to match, removed the "not yet implemented" status block and mono-MP3 wording. webapp/CLAUDE.md's DB-role wording now states the two-role model as enforced by webapp/postgres/init.sh for fresh installs (per #102 on this branch) and points existing installs at the new OPERATIONS.md runbook; removed its reference to the now-deleted init.sql. webapp/CLAUDE.md's stack table updated to match webapp/frontend/package.json (React 19.2.7, Vite 8.1.4, Tailwind 4.3.2, react-router-dom 7.15.0) instead of the stale React 18/Vite 6/Tailwind 3/react-router-dom 6 claim. Root CLAUDE.md and the Dockerfile's stage-header comments claimed Python 3.12 while both backend and bot stages build FROM python:3.14-slim; updated the comments to say 3.14 and noted the pending 3.12-vs-3.14 decision is tracked in #87 (v3.4.0, not in scope here) -- no base image or CI Python version changed. Closes #113 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>The recap email body was built by f-string interpolation with no escaping, so the GM-controlled session title and campaign name and the LLM-generated summary were delivered as live markup (MIMEText(..., "html")) to every recipient's mail client. Values are now html.escape()d before the newline→<br> conversion, so the inserted <br> tags survive, and CR/LF is stripped from the title before it goes into the Subject header. The bot /ask endpoint returned detail=f"LLM error: {exc}", which can leak the configured endpoint URL and other internals and violates the project's error contract. It now returns a generic detail and logs the exception server-side. Closes #109 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Two authorization gaps closed together (cross-component change). Bot — /record start|stop were invocable by any guild member. Both handlers now check authorization first, before reserving any recording state: a member is allowed only if they hold Discord Manage Server, or are the campaign's verified-linked GM (resolved via a new GET /api/bot/guilds/{guild_id}/gms). The check fails closed — an API error or an unlinked guild denies with a distinct "couldn't verify" message so a retry is understood as possibly resolving it, versus a plain "not authorized". Backend — POST /api/bot/sessions/{session_id}/audio authorized only the X-Bot-Key and never checked that the session's campaign belonged to the guild_id in the payload, so audio from guild A could be attached to a session in guild B. It now loads the session's campaign and requires campaign.guild_id == data.guild_id, returning 404 on mismatch (no cross-guild existence probing) and queuing no Celery task. Endpoint audit: /audio is the only /api/bot/* endpoint carrying both a session_id and a bot-supplied guild_id; the other session-keyed endpoints take no guild claim to cross-check, and /guilds/{guild_id}/* endpoints already scope by guild, so no further ownership checks are needed. Tests: bot permission gate for start/stop (Manage Server allow, GM allow, non-GM deny, API-failure/unlinked deny, and denial-before-state-mutation); backend cross-guild upload rejected with no task queued, plus the GM lookup endpoint. Closes #100 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Add the v3.3.0 hardening batch to CHANGELOG.md, and bring the reference docs in line with what the milestone actually changed: - ARCHITECTURE.md: recording pipeline rewritten from "planned refactor" to the shipped per-speaker WAV + /audio + Celery flow; reminders described as poll-based (ETA path retired); SSRF row updated to the host allowlist; celery_task_ids noted as legacy. - docs/API.md: /audio guild-ownership check + corrected response; new /bot/guilds/{guild_id}/gms endpoint; cancel no longer "revokes" reminders. - webapp/CLAUDE.md: Celery task table and celery_task_ids constraint. - bot/CLAUDE.md: Python 3.14 (#87), FFmpeg role as resampling. - docs/DEVELOPMENT.md, README.md: init.sh roles, recording wording. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>