InnConnect Docs ← Back to InnConnect

Security

Defense-in-depth security for sensitive customer data in regulated environments.

Overview

InnConnect handles customer conversations, personal data, and AI-generated content for organizations operating under GDPR, ISO 27001, NIS2, and sector-specific regulations. Every layer of the platform is designed with the assumption that it will be attacked.

Security controls operate at three levels — infrastructure, application, and AI — to provide defense in depth. A vulnerability in one layer does not compromise the system, because the next layer catches it. For compliance framework mapping, see the Compliance page.

Authentication & Access

Two-factor authentication, forced password changes, session management, and 7-role RBAC across all admin panel actions.

Input & Output Protection

Server-side validation, dual-layer HTML sanitization (HTMLPurifier + DOMPurify), CSRF protection, and Content Security Policy.

AI-Specific Defenses

Structural prompt injection defense, SSRF protection on URL scraping, and content sanitization in the AI pipeline.

Tenant Isolation

Separate PostgreSQL database per tenant, per-tenant encryption keys, isolated S3 storage, and Kubernetes Secrets for credentials.

Tenant Isolation

Tenant isolation is the foundational security measure in InnConnect. Every other control depends on it. A bug in one tenant's data handling cannot affect another tenant, because their data is physically separated at every layer.

Layer Isolation method
Database Each tenant gets a dedicated PostgreSQL database (innconnect_t{id}). There are no shared tables between tenants. Cross-tenant queries are architecturally impossible.
Credentials Database credentials for each tenant are stored in Kubernetes Secrets — never in the application database, configuration files, or environment variables. Secrets are mounted at runtime.
Connection pooling PgBouncer handles connection pooling with transaction-level isolation. Each tenant connection is routed through a wildcard database proxy on port 6432.
Encryption keys Field-level encryption uses per-tenant keys stored in Kubernetes Secrets. Compromising one tenant's key does not expose another tenant's encrypted fields.
Object storage Each tenant's file uploads are stored in an isolated prefix within TransIP Object Storage (S3-compatible). Bucket policies prevent cross-tenant access.
Cache Redis cache keys are prefixed with the tenant ID. Cache invalidation is scoped per tenant.
Why separate databases? Row-level tenancy (shared database with a tenant_id column) is a common shortcut that creates a single-bug risk: one missing WHERE tenant_id = ? clause leaks data across all tenants. Separate databases eliminate this class of vulnerability entirely. The trade-off is operational complexity, which PgBouncer and automated provisioning absorb.

Authentication

Admin panel access requires email and password authentication via Laravel Sanctum. Plain-text passwords are never stored, logged, or transmitted after initial hashing.

Two-Factor Authentication

All admin users can enable TOTP-based two-factor authentication. When enabled, login requires both a valid password and a 6-digit time-based code from an authenticator app (Google Authenticator, Authy, 1Password, or any TOTP-compatible app).

Setup process

  1. Navigate to Profile → Security.
  2. Click Enable Two-Factor Authentication.
  3. Scan the QR code with your authenticator app. The QR encodes a standard otpauth:// URI with the account name and secret.
  4. Enter the 6-digit verification code displayed by the app to confirm setup.
  5. Download your recovery codes and store them in a secure location (password manager, printed in a safe).

Screenshot: 2FA setup with QR code and verification input

Recovery codes

During 2FA setup, eight single-use recovery codes are generated. Each code can be used exactly once to bypass 2FA if you lose access to your authenticator app.

Enforce 2FA for your team. Tenant admins can require mandatory 2FA for all users from Settings → Security Policy. Users without 2FA enabled will be redirected to the setup flow on their next login and cannot access any other page until 2FA is configured.

Force Password Change

New users invited to the platform receive a temporary password via email. The system forces a password change on first login before the user can access any other admin panel functionality. This ensures temporary credentials have a minimal window of exposure.

Session Management

Sessions are stored server-side in the PostgreSQL system database — not in cookies, not in Redis. The cookie contains only an opaque session token. This prevents client-side tampering and makes server-side session invalidation take effect immediately.

Setting Default Configurable
Session lifetime 120 minutes of inactivity Yes (per tenant)
Session cookie flags HTTP-only, Secure, SameSite=Lax No (hardcoded for security)
Concurrent sessions Multiple sessions allowed Yes (can restrict to single session)
Logout behavior Invalidates current session only Option to invalidate all sessions
Session storage PostgreSQL system database No

Rate Limiting

All sensitive endpoints are rate-limited to prevent brute force attacks, API abuse, and excessive AI credit consumption. Rate limits are enforced at the application layer using Laravel's built-in throttle middleware. Each limit is keyed to prevent one user's limit from affecting another.

