Security Architecture
A transparent overview of how MAISNER protects user data, credentials, and platform integrity. We believe security should be visible, not hidden.
Transport & Network
All traffic is encrypted via TLS 1.2 and 1.3. HTTP requests are permanently redirected to HTTPS. HSTS is enforced with a 1-year max-age, preventing protocol downgrade attacks.
LET'S ENCRYPT · AUTO-RENEWTwo-layer rate limiting: nginx blocks excessive requests at the network edge (30 req/min general, 60/min API per IP). FastAPI middleware adds per-user limits on compute-intensive endpoints.
NGINX + PYTHON LAYERAll responses include: Strict-Transport-Security, X-Frame-Options (DENY), X-Content-Type-Options (nosniff), X-XSS-Protection, Referrer-Policy, and Permissions-Policy.
OWASP RECOMMENDEDNginx sits in front of the application server, handling TLS termination, rate limiting, and request forwarding. The application port (8000) is not exposed publicly.
NGINX · UBUNTU 24.04Authentication & Access Control
User sessions are managed via signed JSON Web Tokens. Tokens are short-lived and validated server-side on every request. No session state is stored on the server.
HS256 · STATELESSPasswords are hashed using bcrypt with a cost factor of 12. Plain-text passwords are never stored or logged. Even in the event of a data breach, passwords cannot be recovered.
BCRYPT · COST 12Admin endpoints are protected server-side with role verification on every request. Admin UI elements are hidden client-side, but access is enforced at the API level (HTTP 403 for unauthorized requests).
SERVER-SIDE ENFORCEMENTEach user's portfolios are stored in isolated directories scoped to their username. API endpoints validate ownership on every read/write operation. Cross-user data access is not possible.
PATH ISOLATIONData Protection
Full platform backups run nightly at 03:00 UTC via cron. Rotation policy: 7 daily, 8 weekly, 12 monthly snapshots. Backups include all user portfolios, configurations, and application files.
DAILY · 7+8+12 ROTATIONAll significant user actions are logged with timestamps, user identity, and operation details. Logs are retained and accessible via the admin panel for audit purposes.
JSONL · APPEND-ONLYPlatform analytics are self-hosted (Umami) on the same infrastructure. No data is sent to Google, Meta, or any advertising platform. Analytics are GDPR-compliant and privacy-first.
SELF-HOSTED · GDPRMarket data is fetched from FMP Professional and Polygon APIs over HTTPS. API keys are stored as environment variables and never exposed in application code or client-side responses.
ENV VARS · NEVER CLIENT-SIDEAutomated Security Audit Results
MAISNER application code (32,819 lines across all Python modules) was scanned with Bandit v1.9.4 on 19 May 2026. Only issues in MAISNER's own code are reported — third-party library internals are excluded. Findings annotated # nosec are suppressed with documented justification inline.
debug_sector.py, stock_updater_ib.py, test_*.py) — none served in production.hashlib.md5(..., usedforsecurity=False) added to clarify intent and suppress the finding cleanly.# nosec B307 with justification. Sandboxed AST-based parser is on the roadmap.web_app.py and stress_sets.py bind to all interfaces in their __main__ block for local dev convenience. Production is started by systemd via uvicorn socket — the __main__ block is never executed in production. Port 8000 is firewalled; only nginx on port 80/443 is exposed. Annotated # nosec B104.test_constrained.py, test_ls_monitor.py, and debug_sector.py — all non-production test/debug scripts not deployed to the server. Production code in web_app.py and all engine files always passes timeout= to requests.Manual Security Review — May 2026
In addition to the automated Bandit scan, a manual code review was conducted on 19 May 2026 covering authentication, authorization, XSS, injection, rate limiting, and CSP hardening.
j.note), (2) the Tax Harvesting country note (d.note), and (3) the Admin Activity Log (user, det fields). All three fixed with the existing _esc() helper. Admin logs represented a stored XSS vector: a portfolio name containing <img onerror=...> would execute on admin's next log view.auth.py logged the plaintext default password via log.info(f"...пароль: {default_pass}..."). Logs are persisted to disk and accessible via journald. Fixed: log message now says "Admin user created. Change password via /api/auth/change-password" with no credential data.RateLimiter. This prevents both disk exhaustion from log flooding and email spam abuse.Content-Disposition filename was built with note.replace(' ','_')[:30], leaving special characters, semicolons, and quotes intact. A note like "; filename=evil.exe; could manipulate the header in some HTTP clients. Fixed: re.sub(r'[^A-Za-z0-9_-]', '_', note)[:30] applied to both GET and POST PDF export endpoints.if job_id not in jobs / job = jobs[job_id] pattern. While Python's GIL reduces practical risk, the pattern is semantically incorrect. All five instances replaced with atomic job = jobs.get(job_id) followed by a single null check.object-src 'none' added to block Flash/Java/plugin-based content vectors. Referrer-Policy tightened from strict-origin-when-cross-origin to strict-origin — the full URL path is now never sent in the Referer header to any destination. Note: unsafe-eval is retained in script-src because Three.js/globe.gl WebGL shader compilation and MathJax (methodology page) require dynamic code evaluation; removal would require major library upgrades.x-token header — not cookies. Browser same-origin policy prevents cross-origin pages from setting custom headers, making CSRF structurally impossible without a prior XSS. No CSRF tokens are required. Session cookies are not used for authentication._portfolio_pdf_inner() and the chart endpoint constructed filesystem paths from user-supplied portfolio_id without sanitisation. A value like ../other_user/file would resolve to another user's portfolio directory. Fixed: _safe_id(portfolio_id) called before path construction in both endpoints. Confirmed blocked: POST /api/portfolio/chart with portfolio_id="../other_user/file" returns HTTP 400 "Invalid identifier". Discovered and fixed: 19 May 2026.Infrastructure
Responsible Disclosure
If you discover a security vulnerability in MAISNER, please report it responsibly. Do not publicly disclose issues before we have had the opportunity to address them. Contact: maisnerplatform@gmail.com