Security

Production-grade defaults built in. Harden your app using examples/todo as the reference implementation.

LIVE

Server action round-trip

Same RPC as above, plus a validation failure (422) via docs_todo_add.

Built in (no setup)

  • CSRFX-Resuma-CSRF on actions and submits
  • Headers — CSP nonces, HSTS, X-Frame-Options, COOP, CORP
  • Rate limiting — per-IP on actions, submits, and exec routes; disk-backed in prod (RESUMA_RATE_BACKEND=disk, no Redis)
  • Origin check — blocks cross-origin POST abuse
  • SSR safety — escaped HTML + sanitized JSON state + JSON-LD
  • Client bundlesscript-src 'self'; TypeScript via ClientComponent

Trust boundaries

APIEscapingNotes
view! text/attrsAutoDefault for UI
View::raw()NoneTrusted static HTML only
with_head()PartialInline <style> / <script> get CSP nonces; external src scripts use 'self'
ClientComponentAttrs escapedIds restricted to [a-zA-Z0-9_-]
Resumability payloadSanitizedBlocks </script> breakouts

Rate limiting

Per-IP sliding window on actions, submits, and exec routes. Defaults: 120 action RPC/min, 60 submits/min — tune with RESUMA_RATE_ACTIONS and RESUMA_RATE_SUBMITS. No Redis or external datastore — Resuma ships built-in backends:

BackendWhenStorage
memoryLocal dev (default)In-process HashMap
diskRESUMA_ENV=production (auto){RESUMA_DATA_DIR}/rate-limit/ — file locks, multi-process on same volume

Set RESUMA_RATE_BACKEND=memory|disk to override. Mount a persistent volume at RESUMA_DATA_DIR on Fly/Docker so limits survive restarts. For multi-region or many machines without a shared volume, add edge rate limiting (nginx limit_req, Fly proxy, Cloudflare) — still no Redis in your app. Exec routes have separate limits — Exec security.

Guides

Quick start (ResumaApp)

mod security;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    security::install();
    ResumaApp::new()
        .page("/", || Home::render(HomeProps::default()))
        .serve(security::serve_options())
        .await
}