# Security > Status: threat model and security posture for the engine as implemented. volumen is a small, file-based blog engine: a single Ruby process exposing a read-only public JSON API and a session-authenticated admin, behind nginx. This document describes the trust model, the controls in place, and the known limitations. If you find a security issue, please email **opensource@petrbalvin.org** rather than opening a public issue. ## Trust model - **Content authors are trusted.** Posts are written by authenticated admin users. kramdown renders Markdown and **passes raw HTML through** on purpose, so an author can embed HTML. The stored/served `html` is therefore only as safe as the people who can log in. If you need to accept posts from untrusted authors, add an HTML sanitiser before serving. - **The public API is read-only.** It exposes published (non-draft) posts, tags, a feed and a sitemap. No public endpoint mutates state. ## Authentication - Passwords are hashed with **scrypt** via `OpenSSL::KDF` (`N=16384`, `r=8`, `p=1`, 32-byte key, 16-byte random salt). Verification uses `OpenSSL.fixed_length_secure_compare` (constant-time). - Login takes a **username + password**. The error message is intentionally generic ("Invalid username or password.") to avoid user enumeration. - Sessions use signed cookies (`Rack::Session::Cookie`) marked **`HttpOnly`** and **`SameSite=Strict`**. The **`Secure`** attribute is added automatically on HTTPS requests (detected via `request.ssl?` / `X-Forwarded-Proto`), so the cookie never travels over plain HTTP in production. The signing secret comes from `admin.session_key`; if it is shorter than 64 bytes a random secret is generated per start (sessions then reset on restart, so set a stable key in production). ## Authorization (roles) - Two roles: **admin** (full access, manages users) and **author** (posts and own account only). - User management (`add`/`delete`/role change) is gated by `require_admin!` **on the server**, not merely hidden in the UI. - Safeguards prevent lockout: the **last admin** cannot be deleted or demoted, and a user cannot delete their own account or change their own role. ## CSRF - Every state-changing form (`POST`) carries a per-session token (`SecureRandom.hex(32)`), validated with `Rack::Utils.secure_compare`. Requests without a valid token get `403`. - `SameSite=Strict` cookies provide a second, independent layer. ## Content Security Policy (admin) All `/admin/*` responses send a strict **Content-Security-Policy** header: ``` default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; form-action 'self'; frame-ancestors 'none' ``` The inline allowances are required by the server-rendered admin (ERB templates emit small inline `