Security#
Hardening, CSRF, rate limiting, trusted proxies, and the error-disclosure standard every request path in Hayavega is held to.
Secret key#
import os
class Settings:
secret_key = os.getenv("HAYAVEGA_SECRET_KEY", "change-this-in-production")
Generation. Hayavega does not auto-generate one for you at hayavega
newproject time (unlike Django's startproject). The scaffolded
secret_key reads HAYAVEGA_SECRET_KEY from the environment, falling back
to the placeholder string "change-this-in-production" if it's unset.
Generate a real one yourself and set it as an environment variable, never
committed to source control:
Validation. Two places reject the placeholder:
hayavega diagnose(below) warns (or errors, oncedebug = False) ifsecret_keyis empty or a known placeholder ("change-this-in-production","changeme","secret").hayavega launch/production validation refuses to start a non-debug server with the same placeholder values.
What it's used for -- and what it isn't. Unlike Django, where
SECRET_KEY signs session cookies, CSRF tokens, and password-reset
tokens, Hayavega generates independent randomness via secrets for each of
those (auth.create_session, auth.create_password_reset_token,
auth.create_email_verification_token) -- secret_key plays no part in
any of them. The one place it's actually load-bearing is TOTP secret
encryption at rest:
from hayavega.auth import enable_totp, authenticate
secret = enable_totp(DATABASE, "alice", secret_key=SETTINGS.secret_key)
authenticate(DATABASE, "alice", password, totp_code="123456", secret_key=SETTINGS.secret_key)
enable_totp derives a 256-bit AES key from secret_key via HKDF-SHA256
and encrypts the generated TOTP secret with AES-256-GCM
(hayavega.crypto) before storing it, using a fresh random nonce each
time. This needs cryptography installed (uv add cryptography --
kept optional, the same policy as the PostgreSQL/MySQL drivers, rather
than a mandatory dependency for every project that never enables TOTP).
Without it, or without secret_key, the secret is stored in plaintext
instead -- hayavega diagnose reports that fallback as an info-level
finding.
manage.py enable2fa already passes Settings.secret_key for you; code
that calls enable_totp/authenticate/auth_routes directly needs to
pass it explicitly. If a user's secret was stored encrypted, every later
authenticate() call for them needs the same secret_key, or the TOTP
check fails closed -- treated as a wrong code, never silently
skipped -- rather than accepting any code when decryption can't happen.
See Authentication for the full
TOTP walkthrough.
Rotating it. Changing secret_key invalidates every TOTP secret
encrypted under the old one -- affected users need enable2fa run again.
It has no effect on sessions, CSRF tokens, or password-reset tokens,
since none of those are derived from it.
The security section#
class security:
allowed_origins = ["https://myapp.example.com"]
rate_limit = 120 # requests per window, per client
rate_window_seconds = 60
security_headers = True # X-Frame-Options, X-Content-Type-Options, etc.
force_https = True # required in production
trust_proxy_headers = True # only behind a real, trusted reverse proxy
trusted_proxy_ips = ["10.0.0.0/8"]
max_request_bytes = 1_048_576
HardeningOptions.from_settings(allowed_hosts=..., security=...) builds
the full set consumed by both hayavega.runtime.create_application and
hayavega.asgi.create_asgi_application — identical enforcement on
either transport.
manage.py diagnose#
Proactively reports unsafe settings (Settings.debug left on, a placeholder
Settings.secret_key, a wildcard or empty Settings.allowed_hosts, force_https/
secure_cookies off), deprecated Pulse-era API usage, and pending
migrations — before you discover them at runtime or in production.
Severity scales with whether Settings.debug is on.
Production validation#
Fails with actionable errors for unsafe configuration — a missing
Settings.secret_key, Settings.debug = True in what looks like a production run,
Settings.security.force_https not set. See validate_production() in
hayavega.production.
CSRF#
Accepts either an X-Csrf-Token header or a csrf_token form field —
covers both JSON/API clients and traditional server-rendered <form>
submissions with one decorator.
Rate limiting#
Bounded-memory, per-client rate limiting with a documented Redis/edge
replacement path for multi-instance deployments — see
RATE_LIMIT/RATE_WINDOW_SECONDS/RATE_LIMIT_MAX_CLIENTS above.
The error-disclosure standard#
An unhandled exception on any request path — REST, GraphQL, template
rendering, routing — never leaks its raw message to an anonymous
client. Only error_type (e.g. "RuntimeError") is exposed in the
response; the full exception with traceback is always logged
server-side. This closed a real, found gap: a database connection
failure's message routinely embeds host, port, and username, which would
otherwise go straight to any client that triggers one.
Debug-mode 404s#
create_application(..., debug=True) lists every registered route
pattern on a 404 — computed once at application-build time (free
when a route matches, free in production where debug=False is the
default), the same trade-off many frameworks' debug-mode 404 pages make.
Never exposes anything beyond your own app's route table.
Audit log#
Authentication and admin actions are recorded to a persistent audit log
(hayavega_audit_events) — field names that changed, never the values,
for every recorded update. See Authentication
and Admin.
Security review checklist#
A living checklist covering sessions, CSRF, CORS, proxy trust, headers,
and rate limits, each item linked to a real test or a documented
operational control — see SECURITY_REVIEW_CHECKLIST.md in the
repository.