Eviworx
Docs

Security Architecture

The Security Architecture is based on defense-in-depth with 8 security layers: Transport Security (TLS 1.2+ via Traefik), Application Security (RBAC, JWT), Data Security (SHA-256 Audit Chain, AES-256 Encryption), Container Security (Zero-Trust, Least Privilege), File Security (ClamAV Virus Scan), API Security (SSRF Protection, Rate Limiting), Authentication (SSO, native MFA/TOTP, FIPS 140-2 compatible) and Audit & Compliance Support (ISO 27001, GDPR).

🔒
Features
✓ FIPS 140-2 compatible (no certification)
✓ Native MFA/TOTP (HMAC-SHA256)
✓ Entra ID SSO with MFA (group → role)
✓ Traefik gateway (TLS 1.2/1.3, HSTS, CSP)
✓ Rate limits at gateway and application level
✓ RBAC (28+ modules, 350+ permissions)
✓ Audit chain with tamper detection (SHA-256)
✓ Zero-trust virus scan (ClamAV)
✓ Stored secrets encrypted (AES-256-GCM)
✓ SSRF protection (webhooks, workflow actions)

Security Layers Overview

┌────────────────────────────────────────────────────────────────────────┐
│                DEFENSE-IN-DEPTH (8 LAYERS)                        │
└────────────────────────────────────────────────────────────────────────┘

Layer 1: TRANSPORT SECURITY (Traefik API Gateway)
┌──────────────────────────────────────────────────────────────────────┐
│ • TLS 1.2/1.3 only (no SSLv3, TLS 1.0, TLS 1.1)                     │
│ • Strong Ciphers: ECDHE-RSA-AES-GCM, ECDHE-ECDSA, ChaCha20-Poly1305 │
│ • HSTS: max-age=31536000; includeSubDomains; preload                 │
│ • HTTP → HTTPS Redirect (Traefik entrypoint redirect)                │
│ • Rate-Limiting: 100 req/s, burst 200 (Traefik middleware)           │
│ • Internal service interfaces blocked externally (priority routing)  │
│ • Cloudflare Turnstile CAPTCHA (Bot Protection)                      │
└──────────────────────────────────────────────────────────────────────┘

Layer 2: APPLICATION SECURITY
┌──────────────────────────────────────────────────────────────────────┐
│ • JWT Authentication (HttpOnly Cookies)                              │
│ • RBAC: 28+ Permission-Module, 350+ Individual Permissions           │
│ • Permission cache per role, writes always fresh from DB             │
│ • Permissions loaded per request, not from token                     │
│ • CSP Headers via Traefik: default-src 'self', script-src 'self'     │
│ • X-Frame-Options: SAMEORIGIN                                        │
│ • X-Content-Type-Options: nosniff                                    │
│ • X-XSS-Protection: 1; mode=block                                    │
│ • Referrer-Policy: strict-origin-when-cross-origin                   │
│ • Permissions-Policy: geolocation=(self), microphone=(), camera=()   │
│ • CSRF Protection (built-in)                                         │
└──────────────────────────────────────────────────────────────────────┘

Layer 3: DATA SECURITY
┌──────────────────────────────────────────────────────────────────────┐
│ • SHA-256 Audit-Chain (immutable, tamper-evident)                    │
│ • PII-Scrubbing (Passwords, Tokens auto-redacted)                    │
│ • AES-256-GCM Encryption (License-Keys, TOTP, alle Credentials)     │
│ • Optimistic Locking (version field prevents concurrent mods)        │
│ • Soft-Delete (Recovery-Window)                                      │
└──────────────────────────────────────────────────────────────────────┘

Layer 4: CONTAINER SECURITY
┌──────────────────────────────────────────────────────────────────────┐
│ • Read-only Filesystem (av-worker)                                   │
│ • Capability-Drop ALL (av-worker)                                    │
│ • Capability-Drop 3 (ClamAV: NET_RAW, SYS_ADMIN, MKNOD)             │
│ • Non-root Users (all workers: UID 1001)                             │
│ • no-new-privileges (Traefik, ClamAV, av-worker)                     │
│ • tmpfs for temporary storage (in-memory, volatile)                  │
│ • Resource-Limits (ClamAV: 2GB, av-worker: 1.5GB)                    │
│ • Redis Password Authentication (--requirepass)                      │
└──────────────────────────────────────────────────────────────────────┘

