Security & privacy
This portal holds the IMAP credentials and live mail of every connected mailbox, so security is treated as a first-class concern, not an afterthought. Here's what's actually in place.
Your credentials
- Portal password is hashed, never stored in plaintext. Django's default password hasher writes a salted PBKDF2-SHA256 digest (hundreds of thousands of iterations per password) to the database. At login we re-hash what you typed and compare against the stored digest; the original password is never written anywhere on the server.
- Password strength is enforced at signup. Django's standard validators reject too-short passwords, all-numeric passwords, passwords too similar to your email or username, and passwords on the well-known common-password list.
-
IMAP passwords are encrypted at rest. Stored as
Fernet ciphertext
(AES-128-CBC + HMAC-SHA256) using a
FIELD_ENCRYPTION_KEYthat is separate from Django'sSECRET_KEY. The two keys can be rotated independently. Passwords are never written to logs. - Message bodies are never persisted. The Inbox fetches them live from IMAP on every request; the server keeps no cache. Close the tab and the messages stay only on your Mail.ru side.
Hardening on every request
-
HTTPS everywhere in production.
SECURE_SSL_REDIRECTforces HTTPS, and HSTS is preloaded withincludeSubDomainsso browsers refuse to fall back to plain HTTP. - Secure, HttpOnly, SameSite=Lax cookies. Session and CSRF cookies can't be read by JavaScript or sent on cross-site requests in normal flows.
-
No clickjacking.
X-Frame-Options: DENYblocks every attempt to embed the portal in a frame, on every page. -
No MIME sniffing, minimal referrer leakage.
X-Content-Type-Options: nosniffandReferrer-Policy: same-originare set explicitly. -
Cross-origin isolation.
Cross-Origin-Opener-Policy: same-originkeeps the portal in its own browsing-context group. - No HTML caching. A custom no-cache middleware tags every HTML response so logged-in pages don't sit in proxy or browser caches after you log out.
- Rate limiting on auth endpoints. Login, signup, OTP entry, OTP resend, and password reset all have per-user / per-IP budgets backed by Django's cache; abusive bursts get blocked.
What we don't collect
- No analytics, no third-party trackers, no ad pixels — the portal and this site are both clean of all of it.
- No message body persistence. The portal reads IMAP and renders. Nothing is stored server-side.
- No marketing emails. The only mail you'll ever get from the portal is transactional: signup verification, OTP codes, and password reset links.
- No "telemetry" beep-back to the dev. Production logs are stdout-only, scoped to errors.
Threat model
Being honest about scope: this is a portfolio project. The security story above is real and the controls are in place, but the threat model is deliberately bounded.
- In scope: standard web-app threats — credential stuffing, brute-force, session hijack, CSRF, clickjacking, plaintext credential disclosure, MITM, basic abuse / rate-limit bypass.
- Out of scope: nation-state attackers, advanced persistent threats, supply-chain compromise of pinned upstream packages, side-channel attacks on the hosting provider.
- Recommendation: use the portal for personal mail and side projects. Don't connect mailboxes that hold high-value secrets (corporate, financial, legal). If you need that, fork the repo, audit it yourself, and self-host.
Self-hoster note
If you're running your own copy:
-
Keep
FIELD_ENCRYPTION_KEYstable. Rotating it invalidates every stored IMAP password. There's no re-wrap migration shipped with the app; you'd need to re-enter passwords after a rotation. -
Don't reuse the example values in
.env.exampleor CI. The repo's CI workflow uses known test-only values explicitly labelled as such. - Verify your Resend sender domain. Transactional emails won't deliver reliably from an unverified domain, and the portal won't fall back to a different sender.
-
Pin your dependencies.
requirements.txtuses minimum-bound versions. For production, generate a fully pinned lockfile (pip-compileor similar).
Reporting a security issue
If you find a vulnerability, please don't open a public GitHub issue. Instead, email the maintainer privately via the contact address listed on the project's GitHub profile, or use GitHub's private vulnerability reporting. Public disclosure can wait until the fix is shipped.