Harden uploads + production transport (security audit H1–H3, M1)

Harden uploads + production transport (security audit H1–H3, M1)

#50 in Riparion/riparion-cms — merged 2026-06-03

Implements the agreed remediation bundle from the full-app security audit.

Findings fixed

# Sev Fix
H1 High Upload memory-exhaustion DoS — add tower-http RequestBodyLimitLayer (24 MiB) to the router so Dioxus server fns (which read the whole body themselves, bypassing axum's default 2 MiB limit) can't be driven to OOM. Also reject oversized base64 before decoding in upload_media.
H2 High Decompression-bomb OOM — decode uploads through image::ImageReader with explicit max_image_width/height (10k px/side) instead of unbounded load_from_memory, so a tiny file declaring huge dimensions can't allocate GBs during to_rgba8/resize.
H3 High Session cookie shipped without Secure/HSTS — enable cookie_secure(true) + HSTS, gated on SITE_URL being https:// (or DX_COOKIE_SECURE=1) so local dx serve over http stays loginnable. HSTS conservative by default (max-age=86400; includeSubDomains, no preload); ramp via DX_HSTS.
M1 Med Content smuggling — validate uploaded bytes are an allowed raster image via magic-byte sniff (image::guess_format); the extension alone is client-controlled.

Dropped

  • L3 (ServeDir symlink hardening): tower-http 0.6 exposes no follow_symlinks toggle, and the upload path can't create symlinks (filename sanitizer + {id}_ prefix), so there's no app-reachable exploit. Noted rather than hacked around.

Not included (need a product decision)

  • M2 (pages have no per-object authz): fine if PAGES_WRITE stays admin-only; needs a can_edit_page check only if page authoring is delegated.

Notes

  • Cargo.lock also carries a pre-existing arium pin bump (already running in prod) — it provides the cookie_secure/hsts builder methods H3 relies on.
  • L6 (default postgres:postgres DB password) was already fixed out-of-band during the prod migration.

Verification

  • cargo fmt --all -- --check
  • cargo clippy on server,sqlite / server,postgres / web (wasm32) — all -D warnings clean ✅
  • cargo test --features server,sqlite — 42 passed ✅ (incl. the rendition tests exercising the rewritten decode path)

🤖 Generated with Claude Code

Last updated 2026-06-03