> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quixa.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Installation Hardening Guide

> Secure defaults of an on-premise Quixa installation and the settings to review before production: TLS, service accounts, firewall, outbound rules, secrets, backups, SIEM.

# Installation Hardening Guide

`scripts/install.sh` installs Quixa with secure defaults. This page lists what those defaults are and what you should configure for your environment.

## Defaults you get

* **Pinned images**: every container image has an exact version; Quixa images use the release tag you install (never `latest`). The air-gap bundle contains `SHA256SUMS` (optionally GPG-signed) and the image digests.
* **Non-root containers** with a read-only root file system (`read_only`, `no-new-privileges`) for the API, web app and script sandbox.
* **Only ports 3002 and 5001** (and 3003 when Metabase is installed) are published. SQL Server and the script sandbox are not.
* **Least-privilege database logins**: the API uses `quixa_app` (Quixa database only: read, write, schema changes); Metabase uses `quixa_reporting` (read-only).
* **Generated secrets** in `/opt/quixa/.env` (root only, `chmod 600`): SQL passwords, JWT signing key (48 random bytes), resolver token, Metabase admin password, backup key.
* **Production mode**: Swagger and the Hangfire dashboard are off; internal error details are not returned; CORS allows only your web app origin.
* **Sign-in**: 15-minute access tokens with rotating refresh tokens; sessions end after 60 minutes idle or 12 hours; accounts lock for 15 minutes after 5 wrong passwords. Users normally sign in through your directory (LDAP / Entra ID), which also enforces MFA; an authenticator app for local (break-glass) administrators can be switched on (see the settings reference).

## Before production

### 1. TLS in front of Quixa

Put Quixa behind your reverse proxy or load balancer (nginx, F5, HAProxy) and terminate HTTPS there. Forward `X-Forwarded-Proto`. Set `FRONTEND_ORIGIN` and `API_BASE_URL` in `.env` to the HTTPS addresses. Allow only TLS 1.2+.

### 2. Firewall

| Source | Destination | Port | Purpose |
| :- | :- | :- | :- |
| User network | Reverse proxy | 443 | Web app and API |
| Quixa VM | APIs under test | as needed | Flow requests |
| Quixa VM | Domain controllers | 636 / 389 | LDAPS / StartTLS (if used) |
| Quixa VM | SIEM | 6514 | Audit events (if used) |
| Quixa VM | AI provider | 443 | Only if AI is on and not a local model |

Block everything else outbound from the VM.

### 3. Which internal APIs flows may call

Private addresses are blocked by default. Set the range of the APIs under test:

```bash theme={null}
# /opt/quixa/.env
OUTBOUND_ALLOWED_CIDR=10.20.0.0/16
# single-customer installation: workspace admins may add their own private allow entries
OUTBOUND_TENANT_RULES_MAY_ALLOW_PRIVATE=true
```

Cloud metadata (`169.254.169.254`), link-local, loopback and the Docker network stay blocked whatever you set. Workspace administrators can further restrict destinations (deny lists, allow-list-only mode) in **Backoffice → Security & Audit** and test a URL there.

If your internal APIs use certificates from a private CA, answer *yes* to the CA question in the installer (or set `Security__Outbound__TrustedCaCertificatesPath` to a PEM file). Host names are still checked; there is no "accept any certificate" option.

### 4. SQL Server certificate

The bundled SQL Server uses a self-signed certificate (connections are encrypted, `DB_TRUST_SERVER_CERTIFICATE=True`). To use a CA-issued certificate, mount it into the `quixa-db` container (`/var/opt/mssql/certs`), configure `mssql.conf` (`tlscert`, `tlskey`, `forceencryption = 1`) and set `DB_TRUST_SERVER_CERTIFICATE=False`.

### 5. Key protection

Credentials are encrypted with ASP.NET Data Protection; the key ring is stored in the database. Encrypt the key ring itself with a certificate or Azure Key Vault:

