Security
How Zefira defends itself — and how to keep it that way.
Built-in defenses
| Layer | Implementation |
|---|---|
| Passwords | scrypt hashing, 10+ chars with letters + digits enforced on change |
| Sessions | JWT in HttpOnly + SameSite=Strict cookies, Secure behind HTTPS, versioned (logout / password change kills all sessions) |
| 2FA | TOTP (SHA1/6-digit/30s), secrets Fernet-encrypted at rest |
| Brute force | 8 tries / 15 min per IP+user, plus a loose global per-user bucket (lockout-DoS resistant); identical 401s + dummy-hash timing for unknown users |
| CSRF | Mutating /api calls require X-Requested-With: XMLHttpRequest |
| Headers | CSP, frame-ancestors 'none', X-Frame-Options DENY, nosniff, no-referrer, CORP same-origin, HSTS on HTTPS, no Server banner |
| Input | Strict Pydantic schemas everywhere, 1 MiB body cap (64 MiB only for restore), LIKE-escape on search |
| Secrets | Bot tokens, tunnel tokens, TOTP and REALITY private keys encrypted with a host-local master key |
| Visibility | Audit log (last 2000 events), Telegram brute-force alerts (spam-throttled) |
Penetration-test suite
security_test.py attacks a live panel: auth boundary, JWT forgery (alg=none, tampering, bad signature), rate limits, CSRF, mass assignment, backup/restore atomicity, fuzzing, XSS sources, header checks — currently 76/76 passing:
.venv\Scripts\python security_test.py http://127.0.0.1:8000 admin YOUR_PASSWORD
Restore/atomicity tests wipe users. The suite refuses to run against a panel with users unless you pass
--allow-live — back up first.Operator checklist
- Change the password + enable 2FA right after first login.
- Serve the panel over HTTPS (installer step 7 or Settings → SSL).
- Keep
/opt/zefira/instance/secret.keybacked up offline — without it, encrypted data is unrecoverable. - Behind a proxy, set
trusted_proxiesso rate limits and audit IPs see real clients. - Never share backup JSON files — they hold password hashes and secrets.
ZEF