Layer 5: FILE SECURITY
┌──────────────────────────────────────────────────────────────────────┐
│ • Zero-Trust Virus-Scan (ClamAV, av-worker has NO file access)       │
│ • Scan-Status-Tracking: PENDING → SCANNING → CLEAN/INFECTED          │
│ • Quarantine-Volume (infected files isolated)                        │
│ • Extension-Blacklist (.exe, .bat, .sh, .dll, .js, .vbs)            │
│ • MIME-Type Validation (server-side)                                 │
│ • File-Size-Limits (Global: 100MB, per-Entity configurable)          │
│ • Max-Files-Limit (per entity)                                       │
│ • Retention-Policy (Auto-Delete after X days)                        │
│ • Orphan-Cleanup (unused files after 24h)                            │
└──────────────────────────────────────────────────────────────────────┘

Layer 6: API SECURITY
┌──────────────────────────────────────────────────────────────────────┐
│ • SSRF Protection (Webhooks, Workflow-Actions, Teams-URLs)           │
│ • HMAC Signature (webhooks with shared secret)                       │
│ • Rate-Limiting (all values ENV-configurable):                                  │
│   - Traefik Gateway: 100 req/s, burst 200                            │
│   - Global /api/*: 2000/min/IP · Critical-Ops: 60/min (writes only)  │
│   - Login: 5 Fails/15min je (IP+Konto) + 30/15min/IP Backstop        │
│   - File Uploads: 200/hour/IP (FILE_UPLOAD_RATE_LIMIT)               │
│   - Email Inbound: 60/min global, 30/hour/sender                     │
│ • Input Validation (Zod schemas)                                     │
│ • SQL Injection Protection (Prisma ORM)                              │
│ • XSS Protection (DOMPurify client-side)                             │
│ • API-Keys (INTERNAL_API_KEY for worker auth)                        │
└──────────────────────────────────────────────────────────────────────┘

Layer 7: AUTHENTICATION & AUTHORIZATION
┌──────────────────────────────────────────────────────────────────────┐
│ • JWT Tokens (HttpOnly Cookies)                                      │
│ • Password Hashing (PBKDF2-SHA512, 210k iterations — FIPS 140-2)     │
│ • Multi-Factor Authentication (native TOTP + Entra ID MFA)           │
│ • Entra ID SSO (Azure AD, OAuth 2.0)                                 │
│ • API-Keys (for external integrations)                               │
│ • Token Refresh (refresh_token flow)                                 │
│ • Session Management (Redis-backed, configurable expiry)             │
│ • Cloudflare Turnstile CAPTCHA (Bot Protection)                      │
└──────────────────────────────────────────────────────────────────────┘

Layer 8: COMPLIANCE & AUDIT
┌──────────────────────────────────────────────────────────────────────┐
│ • SHA-256 Hash-Chain (tamper-evident, immutable)                     │
│ • Immutability (DB trigger prevents UPDATE/DELETE)                   │
│ • 9 Audit-Domains (AUTH, ENTITY, ADMIN, SECURITY, SLA, etc.)         │
│ • Chain-Verification-API (Quick-Check & Full-Verification)           │
│ • FIPS 140-2 compatible algorithms (Standard + FIPS Mode)            │
│ • Supports ISO 27001, SOC 2 and GDPR audits                          │
│ • Retention-Policies (7+ years for Audit-Logs)                       │
└──────────────────────────────────────────────────────────────────────┘

1. Transport Security (Traefik API Gateway)

TLS/SSL Configuration

Transport security is provided by Traefik v3 as the central API gateway. Traefik terminates TLS, sets security headers, and enforces rate limits before requests reach the backend.

Feature Configuration Details
TLS Versions TLS 1.2, TLS 1.3 Blocked: SSLv3, TLS 1.0, TLS 1.1 (Traefik minVersion: VersionTLS12)
Cipher Suites ECDHE-RSA/ECDSA-AES-GCM, ChaCha20-Poly1305 Perfect forward secrecy, 6 explicit cipher suites
HSTS max-age=31536000; includeSubDomains; preload 1 year, all subdomains, preload-ready
HTTP Redirect 301 Permanent Redirect Enforces HTTPS (Traefik entrypoint redirect)
Rate-Limiting 100 req/s, burst 200 DDoS protection at gateway level (Traefik middleware)
Internal interface block Priority-Router → noop@internal Internal interfaces between the services are not reachable from outside (blocked by Traefik)

External Reverse Proxy: When running behind an external reverse proxy (e.g. Nginx, HAProxy), TRUSTED_PROXIES (.env) and forwardedHeaders.trustedIPs (traefik/traefik.yml) must be configured so client IPs are logged correctly and rate limiting applies to the real client IP. Installation → External Reverse Proxy

📘 Details: See Container Architecture → (Traefik Container)

Traefik Routing & Priorities

Router Rule Priority Middlewares
block-internal-apiPathPrefix(/api/internal)40security-headers
backend-apiPathPrefix(/api)30security-headers, compress, rate-limit
backend-wsPathPrefix(/socket.io)20security-headers
frontend-spaPathPrefix(/)10security-headers, compress, rate-limit

2. Application Security

Role-Based Access Control (RBAC)

  • 28+ Permission Modules: tickets, problems, changes, incidents, assets, contracts, licenses, workflows, cronjobs, users, settings, audit, etc.
  • 200+ Individual Permissions: viewAll, viewOwn, create, editStatus, assign, approve, delete
  • Permission Cache: per role, 5 min for read requests; modifying requests and critical actions always read permissions fresh from the database
  • No Permissions in the Token: permissions are loaded from the role on every request; an older token grants no permissions that have since been removed
  • 4 System Roles: END_USER, AGENT, ADMIN, APPROVER (cannot be deleted or deactivated)
  • Unified Actor: User (cookie) and API key (X-API-Key with role) go through the same permission check against their role's matrix

📘 Details: See Permissions & RBAC →, User Management → and Users & Roles API →

Security Headers (via Traefik)

All security headers are set centrally through the Traefik security-headers middleware:

Header Value Purpose
Content-Security-Policy default-src 'self'; script-src 'self' https://challenges.cloudflare.com XSS protection (Turnstile CAPTCHA allowed)
X-Frame-Options SAMEORIGIN Clickjacking protection
X-Content-Type-Options nosniff Prevent MIME sniffing
X-XSS-Protection 1; mode=block Enable XSS filter
Referrer-Policy strict-origin-when-cross-origin Prevent referrer leak
Permissions-Policy geolocation=(self), microphone=(), camera=(self) Restrict browser permissions
Strict-Transport-Security max-age=31536000; includeSubDomains; preload Enforce HTTPS (1 year)

3. Data Security

SHA-256 Audit Chain

  • Immutable Logs: UPDATE/DELETE forbidden (DB trigger)
  • Hash Chain: Each event links to previous via SHA-256
  • Chain Verification: API endpoint validates entire chain integrity
  • PII Scrubbing: Automatically redacts passwords, tokens, email, phone
  • Multi-Org Isolation: Separate chains per organization
  • Redis Fallback: On DB failure → Redis backup (7 days TTL)

📘 Details: See Enterprise Audit System →

Encryption at Rest

Data Type Encryption Key Management
License keys AES-256-GCM LICENSE_ENCRYPTION_KEY (env)
Passwords PBKDF2-SHA512 (210k iterations) One-way hash (FIPS 140-2)
TOTP secrets AES-256-GCM TWO_FACTOR_ENCRYPTION_KEY (env)
Mailbox credentials (SMTP/IMAP) AES-256-GCM MAILBOX_ENCRYPTION_KEY (env)
Global credentials: SMTP password, MS-Graph & Entra ID clientSecret, notification adapters (Webex botToken, Teams appPassword) AES-256-GCM MAILBOX_ENCRYPTION_KEY (env)
JWT Tokens HMAC-SHA256 (HS256) JWT_SECRET (env)

All global credentials are encrypted with AES-256-GCM on save (the same key as the mailbox credentials: MAILBOX_ENCRYPTION_KEY, falling back to LICENSE_ENCRYPTION_KEY) and decrypted only when used. If a stored value cannot be decrypted, the function stops with an error; the credentials must then be re-entered.

📘 Details: See Contracts & Licenses API → (License Key Encryption)

Optimistic Locking

  • Incidents: Version field prevents concurrent modifications
  • Problems: Version field for conflict detection
  • Assets: Version field on updates
  • Contracts: Version field on financial updates
  • Conflict Response: 409 CONFLICT with current entity state

4. Container Security

Zero-Trust Architecture (av-worker)

# av-worker: MAXIMUM HARDENING
read_only: true                    # Filesystem fully read-only
tmpfs:
  - /app/tmp:size=64M,mode=1777    # In-memory temporary storage
  - /tmp:size=64M,mode=1777
security_opt:
  - no-new-privileges:true         # Prevents privilege escalation
cap_drop:
  - ALL                             # All capabilities dropped!

# AV-Worker has NO access to the uploads volume
# Communicates only via:
# 1. ClamAV TCP API (Port 3310)
# 2. Backend HTTP API (Status-Updates)

🔒 Zero-Trust: The av-worker is the most hardened container. See Container Architecture → and Attachments API →

Traefik Container Security

# Traefik: API gateway hardening
security_opt:
  - no-new-privileges:true         # Prevents privilege escalation
volumes:
  - ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro    # Read-only config
  - ./traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro    # Read-only routing
  - ./certs/cert.pem:/etc/traefik/ssl/cert.pem:ro     # Read-only cert
  - ./certs/cert.key:/etc/traefik/ssl/cert.key:ro     # Read-only key

Redis Password Authentication

Redis is password-protected. All services connect with an authenticated URL:

# Redis: password-protected
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
# All services connect via:
# redis://:${REDIS_PASSWORD}@redis:6379

Least Privilege Database Access

DB User Permissions Used By
helpdesk_user Full access (all tables) Backend (Migrations)
helpdesk_jobworker Restricted: CronJob, JobExecution, WorkerInstance Job-Worker
helpdesk_readonly SELECT-only (all tables) Analytics, reporting

📘 Details: See Container Architecture → (PostgreSQL Container)

Container Hardening Summary

Container Read-Only Non-Root Cap-Drop no-new-priv Resources Hardening Level
AV-Worker✅ ALL1.5G / 1C⭐⭐⭐⭐⭐
Email-Worker✅ ALL512M / 1C⭐⭐⭐⭐⭐
Job-Worker✅ ALL1G / 1C⭐⭐⭐⭐⭐
Notification-Worker✅ ALL512M / 1C⭐⭐⭐⭐⭐
Workflow-Engine✅ ALL512M / 1C⭐⭐⭐⭐⭐
Report-Generator✅ ALL1.5G / 1C⭐⭐⭐⭐⭐
Frontend✅ 3 caps256M / 0.5C⭐⭐⭐⭐
TraefikPartial (ro)256M / 1C⭐⭐⭐⭐
ClamAV✅ 3 caps2G / 2C⭐⭐⭐⭐
Backend✅ 3 caps2G / 2C⭐⭐⭐
PostgreSQL✅ 2 caps2G / 2C⭐⭐⭐
Redis✅ 2 caps1G / 1C⭐⭐⭐

Cap-Drop "ALL" = all Linux capabilities removed. "3 caps" = NET_RAW, SYS_ADMIN, MKNOD. "2 caps" = NET_RAW, SYS_ADMIN. Resources = memory limit / CPU limit. All containers have JSON logging with rotation (10MB x 3).

5. File Security

Zero-Trust Virus Scan Flow

ZERO-TRUST VIRUS SCAN (7 STEPS):

1. User uploads file
   POST /api/attachments/TICKET/:id
   ↓
2. Backend receives file
   • Stores in uploads/ volume
   • Creates Attachment record (scanStatus: PENDING)
   • NO direct ClamAV scan (zero-trust!)
   ↓
3. AV-Worker polls (every 10 seconds, SCAN_POLL_CRON)
   • Backend returns list of PENDING attachments
   ↓
4. AV-Worker initiates scan
   • TCP 3310 to ClamAV
   • Sends file PATH (not content!)
   • ClamAV reads from uploads/ volume (read-only)
   ↓
5. ClamAV scans file
   • Returns: CLEAN / INFECTED / ERROR
   ↓
6. AV-Worker updates Backend
   • Reports scan result (e.g. CLEAN)
   ↓
7. Backend updates Attachment
   • scanStatus: PENDING → CLEAN
   • If INFECTED:
     - Move to quarantine/ volume
     - Notify admin
     - Block download

SECURITY FEATURES:
✅ av-worker has NO file access (read-only filesystem, no uploads mount)
✅ ClamAV has a read-only mount on uploads/
✅ Backend has read-write, but no scan access
✅ Quarantine volume isolates infected files

📘 Details: See Attachments API → (Zero-Trust Virus Scan Section)

File Upload Security

  • Extension Blacklist: .exe, .bat, .sh, .dll, .js, .vbs, .msi, .com, .cmd, .scr blockiert
  • MIME-Type Validation: Server-side (not just extension)
  • File Size Limits: Global 100MB, per-entity configurable
  • Upload Rate Limit: FILE_UPLOAD_RATE_LIMIT: 200/hour/IP (configurable)
  • Max Files Limit: Per entity (e.g., max 10 attachments per ticket)
  • Retention Policy: Auto-delete after X days (configurable)
  • Orphan Cleanup: Delete unused files after 24h
  • Soft-Delete: Recovery window (files remain X days after delete)

6. API Security

SSRF Protection

Webhook URLs and external API calls are validated to prevent SSRF attacks. The same check applies on save and at execution (job worker, workflow engine). Invalid allowlist entries are ignored and unlock nothing.

  • Allowed schemes: HTTP and HTTPS (no HTTPS-only enforcement), restricted to allowed ports — default 80, 443, 8080, 8443, configurable via SSRF_ALLOWED_PORTS
  • Hard-blocked (NEVER allowlistable): localhost, 127.0.0.1, ::1, 0.0.0.0, cloud metadata (169.254.169.254, metadata.google.internal, metadata.azure.com), link-local (fe80::), multicast (ff00::)
  • Blocked by default, allowlistable via SSRF_ALLOWLIST : private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, IPv6 ULA fc00::/fd00::) — e.g. for internal ERP/monitoring webhooks (IP/CIDR/hostname, no ports)
  • Teams URLs: Only Microsoft domains allowed (outlook.office.com, outlook.office365.com, webhook.office.com)

📘 Details: See Workflows API →, CronJobs API →, Integrations →

Rate Limiting

Endpoint/Feature Limit Time Window Type
Traefik Gateway100 req/s, burst 200Per secondGlobal (DDoS protection)
File uploads200Per hourPer IP
Email inbound (global)60Per minuteGlobal
Email inbound (sender)30Per hourPer Sender
Email max size25 MB-Per email

Implementation: Traefik rate limit (gateway level) plus limits in the backend (application level)

📘 Details: See Integrations → (Email Rate Limiting)

Input Validation

  • Zod Schemas: All API inputs validated (type-safe)
  • SQL Injection: Prisma ORM prevents SQL injection (prepared statements)
  • XSS Protection: DOMPurify client-side, CSP headers server-side (Traefik)
  • Path Traversal: File paths validated, no ../ allowed

7. Authentication & Authorization

Authentication Methods

Method Description Use Case
JWT (HttpOnly Cookies) Standard web login Portal users (agents, customers)
Entra ID SSO Azure AD OAuth 2.0 Enterprise single sign-on
Native TOTP MFA HMAC-SHA256 TOTP (FIPS-compatible) All users (security settings)
Cloudflare Turnstile CAPTCHA bot protection Login/registration
API-Keys X-API-Key header for external systems Workflow API trigger, integrations
INTERNAL_API_KEY Shared key the workers use to authenticate to the backend Inter-container communication

📘 Details: See Authentication API →

FIPS 140-2 Compatible Cryptography

The backend exclusively uses FIPS 140-2 compatible algorithms. Note: This refers to the use of FIPS-compatible algorithms, not an official FIPS certification. There are two operating modes:

Component Algorithm Details
Password hashing PBKDF2-SHA512 210,000 iterations, 64-byte key, 32-byte salt
2FA (TOTP) HMAC-SHA256 Not SHA-1 (FIPS-compatible)
Encryption AES-256-GCM License keys, TOTP secrets and stored credentials
JWT Signing HS256 HMAC-SHA256

Operating Modes

Mode FIPS-compatible algorithms FIPS mode enforced Docker Image
Standard node:24-slim
Strict FIPS Distroless FIPS base image (Node.js 24, OpenSSL 3.0 FIPS Provider)

In Strict FIPS mode, Node.js runs with --enable-fips and OpenSSL 3.0 FIPS Provider. MD5, MD4, RIPEMD160 are blocked. The startup check (ENABLE_FIPS=true) stops the server immediately if crypto.getFips() !== 1.

FIPS Configuration

Variable Default Description
DOCKERFILE_BACKENDDockerfileDockerfile.fips for Strict FIPS mode
ENABLE_FIPSfalseStartup check: server stops if FIPS not active
PBKDF2_ITERATIONS210000PBKDF2 iterations (range: 100k–2M)
UV_THREADPOOL_SIZE16libuv thread pool for async PBKDF2

Password Hashing Security Mechanisms

  • Verify Whitelisting: Only sha512, keylen 64, iterations 100k–2M accepted
  • Timing-Safe: crypto.timingSafeEqual for password comparisons
  • Constant Response Time: a hash is computed for unknown accounts too, so the response time does not reveal whether an account exists
  • Automatic Rehash: hashes with a lower iteration count than configured are recomputed on the next successful login
  • Hash Format: pbkdf2:sha512:<iterations>:<keylen>:<salt_base64>:<hash_base64>
  • Base64 Validation: Strict validation of salt and hash buffers

Native MFA/TOTP

In addition to Entra ID MFA, the system provides native TOTP-based multi-factor authentication:

  • Algorithm: HMAC-SHA256 (FIPS-compatible, not SHA-1)
  • Secret Encryption: AES-256-GCM via TWO_FACTOR_ENCRYPTION_KEY
  • Activation: User security settings (self-service)
  • Key Separation: TWO_FACTOR_ENCRYPTION_KEY is separate from JWT_SECRET

Cloudflare Turnstile CAPTCHA

  • Protection: Bot protection for login and registration
  • CSP Integration: challenges.cloudflare.com allowed in script-src, connect-src, frame-src
  • Configuration: TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY (env)

JWT Token Security

  • HttpOnly Cookies: JavaScript cannot read token (XSS protection)
  • Secure Flag: Cookie only sent over HTTPS
  • SameSite: CSRF protection
  • Token Rotation: JWT_SECRET_OLD supports gradual key rotation
  • No Permissions in the Token: permissions are loaded from the role on every request (prevents privilege escalation via stale tokens)
  • Expiration: Tokens expire (configurable)

Session Security

Variable Default Description
SESSION_MAX_HOURS12Hard session end in hours (backend-enforced, not extendable via refresh)
ACCESS_TOKEN_EXPIRY_MINUTES60Access token expiry in minutes
REFRESH_TOKEN_EXPIRY_MINUTES100Refresh token expiry in minutes (deliberately tight — see rotation below)
IDLE_TIMEOUT_MINUTES30Auto-logout on inactivity (server value, enforced in the client)
COOKIE_SECURE(NODE_ENV)Explicitly force/disable the secure flag of the auth cookies
JWT_SECRET_OLD-Previous JWT secret for gradual key rotation

Refresh token rotation & theft detection

  • Rotation on every refresh: A refresh token is a one-time ticket — it is replaced once redeemed.
  • Grace window (60 s): If the response is lost (timeout/abort), the old token may briefly be redeemed again — the session does not strand because of a network glitch.
  • Reuse detection (10 min): If a long-replaced token reappears afterwards, it counts as stolen: the entire session is terminated immediately, WebSockets disconnected, audit entry REFRESH_TOKEN_REUSE.
  • Immediate revoke: Session termination (sessions UI, password change, reuse) takes effect immediately via the stable session ID — regardless of how often the token has rotated since.
  • Denied when in doubt: If Redis is unreachable for the blacklist/revoke check, access is denied (503) rather than granted in doubt.
  • Terminated stays terminated: Once a session has been terminated, no path ever issues tokens for it again — not even the rotation grace window.

Brute force protection & rate limiting

Protection sits on the TARGET object, not on the IP. This is the key design decision in this area — because behind a corporate NAT all employees share a single IP. An IP limit tight enough to stop an attacker would lock out your own office first.

Layer Protection
1. On the object (the real brake) Per-email account lockout with exponential backoff (5 failures → up to 4 h), CAPTCHA from the 3rd failure, per-user 2FA attempt limit, password reset 1 request / 2 min per email. These limits apply regardless of how many IPs an attacker uses.
2. On the IP (flood backstop) Login counts per combination of IP AND account (5/15 min) — a colleague with a typo locks only themselves out; plus an IP cap across all accounts (30/15 min) against enumerating many addresses. Refresh (60), logout (120), 2FA/reset (20), invitation lookup (30) and the global cap (2000/min) are deliberately generous: they are meant to brake floods, not guess passwords — 64-character refresh/reset tokens are unguessable anyway.

Why the login counter keys on (IP + account) and not on the email alone: a pure email counter could be abused to deliberately lock out other people's accounts — you would only have to send a wrong password often enough. The pairing prevents that.

Anyone running into an active account lockout gets the same answer as at every other brake: 429 with a note about too many failed attempts. No CAPTCHA is requested — it would change nothing about an active lockout. The message therefore states what is really the case: a temporary state, not a blocked account.

Two operational notes: the layer-2 IP counters live in the backend instance's memory; a restart resets them. The layer-1 account lockouts live in Redis and survive a restart. And behind an external reverse proxy TRUSTED_PROXIES must be correct — otherwise all requests look like one IP and every IP limit hits everyone at once. All values are ENV-configurable and can be raised without a rebuild: Environment → Rate limits.

Real-time channels check the same visibility

The application keeps detail pages live: whoever has an object open sees the other viewers and receives changes without reloading. It checks the same row-level visibility as the REST endpoint, the list and the global search, from the same source.

  • Two levels, both checked: the list channel of a type requires the read permission of that type; the channel of a single object additionally requires visibility of that very row — including asset type locks, mailbox and group restrictions, and the grants of a knowledge article.
  • No guessing via IDs: Without visibility of an object you get a rejection on join instead of a viewer list, and a bulk subscription silently filters such objects out. Unknown types are rejected, and the number of objects per subscription is capped.
  • Consequence for restricted rows: A link visible only as a placeholder does not update live. Deliberately so: the placeholder reveals nothing about the content, and neither does the live channel.

How the channel works, what users see of it and which object kinds it covers: Real-time & Presence →

8. Compliance & Audit

Enterprise Audit System

  • SHA-256 Hash Chain: Each entry is chained to the previous one via SHA-256; tampering is detected on verification
  • Immutability: UPDATE/DELETE forbidden (DB trigger)
  • PII Scrubbing: Automatically redacts passwords, tokens, email, phone
  • 9 Audit Domains: AUTH, ENTITY, ADMIN, SECURITY, SLA, WORKFLOW, SYSTEM, DATA_ACCESS, NOTIFICATION
  • 5 Severity Levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
  • Redis Fallback: On DB error → Redis backup (7 days TTL)
  • Chain Verification API: Quick check & full verification

📘 Details: See Enterprise Audit System →

Supported Standards

Standard Supported Requirements
FIPS 140-2 PBKDF2-SHA512, HMAC-SHA256, AES-256-GCM, Strict FIPS mode available
ISO 27001 Audit logging, access control, encryption, incident management
SOC 2 Audit trail, change management, monitoring, security controls
GDPR PII scrubbing, data retention, user anonymization, audit logs
NIS2 Incident reporting, SLA tracking, security monitoring

Security Best Practices

Secrets Management

  1. Change All Secrets: JWT_SECRET, POSTGRES_PASSWORD, INTERNAL_API_KEY, LICENSE_ENCRYPTION_KEY, TWO_FACTOR_ENCRYPTION_KEY, REDIS_PASSWORD
  2. Docker Secrets: Use Docker Secrets (Swarm) or Kubernetes Secrets instead of ENV
  3. Key Rotation: JWT_SECRET_OLD enables gradual rotation
  4. LICENSE_ENCRYPTION_KEY: NEVER change after first licenses (data loss!)
  5. TWO_FACTOR_ENCRYPTION_KEY: NEVER change after first MFA activation (TOTP secrets unreadable!)
  6. Generation: openssl rand -hex 32 for secure keys

📘 Details: See Environment Variables → (Secret Generation)

Role-Based Access Control (RBAC)

  1. Least Privilege: Use viewOwn for END_USER, viewAll only for AGENT/ADMIN
  2. Custom Roles: Create custom roles only when needed (complex to manage)
  3. Permission Audit: Role changes are logged (ADMIN domain, WARNING severity)
  4. Critical Actions: tickets.delete, changes.approve always DB revalidation (no cache)
  5. Permission Cache: 5min TTL, invalidated on role change

Password Policy

  • PBKDF2-SHA512: 210,000 iterations (FIPS 140-2 compatible, configurable via PBKDF2_ITERATIONS)
  • One-Way Hash: Passwords cannot be decoded
  • Password Reset: Secure token via email (time-limited)
  • Recommendation: Min. 12 characters, special chars, numbers

Security Features by Feature Area

ITSM Core (Tickets, Incidents, Problems, Changes)

  • Permission-Based Visibility: Users see only allowed entities
  • Internal Notes: Only visible with viewInternal permission
  • Optimistic Locking: Version field prevents race conditions
  • Self-Approval Block: Requester ≠ Approver (changes, incidents)
  • Activity Timeline: Complete audit trail of all changes

Assets & Inventory

  • QR Code Handover: HMAC-Token for public endpoint (time-limited)
  • Type-Based Permissions: 6 permissions per asset type
  • Financial Data: Separate permission for financial fields
  • Checkout Tracking: Who has which asset when (complete history)

📘 Details: See Assets API → (HMAC Token, Type Permissions)

Contracts & Licenses

  • AES-256-GCM Encryption: License keys encrypted at-rest
  • Format: iv:authTag:encrypted (Base64)
  • Key Access Audit: Every decrypt is logged (IP, UserAgent, timestamp)
  • viewSensitive Permission: Required for license key access

📘 Details: See Contracts & Licenses API → (AES-256-GCM Encryption)

Workflows & Automation

  • SSRF Protection: Webhook actions validated (private IPs only via SSRF_ALLOWLIST, loopback/metadata always blocked)
  • Circuit Breaker: Protection against cascade failures (threshold: 5, reset: 30s)
  • Timeout Protection: Webhook calls max. 30s
  • API-Key Auth: Workflow API triggers require API key

📘 Details: See Workflows API → (SSRF Protection, Circuit Breaker)

Security Incident Response

Detection Mechanisms

  • Virus Detection: ClamAV scans ALL uploads (no exceptions)
  • Intrusion Detection: Audit events (SECURITY domain) for suspicious activities
  • Failed Login Tracking: Audit events (AUTH domain) for brute-force detection
  • Permission Violations: Audit events (SECURITY domain, ERROR severity)

Response Actions

  • Infected Files: Automatically moved to quarantine volume
  • Admin Notification: Multi-channel alert on security events
  • User Deactivation: isActive = false (soft-delete, reversible)
  • Permission Revocation: Role changes take effect immediately (the role's permission cache is cleared)
  • Audit Investigation: Chain verification API for forensics

Security Checklist (Production)

Before Production Start

⚠️ CRITICAL - MUST be changed:

  • ☐ JWT_SECRET change (don't use default!)
  • ☐ POSTGRES_PASSWORD change (supersecretpassword → secure)
  • ☐ JOBWORKER_DB_PASSWORD change
  • ☐ READONLY_DB_PASSWORD change
  • ☐ INTERNAL_API_KEY change (openssl rand -hex 32)
  • ☐ LICENSE_ENCRYPTION_KEY change (NEVER change later!)
  • ☐ TWO_FACTOR_ENCRYPTION_KEY change (NEVER change later!)
  • ☐ REDIS_PASSWORD change (openssl rand -hex 32)
  • ☐ TURNSTILE_SITE_KEY + TURNSTILE_SECRET_KEY configure (Cloudflare dashboard)
  • ☐ VAPID_KEYS regenerate (web push)

After Production Start

  • ☐ Change admin password (admin@company.com) and enable MFA
  • ☐ SEED_DATABASE=false set (no new test users)
  • ☐ TLS certificates from Let's Encrypt (not self-signed)
  • ☐ Firewall rules (only 80, 443 public)
  • ☐ Monitor health checks
  • ☐ Test backup strategy (postgres_data, uploads)
  • ☐ Regularly check audit logs (chain verification)
  • ☐ ClamAV virus defs updated (Freshclam)
  • ☐ Consider FIPS mode for regulated environments (DOCKERFILE_BACKEND=Dockerfile.fips)

Related Documentation