• v3.3.0 aa3b4299db

    v3.3.0
    All checks were successful
    CI / Frontend tests, audit, and build (push) Successful in 1m18s
    CI / Bot tests and audit (push) Successful in 2m2s
    Release / Create Forgejo release (push) Successful in 1m55s
    CI / Backend lint (ruff) (push) Successful in 2m17s
    CI / Docker image build (push) Successful in 2m48s
    CI / Backend migration, tests, and audit (push) Successful in 3m31s
    Stable

    claude-bot released this 2026-07-15 04:38:26 +00:00 | 776 commits to main since this release

    Hardening: bugs & security.

    Correctness and security fixes from the July 2026 full-project review. All 13
    milestone issues; backend and bot test suites green.

    Security

    • [webapp] Discord webhook SSRF allowlistdiscord_webhook_url (campaign
      and admin-fallback) is validated with real URL parsing against a Discord host
      allowlist (discord.com, discordapp.com, ptb.discord.com,
      canary.discord.com; scheme https; path /api/webhooks/) at write time,
      and re-checked immediately before every server-side POST so legacy rows are
      neutralised — a non-Discord URL is logged and skipped, never fetched. Closes a
      server-side request forgery vector against the deployment's internal network.
    • [webapp+bot] Recording authorization/record start|stop now require the
      invoker to hold Discord Manage Server or be the campaign's verified-linked GM
      (fails closed on lookup error); the audio-upload endpoint verifies the
      session's campaign belongs to the supplied guild_id (404 on mismatch, no
      task queued), preventing cross-guild audio attachment. New
      GET /api/bot/guilds/{guild_id}/gms supports the bot-side check.
    • [webapp] Reject placeholder/weak secrets at startupSECRET_KEY (rejects
      the .env.example placeholder, requires ≥ 32 chars), DATABASE_URL /
      DATABASE_MIGRATE_URL (reject changeme/changeme_migrate passwords, parsed
      not substring-matched), and bot_api_key (rejects placeholders / < 16 chars
      when set) fail fast with an actionable message.
    • [webapp] DML-only app DB role — the app now connects as a non-superuser
      questboard role (DML only); POSTGRES_USER becomes a dedicated superuser
      questboard_admin used only for break-glass; pg_dump backups run as the
      schema-owning questboard_migrate role. Breaking for existing installs
      see the "Existing Installs" runbook in docs/OPERATIONS.md. Requires
      POSTGRES_USER=questboard_admin and a new POSTGRES_APP_PASSWORD in .env.
    • [webapp] Recap-email HTML escaping — GM- and LLM-supplied values
      (session_title, campaign_name, summary) are HTML-escaped in recap
      emails, and CR/LF are stripped from the subject (header-injection guard).
    • [webapp] Stop echoing raw LLM errors — the /ask endpoint returns a
      generic detail and logs the real exception server-side.
    • [bot] HTTP server binds loopback by defaultHTTP_HOST defaults to
      127.0.0.1 (compose overrides to 0.0.0.0 for the internal network); the
      aiohttp request-body cap is now pinned explicitly.

    Bug fixes

    • [bot] /record start guard leak — a bad session UUID (or the user not
      being in voice) no longer strands the guild in the "starting" set, which had
      permanently locked out recording until a bot restart.
    • [bot] Audio cleanup no longer deletes queued recordings — the audio_temp
      sweep runs once per process (not on every gateway reconnect) and skips any
      session directory the bot has marked as handed off to the backend, so
      recordings awaiting or undergoing processing survive reconnects and restarts.
    • [webapp] Duplicate reminder delivery removed — the legacy ETA-scheduled
      reminder path is retired; the poll_session_reminders Beat task (deduped by
      session_reminders_sent rows) is the sole delivery mechanism, so a rescheduled
      session is reminded exactly once per offset. celery_task_ids is no longer
      written.
    • [webapp] Deterministic lore-match chunk index — replaced PYTHONHASHSEED-
      salted builtin hash() with a SHA-1 digest, so lore consolidation survives a
      worker restart mid-pipeline instead of failing with a consolidation timeout.
    • [bot] Discord embed size budget — a shared fit_embed helper keeps the
      whole embed (title + description + fields + footer) under Discord's 6000-char
      total, so /recap and summary embeds no longer fail outright on long content.
    • [webapp] Public analytics share page — the frontend now proxies /public/
      to the backend (nginx + Vite dev), so analytics share links load instead of
      returning the SPA HTML.

    Docs

    • Corrected the recording architecture (per-speaker WAV + /audio pipeline, no
      longer "not yet implemented"), the DB-role model, frontend stack versions, and
      Python base-image version across CLAUDE.md, webapp/CLAUDE.md,
      bot/CLAUDE.md, ARCHITECTURE.md, docs/API.md, and docs/DEVELOPMENT.md.
    Downloads