Fix bug-audit round 3: PG image decode, theme SSR, SSE resilience, async I/O
Fix bug-audit round 3: PG image decode, theme SSR, SSE resilience, async I/O
#49 in Riparion/riparion-cms — merged 2026-06-02
Summary
A multi-agent audit (DB/SQL, auth/authz, XSS/escaping, Dioxus/SSR, logic & KISS/DRY) of the codebase. Each finding was verified against the actual code to rule out false positives before fixing. The codebase came out well-defended — no SQL injection, no auth-bypass, no stored-XSS were found. This PR addresses the real defects and worthwhile cleanups.
HIGH
- Postgres responsive images broken.
srcsets_for_urlsdecodedmedia_variants.width(INTEGER/int4) intoi64. sqlx-postgres is strict about integer width, so this errored at runtime for any post with local-upload renditions — the whole responsive-image feature failed on the Postgres backend (worked on SQLite, which decodes all ints as i64). Now decoded asi32, matching the insert side. - Theme hue FOUC / missing SSR styling. The
--brand-hueoverride<style>was fetched withuse_resource, which doesn't resolve during SSR — custom-hue sites rendered the compiled-in default server-side and snapped after hydration. Now resolved server-side via a newThemeStylecomponent (use_server_future), so the override is baked into the SSR HTML (same fix already applied tofeed_layout).
MEDIUM
- SSE loops survive error frames.
use_live/use_admin_liveusedwhile let Some(Ok(..)), tearing theEventSourcedown permanently on the firstErr(e.g. the admin stream's 403, or any terminal network error), killing live presence/comments/reactions until a full reload. They now skipErrframes instead. - Async disk I/O + shared rendition helper (DRY). Extracted
persist_renditions(usestokio::fs, logs failures) shared by the upload path and the backfill — previously duplicated and drifting. Media upload/delete now usetokio::fsso disk writes no longer block a tokio worker, and variant-insert failures are logged rather than silently dropped (which orphaned renditions on disk). - Bounded slug allocation.
unique_slugcapped its sequential-2, -3, …probing, then falls back to a random hex suffix.
LOW
escape_attrthesrcset/sizesvalues inrewrite_inline_images(parity with theResponsiveImgwidget).- Generous length caps on authored post/page
title/body/excerpt/seo_description(parity with the comment path). RelatedPostsreads its resource viaread()(subscribed) into an owned value instead ofread_unchecked().
Deferred items (intentional trade-offs / larger changes) are documented in the local TODO_BUGS.md.
Test plan
cargo fmt --all -- --check✅cargo clippy --no-default-features --features server,sqlite --all-targets -- -D warnings✅cargo clippy --no-default-features --features server,postgres --all-targets -- -D warnings✅cargo clippy --no-default-features --features web --target wasm32-unknown-unknown -- -D warnings -A dead_code✅cargo test --no-default-features --features server,sqlite→ 42 passed ✅- Postgres test suite runs in CI (needs a live DB).
🤖 Generated with Claude Code
Last updated 2026-06-03
Links to this note
Credits
Merged pull requests, newest first.