# 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](flows/01-authentication-flow.md) - Detailed authentication flow diagrams and implementation
- [Network Topology](07-network-topology.md) - Network zones and firewall rules
- [Container Diagram](02-container-diagram.md) - Application security boundaries
- [Critical Configurations](08-critical-configurations.md) - SSL/TLS, secrets management
---
## Table of Contents
1. [Security Zones & Trust Boundaries](#1-security-zones--trust-boundaries)
2. [Authentication Architecture](#2-authentication-architecture)
3. [Authorization Model](#3-authorization-model)
4. [Security Controls](#4-security-controls)
5. [Threat Model & Attack Surface](#5-threat-model--attack-surface)
6. [Data Security](#6-data-security)
7. [API Security](#7-api-security)
8. [Infrastructure Security](#8-infrastructure-security)
9. [Security Monitoring](#9-security-monitoring)
10. [Incident Response](#10-incident-response)
11. [Compliance & Best Practices](#11-compliance--best-practices)
12. [Security Roadmap](#12-security-roadmap)
---
## 1. Security Zones & Trust Boundaries
### 1.1 Security Zones Diagram
```mermaid
graph TB
subgraph "Zone 0: Public Internet (UNTRUSTED)"
Internet["π Public Internet
Trust Level: NONE
Access: Anonymous users
Threat Level: HIGH
Threats:
β’ DDoS attacks
β’ SQL injection
β’ XSS attacks
β’ CSRF attacks
β’ Brute force
β’ Bot traffic"]
end
subgraph "Zone 1: Network Perimeter (SECURITY BOUNDARY)"
Fortigate["π‘οΈ FORTIGATE FIREWALL
Trust Level: BOUNDARY
Controls:
β’ NAT (85.237.177.83 β 10.22.68.250)
β’ Port filtering (443, 80, 22)
β’ Stateful inspection
β’ DDoS protection
β’ Intrusion prevention
Default Policy: DENY ALL"]
end
subgraph "Zone 2: DMZ - Reverse Proxy (SEMI-TRUSTED)"
DMZ["π₯οΈ NPM REVERSE PROXY
IP: 10.22.68.250
Trust Level: LOW
Controls:
β’ SSL/TLS termination
β’ HTTP β HTTPS redirect
β’ Let's Encrypt certificates
β’ Request filtering (block exploits)
β’ WebSocket upgrade control
β’ HSTS enforcement
Exposed Ports: 443, 80, 81 (admin)
Allowed Outbound: App Zone only"]
end
subgraph "Zone 3: Application Zone (TRUSTED)"
AppZone["π₯οΈ APPLICATION SERVER
IP: 10.22.68.249
Trust Level: MEDIUM
Controls:
β’ Flask-Login authentication
β’ CSRF protection (Flask-WTF)
β’ Rate limiting (Flask-Limiter)
β’ Input sanitization
β’ XSS prevention
β’ SQL injection prevention (SQLAlchemy ORM)
β’ Session security (secure cookies)
Exposed Ports: 5000 (internal), 22 (SSH)
Allowed Outbound: Internet (APIs), Data Zone"]
end
subgraph "Zone 4: Data Zone (HIGHLY TRUSTED)"
DataZone["ποΈ DATABASE SERVER
IP: 10.22.68.249:5432
Trust Level: HIGH
Controls:
β’ PostgreSQL authentication
β’ Localhost-only binding (127.0.0.1)
β’ Role-based access control
β’ Connection encryption (SSL/TLS)
β’ pg_hba.conf restrictions
β’ Database user separation
Exposed Ports: 5432 (localhost only)
Allowed Connections: Application Zone only"]
end
subgraph "Zone 5: External APIs (THIRD-PARTY)"
APIs["βοΈ EXTERNAL APIs
Trust Level: THIRD-PARTY
Services:
β’ Google Gemini AI
β’ Google PageSpeed Insights
β’ Google Places API
β’ Microsoft Graph API
β’ Brave Search API
β’ KRS Open API
Controls:
β’ API key authentication
β’ OAuth 2.0 (MS Graph)
β’ HTTPS/TLS 1.2+ only
β’ Rate limiting (client-side)
β’ API key rotation
β’ Cost tracking"]
end
Internet -->|"HTTPS :443
HTTP :80"| Fortigate
Fortigate -->|"NAT + Filter
Allow: 443, 80"| DMZ
DMZ -->|"HTTP :5000
(internal network)"| AppZone
AppZone -->|"PostgreSQL :5432
(localhost)"| DataZone
AppZone -->|"HTTPS
(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/`, `/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](flows/01-authentication-flow.md)
### 2.2 Authentication Security Architecture
```mermaid
graph TB
subgraph "Authentication Layer"
User["π€ User"]
Browser["π Browser"]
subgraph "Flask Authentication Stack"
FlaskLogin["Flask-Login
(Session Management)"]
CSRF["Flask-WTF
(CSRF Protection)"]
RateLimit["Flask-Limiter
(Rate Limiting)"]
Sanitize["Input Sanitization
(XSS Prevention)"]
end
subgraph "User Model & Database"
UserModel["User Model
(SQLAlchemy)"]
PostgreSQL["PostgreSQL
(users table)"]
end
subgraph "Password Security"
Hash["Password Hashing
(PBKDF2:SHA256)"]
TokenGen["Token Generation
(secrets.token_urlsafe)"]
end
subgraph "Email Verification"
EmailService["Email Service
(email_service.py)"]
MSGraph["Microsoft Graph API
(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:**
```python
# 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:**
1. **Creation:** On successful login (`login_user()`)
2. **Validation:** On every request (`@login_required`)
3. **Regeneration:** On login (prevents session fixation)
4. **Expiration:** After 7 days or on logout
5. **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_hash` column (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)
```mermaid
graph TB
subgraph "Authorization Hierarchy"
Public["π Public
Anonymous Users
Permissions:
β’ View company directory
β’ Search companies
β’ View audit reports
β’ Access registration/login"]
Auth["π Authenticated
Logged-in Users
Permissions:
β’ All Public permissions
β’ Access dashboard
β’ Use AI chat
β’ Participate in forum
β’ Send/receive messages
β’ RSVP to events
β’ Post classifieds"]
Member["π NORDA Members
is_norda_member=TRUE
Permissions:
β’ All Authenticated permissions
β’ Company profile editing (own)
β’ Request recommendations
β’ View member directory
β’ Access member-only content"]
Admin["π¨βπΌ Administrators
is_admin=TRUE
Permissions:
β’ All Member permissions
β’ Manage all companies
β’ Moderate forum/news
β’ Manage users
β’ Run audit scans
β’ View analytics
β’ Generate fees
β’ 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:**
```sql
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:**
```python
# 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/` | β
| β
| β
| β
|
| `/audit/*/` (SEO, Social, GBP, IT) | β
| β
| β
| β
|
| `/api/companies` (JSON export) | β
| β
| β
| β
|
| `/health` (Health check) | β
| β
| β
| β
|
| **Authentication** | | | | |
| `/register`, `/login` | β
| β | β | β |
| `/logout` | β | β
| β
| β
|
| `/verify-email/` | β
| β
| β
| β
|
| `/forgot-password`, `/reset-password/` | β
| β
| β
| β
|
| **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//read` (PUT) | β | β
| β
| β
|
**Legend:**
- β
Access granted
- β Access denied (redirect to login or show error)
### 3.4 Authorization Enforcement
**Decorator-based Protection:**
```python
# 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:**
```python
# 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
```mermaid
graph LR
subgraph "Input Security"
Input["User Input"]
Sanitize["Input Sanitization
(sanitize_input)"]
Validate["Input Validation
(length, format, type)"]
end
subgraph "Request Security"
CSRF["CSRF Protection
(Flask-WTF)"]
RateLimit["Rate Limiting
(Flask-Limiter)"]
Auth["Authentication
(Flask-Login)"]
end
subgraph "Response Security"
XSS["XSS Prevention
(Jinja2 auto-escape)"]
Headers["Security Headers
(HSTS, CSP, X-Frame)"]
SSL["SSL/TLS Encryption
(HTTPS)"]
end
subgraph "Data Security"
SQLInj["SQL Injection Prevention
(SQLAlchemy ORM)"]
PassHash["Password Hashing
(PBKDF2:SHA256)"]
Secrets["Secrets Management
(.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)
```python
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'', '', 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:**
```python
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:**
```python
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:**
```python
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:**
```python
# 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:**
```html
```
**AJAX Requests:**
```javascript
// 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:**
```python
# 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//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:**
```python
@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:**
```python
# β
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):**
```python
# β
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:**
```python
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$$
```
**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):**
```bash
# 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=
GOOGLE_PAGESPEED_API_KEY=
BRAVE_SEARCH_API_KEY=
# Microsoft Graph OAuth
MS_CLIENT_ID=
MS_CLIENT_SECRET=
MS_TENANT_ID=
# Email
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_USER=noreply@nordabiznes.pl
SMTP_PASSWORD=
```
**Secrets Storage:**
- **Development:** `.env` file (gitignored)
- **Production:** `.env` file on server (owned by www-data, mode 600)
- **Never committed to Git:** `.env` in `.gitignore`
**Secret Rotation:**
- β No automatic rotation (manual process)
- β No secret versioning (future improvement)
**Access Control:**
```bash
# 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/` (Company profiles)
- `/audit/*/` (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:**
```python
# 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:**
```python
# β
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:**
```html
```
**Defense:**
```python
# Input sanitization (before storage)
sanitized_post = sanitize_input(user_post) # Strips