```bash theme={null}
# certificate (PFX) mounted into quixa-api
DataProtection__CertificatePath=/certs/dataprotection.pfx
DataProtection__CertificatePassword=...
# or Azure Key Vault
DataProtection__KeyVaultKeyId=https://<vault>.vault.azure.net/keys/quixa-dp
```

Back up the certificate separately: without it, stored credentials cannot be decrypted.

### 6. SIEM

```bash theme={null}
SIEM_ENABLED=true
SIEM_SINK=syslog      # syslog (RFC 5424), http (Splunk HEC / Elastic) or file (JSON lines)
SIEM_PROTOCOL=tls     # udp, tcp or tls
SIEM_HOST=siem.bank.internal
SIEM_PORT=6514
```

Each event is one JSON document (`timestamp`, `tenantId`, `category`, `action`, `outcome`, `actor`, `target`, `client`, `details`). Administrators can also export the audit log as JSON lines or CSV in the Backoffice.

### 7. Backups

```bash theme={null}
sudo quixa-ctl backup                  # encrypted with /opt/quixa/backup.key, checksummed
sudo quixa-ctl verify-backup <file>    # checksum + RESTORE VERIFYONLY
sudo quixa-ctl restore <file>          # replaces the database, re-links the logins
```

Store `backup.key` offline (and the Data Protection certificate, if used). Test a restore on a separate VM at least once per quarter.

### 8. Rotating secrets

```bash theme={null}
sudo quixa-ctl rotate-secrets          # database passwords and resolver token
```

`JWT_SECRET`: change it in `.env` and run `quixa-ctl restart` (everyone signs in again). `SA_PASSWORD`: change it in SQL Server (`ALTER LOGIN sa WITH PASSWORD = ...`), then in `.env`.

### 9. Retention

Executions (with request and response data) are deleted after 90 days and audit events after 365 days by default. Workspace administrators can change this in **Security & Audit** (audit events: at least 90 days). `DataRetention__ExecutionRetentionDays` / `DataRetention__AuditRetentionDays` set the installation defaults. Deleting a tenant in the Backoffice removes all of its data.

## Upgrading Metabase from v0.51

Releases from October 2026 ship Metabase v0.64.4 (v0.51 has critical vulnerabilities). Metabase's H2 database from v0.51 does not migrate to v0.64 in place. Metabase holds no Quixa data (only its own users and the dashboards that `quixa-metabase-setup` creates), so start it on a new volume:

```bash theme={null}
sudo quixa-ctl stop
docker volume create quixa-metabase-data-v051 && docker run --rm -v quixa-metabase-data:/from -v quixa-metabase-data-v051:/to alpine:3.24.2 cp -a /from/. /to/
docker run --rm -v quixa-metabase-data:/d alpine:3.24.2 sh -c 'rm -rf /d/*'
sudo quixa-ctl start      # quixa-metabase-setup recreates the admin, data source and dashboards
```

Export custom dashboards you built yourself before upgrading and import them afterwards.

## Settings reference

| Setting | Default | Meaning |
| :- | :- | :- |
| `Auth__Sessions__AccessTokenMinutes` | 15 | Access token lifetime |
| `Auth__Sessions__IdleTimeoutMinutes` | 60 | Session ends without activity |
| `Auth__Sessions__AbsoluteLifetimeHours` | 12 | Session ends regardless of activity |
| `ADMIN_MFA_PROVIDER` (`Security__Auth__RequireMfaForAdminProviders__0`) | *(empty)* | Admins signing in with this provider must use an authenticator app: `Local` for break-glass accounts (recommended when local admins exist), `Ldap` for directory admins without directory MFA |
| `Security__Auth__LockoutThreshold` / `LockoutMinutes` | 5 / 15 | Account lockout |
| `Security__Outbound__FlowTimeout` | 00:01:40 | Timeout per flow request |
| `Security__Outbound__FlowMaxResponseBytes` | 52428800 | Largest response a flow request accepts |
| `AiServices__Enabled` | true | Installation-wide AI switch |
| `Karate__AllowRealExecution` | false | Runs Karate as a Java process (its requests bypass the outbound guard) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.