Use the locked dependencies with uv sync --frozen --extra full. Apply uv run stk db upgrade and uv run stk protect-mfa before starting an existing deployment. Version 16 adds rate_limit_window; version 15 added quart_security_state. Both tables are required. Docker Compose runs the migrations before the app starts.
Use a unique generated SECRET_KEY and SECURITY_PASSWORD_SALT. Set STK_ENV=production, QUART_DEBUG=0, and SESSION_COOKIE_SECURE=True. Set STK_PUBLIC_URL to the HTTPS origin of the app. Set SECURITY_WAN_RP_ID to its DNS name and SECURITY_WAN_EXPECTED_ORIGIN to that HTTPS origin. The public URL restricts request hosts and fixes OAuth callback URLs. Trust proxy headers only from your proxy. The Compose app port is private and Uvicorn trusts the internal proxy network.
The production Compose stack requires DB_ADMIN_PASSWORD, DB_PASSWORD, REDIS_PASSWORD, STK_HOST, TLS_CERTIFICATE, and TLS_PRIVATE_KEY. Passwords must be unique and URL-safe because they appear in connection URLs. Certificate paths must be absolute, valid for the public host, and readable by the configured container UID. Renew certificates and reload Nginx before expiry. New PostgreSQL volumes use a separate non-superuser app role. Existing volumes need an operator privilege review because initialization scripts do not run again. Only ports 80 and 443 are published. Port 80 redirects to HTTPS. Keep container images patched and restrict access to the Docker host and network.
Public password registration is disabled by default. Enable it only for an app that needs public registration. OAuth providers remain opt-in. OAuth creates accounts only from verified provider identities; it does not attach a provider to an existing email. Existing users must use their current sign-in method. Review provider links created before version 16 because this update cannot establish how those links were originally authorized. Provider access tokens are no longer stored for login. Remove stored legacy provider tokens through your approved data maintenance process.
All POST, PUT, PATCH, and DELETE routes require the session CSRF token in a form or the X-CSRFToken header. The shared layout configures Axios. Custom API clients must fetch a rendered page and include its token. These APIs use session authentication. Do not disable CSRF for production.
Authentication limits use atomic fixed-minute SQL windows shared across workers. Defaults are 120 authentication requests and 10 authentication attempts per IP per minute, shared across auth endpoints. STK_AUTH_REQUEST_LIMIT and STK_AUTH_ATTEMPT_LIMIT are application config values. Fixed windows permit a burst at a boundary. An edge proxy should apply total connection and traffic limits. Limits rely on a trusted client IP; behind a proxy, configure forwarded-header trust explicitly. A proxy error must not make the whole site use one IP or accept client-supplied IP headers.
WebSocket defaults allow 5 connections per user and 1000 per worker, a queue of 32 messages, and 60 incoming messages per minute with a 64 KiB message limit. Sessions are checked before each outgoing message and every 5 seconds. Logout, account deactivation, expiry, and session revocation close the connection. These checks bound the revocation delay. STK_WS_* application config values can tune these limits. Connections must use the same origin, or an explicit STK_WS_ALLOWED_ORIGINS list. Broadcasts require a user ID. Audit entries are stored in the database and remain accessible through the admin API. In-memory delivery is local to one worker; use a separate authorized message transport if the app needs cross-worker delivery.
New passwords use Argon2id. Legacy hashes verify and upgrade on login. Password creation and reset routes check minimum length and the breach service. The authentication dependency permits a password if that external service is unavailable; monitoring should detect these failures. Admin password hashes are generated off the event loop. MFA, OAuth, Redis, and shared state controls have regression tests. CI also checks PostgreSQL migrations, real Redis session behavior, and runtime dependency advisories.
The 2026-10-06 review covers framework routes and local test fixtures. It is not a certification of a deployed application. Validate live OAuth provider settings, real authenticator/passkey enrollment, proxy behavior, certificate renewal, backup restoration, monitoring, and traffic capacity in your staging environment before production. A dependency scan only checks published advisories. See security-review-2026-10-06.md for the original findings and remediation evidence.
TOTP seeds and pending setup secrets are encrypted by quart-security 2.0.1. Legacy seeds remain readable and upgrade on login or authenticated requests. uv run stk protect-mfa encrypts all dormant seeds and rotates existing encrypted seeds to the current key. The command prints only a count and commits the data changes as one transaction. Run it in a maintenance window after taking a protected backup.
The default MFA encryption key is derived from SECRET_KEY for backward-compatible setup. Keep it stable. For independent key rotation, set SECURITY_TOTP_ENCRYPTION_KEYS to a comma-separated list of Fernet keys. The first key writes new data; all listed keys read existing data. Generate a key with uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())". Protect keys separately from database backups. To move from the default to an independent keyring, include the old default key from derive_totp_encryption_key(old_secret_key) in the keyring until existing seeds have been rotated and pending setup states have expired. Retain keys needed for recovery of required backups. Do not change SECRET_KEY while using its derived MFA key unless you preserve the old MFA key. Invalid ciphertext or a wrong key fails closed.