Endpoint Limit Window Keyed by
Admin login 5 attempts per minute Email + IP address
Chat messages (widget API) 20 messages per minute Session ID
KB Wizard (AI research) 30 requests per minute Authenticated user ID
Credit checkout 10 requests per hour Authenticated user ID
User registration 5 attempts per hour IP address
Password reset 5 attempts per hour User ID or IP address
API key regeneration 10 requests per hour Authenticated user ID

When a rate limit is exceeded, the server returns HTTP 429 Too Many Requests with a Retry-After header indicating when the limit resets. Repeated violations are logged to the security audit log with the offending IP and identity.

Input Validation

All data submitted to InnConnect — through admin panel forms or API endpoints — is validated server-side before processing. Client-side validation exists as a UX convenience; it is never trusted for security decisions.

XSS Protection

Cross-Site Scripting is mitigated through two independent sanitization layers. If one layer fails, the other catches it.

Server-side: HTMLPurifier

The ContentSanitizer service runs HTMLPurifier on all HTML content before storage — AI-generated articles, user-submitted text, and scraped web pages. A strict whitelist of allowed tags and attributes is enforced. Everything else is stripped. Suspicious patterns are logged to the security audit log.

Client-side: DOMPurify

Markdown content rendered in the admin panel (KB articles, AI wizard output) passes through DOMPurify before DOM insertion via x-html. Allowed tags are restricted to safe formatting elements: p, strong, em, ul, ol, li, headings, code, pre, blockquote, and a (with href only).

In addition, Blade templates use double-curly-brace escaping by default, which HTML-encodes output. Unescaped output is used only when content has already passed through HTMLPurifier, and these usages are tracked and audited.

SSRF Protection

The KB Wizard allows admin users to provide external URLs for content scraping. Without validation, this creates a Server-Side Request Forgery (SSRF) vector — an attacker could trick the server into making requests to internal infrastructure, cloud metadata endpoints, or other restricted resources.

The UrlValidator service validates every URL before any outbound HTTP request is made. It blocks:

Blocked target Why
Non-HTTP(S) schemes (file://, ftp://, dict://, gopher://) Prevents local file reads and protocol-based attacks
Loopback addresses (127.0.0.1, ::1, localhost) Prevents access to services on the same host
Private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) Prevents access to internal network services
Link-local range (169.254.0.0/16) Prevents access to cloud metadata endpoints (AWS, Azure, GCP instance metadata)
IP obfuscation (decimal, hex, octal encoding) Prevents bypass via alternative IP representations like 0x7f000001
DNS rebinding Hostnames are resolved and the resulting IP is validated. A domain that resolves to 127.0.0.1 is blocked.

Blocked URL attempts are logged to the security audit log with the requesting user's identity, the rejected URL, and the reason for rejection.

Prompt Injection Defense

The AI chat pipeline processes content from three external sources: Knowledge Base articles, scraped web pages, and end-user messages. Any of these could contain malicious text designed to override the AI's instructions — a class of attack known as prompt injection.

InnConnect uses structural defenses rather than content filtering. Content filtering (e.g. blocking the phrase "ignore all previous instructions") is fragile and easily bypassed. Structural separation is robust.

Defense in depth, not perfection. No prompt injection defense is 100% effective against a determined adversary. The structural approach significantly raises the bar and catches the vast majority of injection attempts. Combined with content sanitization and audit logging, it provides a robust defense posture appropriate for regulated environments.

CSRF Protection

All state-changing form submissions in the admin panel include a Laravel CSRF token (@csrf). Requests without a valid token are rejected with HTTP 419.

Content Security Policy

InnConnect serves a Content Security Policy (CSP) header on all admin panel responses. The CSP restricts which resources can be loaded and executed in the browser.

Security Headers

The following HTTP security headers are set on all admin panel responses by the SecurityHeaders middleware.

Header Value Purpose
X-Content-Type-Options nosniff Prevents browsers from MIME-sniffing responses away from the declared content type
X-Frame-Options SAMEORIGIN Prevents the page from being embedded in frames on other domains (clickjacking protection). Same-origin embedding is allowed.
X-XSS-Protection 1; mode=block Enables the browser's built-in XSS filter in blocking mode. Complements CSP as a legacy defense layer.
Referrer-Policy strict-origin-when-cross-origin Sends only the origin (not the full URL path) in the Referer header for cross-origin requests
Permissions-Policy camera=(), microphone=(), geolocation=() Disables browser APIs that InnConnect does not use, reducing attack surface
Strict-Transport-Security max-age=31536000; includeSubDomains Forces HTTPS for one year after the first visit, including all subdomains (production only)
Responsible disclosure. If you discover a security vulnerability in InnConnect, report it by emailing security@inntelligence.nl. Include a description of the issue, steps to reproduce, and the potential impact. Do not disclose vulnerabilities publicly before a fix has been released. We acknowledge all reports within 2 business days and resolve critical issues within 72 hours.