Login is now by email, not username. username still exists internally
(JWT sub claim, audit logs, Jira actor attribution, SSO provisioning all
still key off it) but is now always kept equal to email everywhere a user
is created or their email changes — never a separately-chosen value.
- User.email is now unique + NOT NULL (migration b067 backfills any
missing/blank email from username first, so existing rows — notably
the seeded admin, which historically had none — never violate it).
- /auth/login and the (unused but updated for consistency)
authenticate_user() now query by email.
- create_user (legacy, unreferenced but kept) and
create_user_without_password both derive username from email.
- update_user keeps username in sync when email changes, and rejects
duplicate emails.
- seed.py reads ADMIN_EMAIL (new env var, wired through install.sh and
docker-compose.prod.yml) for the initial admin; falls back to an
email-shaped ADMIN_USERNAME or a placeholder that's flagged for the
operator to fix.
- admin_config.py's import bundle now matches/creates users by email,
skipping (not crashing on) entries with no email.
- sso_service.py always sets username = email for SSO-provisioned users.
- LoginPage/auth.ts updated to email input/copy (wire field name stays
'username' — that's the OAuth2PasswordRequestForm spec, not the value).
The previous fix defaulted PLATFORM_URL to this deployment's own domain
directly in docker-compose.prod.yml — any other deployment of this repo
would silently inherit it if they forgot to set their own. Now there is
no default at all: the compose file requires the env var to be set, and
the backend refuses to start in production if PLATFORM_URL still equals
the dev value, mirroring the existing SECRET_KEY enforcement.
Set-password links in every password-setup/reset email pointed at
http://localhost:5173 in production because PLATFORM_URL was never mapped
in docker-compose.prod.yml's backend environment block — the app silently
used its dev default. Now defaults to the real domain from CORS_ORIGINS.
Critical (1-3):
- Replace hardcoded admin credentials with secure auto-generation (seed.py)
- Enforce SECRET_KEY configuration, fail in production if missing (config.py)
- Add Zip Slip and Zip Bomb protection to all ZIP import services
High/Medium (4-9):
- Add 50MB file size limit and extension whitelist to evidence uploads
- Configure CORS origins via environment variable instead of hardcoded
- Migrate JWT storage from localStorage to HttpOnly cookies (frontend+backend)
- Add rate limiting (5/min) on login endpoint via slowapi
- Replace generic dict payloads with Pydantic schemas (mass assignment)
Medium (10-17):
- Check is_active on login to prevent disabled users from authenticating
- Sanitize exception messages in API responses (system, data_sources)
- Escape LIKE wildcards in all ilike search filters across 8 routers
- Run Docker container as non-root user (appuser)
- Make MINIO_SECURE configurable via environment variable
- Add password complexity policy (12+ chars, upper/lower/digit/special)
- Implement JWT token revocation via in-memory blacklist + reduce TTL to 15min
- Replace xml.etree with defusedxml to prevent Billion Laughs attacks
Low (18-20):
- Add security headers to Nginx (CSP, X-Frame-Options, HSTS-ready, etc.)
- Disable Swagger UI/ReDoc/OpenAPI in production
- Restrict /health endpoint to internal networks via Nginx ACL
Also: rewrite install.sh as interactive wizard for guided deployment,
fix test-from-template validation error (technique_id UUID vs MITRE ID)
- Update docker-compose.yml with frontend service and healthchecks
- Add frontend Dockerfile with dev and production stages
- Add nginx.conf for production frontend serving
- Add docker-compose.prod.yml for production deployment
- Add .env.example with all configuration options
- Add init scripts (init.sh, init.ps1) for easy setup