24 - Browser Security
Overview
OpenAlgo implements browser-side security measures including session management, CSRF protection, secure cookies, and content security policies.
Architecture Diagram

Session Management
Session Lifecycle

Session Expiry Configuration
is_session_valid() in utils/session.py expires a session when the current IST time is past today's boundary and the login happened before it. app.py also registers a check_session_expiry before_request guard that skips static assets, /assets/, /api/, and the public routes.
Session Validation
Cookie Security
Secure Cookie Settings
PERMANENT_SESSION_LIFETIME is not a fixed 24 hours: handle_auth_success() sets it per login from get_session_expiry_time(), so the cookie expires at the next SESSION_EXPIRY_TIME boundary. The CSRF cookie mirrors the same flags: WTF_CSRF_COOKIE_HTTPONLY, WTF_CSRF_COOKIE_SAMESITE="Lax", WTF_CSRF_COOKIE_SECURE=USE_HTTPS, name from CSRF_COOKIE_NAME (default csrf_token, prefixed __Secure- on HTTPS). CSRF protection itself is toggled by CSRF_ENABLED (default TRUE) and CSRF_TIME_LIMIT is unlimited unless set.
Cookie Flags Explained
Secure
Only sent over HTTPS, and only enabled when HOST_SERVER is an https:// URL
HttpOnly
Cannot be read by JavaScript
SameSite=Lax
Prevents CSRF in most cases
__Secure- name prefix
Added automatically on HTTPS installs
Password Security
Argon2 Hashing
Hashing lives on the User model in database/user_db.py. The pepper is the full API_KEY_PEPPER value, not a slice of APP_KEY, and PasswordHasher() is used with its library defaults.
Password Requirements
The accepted special characters are exactly !@#$%^&*. On success the second element is None, not a message.
Login Rate Limiting
Configuration
Implementation
The limiter keys on get_remote_address, which is Werkzeug's request.remote_addr, not on the TRUST_PROXY_HEADERS-aware get_real_ip() helper used by the IP-ban and audit paths. POST /auth/login/totp and POST /auth/broker carry the same two login limits.
TOTP Two-Factor Authentication
Setup Flow

TOTP Validation
The totp_secret column holds Fernet ciphertext, so pyotp must be handed get_totp_secret() rather than the raw column. See 50-totp-configuration.md for the master switch and the three purpose flags.
Token Revocation
On Logout
The teardown is deliberately conditional. A stale cookie hitting the daily rollover must not revoke a broker token that a second device has already refreshed, so _has_fresher_session() short-circuits the global teardown.
On Session Expiry
React Frontend Security
API Key Handling
AJAX Request Detection
check_session_validity uses its own slightly wider test (it also accepts request.is_json and an application/json content type) to decide between a 401 JSON body and a redirect to the login page.
Security Headers
Recommended Headers
csp.py installs a single after_request hook that applies both the CSP header and the fixed header set.
REFERRER_POLICY and PERMISSIONS_POLICY are read from the environment but are not listed in .sample.env, so most installs run on the code defaults above.
Content Security Policy
CSP_ENABLED defaults to TRUE. When CSP_REPORT_ONLY=TRUE the policy is emitted as Content-Security-Policy-Report-Only instead. A CSP already set by the route handler is left alone, which is what lets the Remote MCP /oauth/authorize consent page widen form-action to the registered redirect origin.
Several code defaults are narrower than the values shipped in .sample.env, so an install that copies the sample gets the wider policy.
Directive
Code default (csp.py)
.sample.env value
default-src
'self'
same
script-src
'self' https://cdn.socket.io
'self' 'unsafe-inline' https://cdn.socket.io https://static.cloudflareinsights.com
style-src
'self' 'unsafe-inline'
same
img-src
'self' data: blob:
'self' data:
connect-src
'self' wss: ws:
'self' wss: ws: https://cdn.socket.io
font-src
'self'
same
object-src
'none'
same
media-src
'self'
'self' data: https://*.amazonaws.com https://*.cloudfront.net
frame-src
'self'
same
form-action
'self'
same
base-uri
'self'
same
frame-ancestors
'self'
same
child-src, report-uri, and report-to have no code default and are emitted only when the matching env var is set. upgrade-insecure-requests is added only when CSP_UPGRADE_INSECURE_REQUESTS=TRUE, which defaults to FALSE in both code and .sample.env.
CORS
cors.py applies Flask-CORS to r"/api/*" only.
Setting
Code default (cors.py)
.sample.env value
CORS_ENABLED
FALSE
TRUE
CORS_ALLOWED_ORIGINS
unset, so no origins key
http://127.0.0.1:5000
CORS_ALLOWED_METHODS
unset
GET,POST,DELETE,PUT,PATCH
CORS_ALLOWED_HEADERS
unset
Content-Type,Authorization,X-Requested-With
CORS_EXPOSED_HEADERS
unset
empty
CORS_ALLOW_CREDENTIALS
FALSE
FALSE
CORS_MAX_AGE
unset
86400
When CORS_ENABLED is not TRUE, get_cors_config() returns an empty dict and Flask-CORS falls back to its own defaults for /api/*. Every other key is applied only when the env var is present, so an unset variable means "leave it to Flask-CORS", not "deny".
Session Storage
What's Stored
The Flask session is a signed, unencrypted client-side cookie, so everything below is readable by whoever holds the cookie.
What's NOT Stored
Passwords (only Argon2 hashes in the DB)
API keys (hashed and encrypted in the DB)
The broker auth token.
handle_auth_success()carries an explicit note that it must never go in the session cookie;get_auth_token()reads the encrypted DB copy insteadRaw password-reset tokens. Only their SHA-256 hash is stored in the session
Credential Masking
Display Masking
The suffix is always eight asterisks and no trailing characters are revealed. The fixed length is intentional: it hides the secret's true length, so a screenshot cannot betray which broker a key belongs to, and it bounds the rendered column width. blueprints/broker_credentials.mask_secret mirrors this.
Key Files Reference
utils/session.py
Session validity, daily expiry, token revocation
utils/auth_utils.py
Password strength, credential masking, AJAX detection, login success handling
database/user_db.py
User model, Argon2 hashing, TOTP
blueprints/auth.py
Auth routes
csp.py
CSP and the fixed security headers
cors.py
CORS configuration for /api/*
app.py
Cookie and CSRF configuration, session-expiry guard
frontend/src/api/
Secure API calls
Last updated
Was this helpful?