Security model
Devora.js treats security as a default, not an opt-in plugin — every app gets a set of protections whether or not its app.config.ts declares a security block at all.
CSP, HSTS, and frame options
Every response carries these headers by default:
Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'— locks scripts, connections, and images to same-origin, blocking injected, inline, or eval'd script execution.style-srcadditionally allows'unsafe-inline'as a deliberate, documented tradeoff: React'sstyle={{...}}prop compiles to an inlinestyleattribute, which a strict default would otherwise silently break for every app.X-Frame-Options: DENY— blocks the app from being framed by another site.Strict-Transport-Security: max-age=63072000; includeSubDomains— a two-year HSTS policy. This header is a no-op over plain HTTP (browsers only honor it on responses received over HTTPS), so it's safe to always send, including in dev.
Each of these is overridable per app (security.csp, security.frameOptions, security.hsts in app.config.ts) — the point is that you have to opt out, not opt in.
Sessions and CSRF
The session system is a carrier, not an identity provider — it doesn't check credentials, it just makes a session survive across requests tamper-evidently once your own code decides someone is logged in. The request context's setSession(data) signs an HMAC cookie using Node's built-in crypto (no external dependency); its requireAuth() throws if there's no valid session, and rejects a tampered cookie rather than trusting it. A shared app gets one project-wide cookie; an isolated app gets its own cookie name and can use its own secret.
CSRF protection follows the same ad hoc, explicit-call shape rather than framework-injected middleware — a route that mutates state calls the context's verifyCsrf(formData) itself, the same way it calls requireAuth(). It's a double-submit-cookie pattern with one difference from the textbook version: the token is embedded server-side into the rendered form rather than read from the cookie by client JS, which means the cookie can stay HttpOnly with no downside. Session and CSRF cookies both get the Secure attribute in production (conditional on environment, so plain HTTP still works in dev).
In production, an app with auth: "shared" or "isolated" requires its session secret to be set (DEVORA_SESSION_SECRET or DEVORA_SESSION_SECRET_<APPNAME>) — the framework throws rather than falling back to an insecure default. An app with auth: "none" never reads or requires any session-related variable at all.
What's bring-your-own, and why
Devora.js deliberately does not ship an ORM, an auth/identity provider, or file storage in v1. Checking who a request actually comes from — verifying a password, validating a token against an identity provider like Clerk or Lucia — is left to you, plugged into the session carrier described above via your own DB client (Prisma, Drizzle, or anything else). This is deliberate scope control: the framework's job is to make the plumbing (signed cookies, CSRF, security headers) secure and boring by default, not to compete with dedicated auth or database tooling that already does that job well. The same reasoning applies to a formal third-party security audit — not done yet, planned once the API surface stabilizes, since auditing a moving target wastes the audit.