Skip to content

Environment Variables

All configuration for Assessors Studio is driven by environment variables. The backend reads them at startup and validates them against a Zod schema; a malformed value fails fast with a descriptive error rather than producing surprising behavior at runtime.

Variables can be set in any of the usual places: a .env file next to the backend process, the environment section of a docker-compose.yml, a Kubernetes ConfigMap or Secret, or directly on the shell. The application does not care where a value comes from; it only reads the final environment.

VariableDefaultDescription
DATABASE_PROVIDERpgliteDatabase engine. Use pglite for embedded local development, postgres for production.
DATABASE_URLpostgresql://localhost:5432/assessors_studioPostgreSQL connection string. Used when DATABASE_PROVIDER=postgres.
PGLITE_DATA_DIR./data/pgliteDirectory for the embedded database files. Ignored when using PostgreSQL.
JWT_SECRETauto generatedSecret for signing session tokens. When unset the backend generates a secret on first run and stores it in the app_config table. Set explicitly (at least 32 characters) for multi replica deployments or to manage the key externally. Changing the value invalidates all active sessions.
JWT_EXPIRY24hToken lifetime. Accepts any value the ms library understands, for example 24h, 7d, 30m.
PORT3001HTTP port the backend listens on.
LOG_LEVELinfoVerbosity of application logs. One of error, warn, info, debug.
CORS_ORIGINhttp://localhost:5173Allowed CORS origin. The packaged container serves the SPA and the API on the same origin, so CORS is effectively a no op in that deployment. Set this when the API is called from a different origin, such as a Vite dev server during local work or a cross origin integration.
APP_URLhttp://localhost:5173Public base URL of the application (for example https://studio.example.com). Required for notification channels that include links back to the app.
NODE_ENVdevelopmentRuntime environment: development, production, or test.
COOKIE_SECUREautoWhether to mark session cookies with the Secure attribute. auto (default) infers from the request scheme and NODE_ENV, setting Secure whenever the app is served over HTTPS or NODE_ENV=production. true forces Secure on every response. false disables it (only appropriate for plain HTTP local development).
TRUST_PROXY_HOPS0Number of reverse proxy hops Express should trust for X-Forwarded-* header parsing. Leave at 0 for direct deployments. Set to 1 when a single TLS terminating ingress (nginx, ALB, Traefik) sits directly in front of the Node process. Required for correct client IP handling, rate limiting, Secure cookie inference, and audit logging in proxied deployments.

No environment variables are used to seed the initial administrator. The first account is created through the in browser setup wizard the first time the application is opened. When no users exist, the API responds to any request with a pointer to /setup. The SPA is always served and routes to the setup wizard automatically. Credentials are never written to configuration files, container images, or deployment manifests.

VariableDefaultDescription
REGISTRATION_MODEdisabledControls self service account creation. disabled rejects every request to POST /api/v1/auth/register. invite_only requires a valid unused invite token issued by an admin. open accepts any well formed registration and assigns the assessee role.
ACCEPT_OPEN_REGISTRATION_RISKfalseOperator acknowledgement required to run REGISTRATION_MODE=open in production. When NODE_ENV=production and REGISTRATION_MODE=open, the backend refuses to start unless this is set to true (or 1). The flag is ignored outside production so local and CI setups remain friction free. Open mode accepts any well formed registration with no invite token; only enable it when you have other controls in place such as network gating, captcha, or email verification.
INVITE_RETAIN_AFTER_TERMINAL_DAYS30Days to retain invites in consumed, revoked, or expired state before the periodic cleanup job purges them.

Admins can always create users through the admin APIs, irrespective of mode. When running in invite_only mode, call POST /api/v1/admin/invites with an optional email, an intendedRole, and an optional expiresInHours (default 168). The plaintext token is returned once; the server stores only the SHA-256 hash. GET /api/v1/admin/invites lists invites and DELETE /api/v1/admin/invites/:id revokes one.

The register endpoint returns a generic 202 Accepted response for both successful and duplicate submissions so an attacker cannot enumerate accounts by probing it.

Mode changes leave a tamper evidence trail. The backend persists the active REGISTRATION_MODE to the app_config table on startup and emits an audit row whenever the runtime value differs from the last persisted value, so a silent toggle from disabled or invite_only to open is always recoverable from the audit log.

VariableDefaultDescription
LOGIN_MAX_FAILED_ATTEMPTS5Number of consecutive failed password attempts before the account is locked. Set to 0 to disable per account lockout (rate limiting still applies).
LOGIN_LOCKOUT_DURATION_MINUTES15Minutes an account remains locked after exceeding LOGIN_MAX_FAILED_ATTEMPTS. The lock auto clears when the duration elapses.

Per IP rate limiting on /api/v1/auth (10 attempts per 15 minutes) is always on outside NODE_ENV=test and is not configurable. The two layers compose: per account lockout protects a known username from a focused attacker, and per IP rate limiting protects against credential stuffing across a username list.

VariableDefaultDescription
SESSION_CLEANUP_INTERVAL_MINUTES15Minutes between runs of the scheduled session cleanup job. Set to 0 to disable scheduled cleanup.
SESSION_RETAIN_EXPIRED_HOURS24Hours to retain expired session rows after they are no longer valid. The cleanup job deletes anything older than this window.
VariableDefaultDescription
PASSWORD_MIN_LENGTH12Minimum password length. Hard floor of 8 and ceiling of 128 enforced by configuration validation.
PASSWORD_HIBP_CHECK_ENABLEDfalseWhen true, the backend performs a Have I Been Pwned k anonymity range check on every new password during registration, password change, and password reset. The full password is never sent over the wire, only the first five characters of its SHA-1 hash.
PASSWORD_HIBP_TIMEOUT_MS3000Network timeout for the HIBP range API in milliseconds. On timeout the check fails open so a momentary network blip does not lock users out of password changes.
VariableDefaultDescription
STORAGE_PROVIDERdatabaseWhere new evidence attachments are stored: database or s3.
UPLOAD_MAX_FILE_SIZE52428800Maximum upload size in bytes. Default is 50 MB.
S3_BUCKETBucket name. Required when STORAGE_PROVIDER=s3.
S3_REGIONus-east-1AWS region or S3 compatible region.
S3_ENDPOINTCustom endpoint URL for MinIO, DigitalOcean Spaces, Backblaze B2, Cloudflare R2, and other S3 compatible providers. Leave unset for AWS S3.
S3_ACCESS_KEY_IDAccess key. Required when STORAGE_PROVIDER=s3 unless the process runs with ambient AWS credentials (IRSA, instance role). Prefer S3_ACCESS_KEY_ID_FILE in production.
S3_SECRET_ACCESS_KEYSecret key. Required alongside S3_ACCESS_KEY_ID. Prefer S3_SECRET_ACCESS_KEY_FILE in production.
S3_ACCESS_KEY_ID_FILEPath to a file whose contents are used as S3_ACCESS_KEY_ID. Intended for Docker Compose secrets mounted at /run/secrets/<name>. Overrides S3_ACCESS_KEY_ID when both are set. The backend refuses to start if the path is set but unreadable.
S3_SECRET_ACCESS_KEY_FILESame as above for the secret access key.
S3_FORCE_PATH_STYLEfalseSet to true for MinIO and other providers that require path style addressing. AWS S3 uses virtual host style by default.

See Evidence Storage for guidance on choosing between database and object storage, and for migration scripts.

VariableDefaultDescription
WEBHOOK_ENABLEDtrueMaster toggle for the webhook notification channel.
WEBHOOK_TIMEOUT10000HTTP timeout for webhook deliveries, in milliseconds.
WEBHOOK_MAX_RETRIES5Maximum retry attempts for failed deliveries.
WEBHOOK_DELIVERY_RETENTION_DAYS30Days to retain webhook delivery logs before automatic purge.

Individual webhook targets are configured through the admin UI and stored (encrypted) in the database, not through environment variables.

VariableDefaultDescription
SMTP_ENABLEDfalseMaster toggle for the email notification channel.
SMTP_HOSTSMTP server hostname.
SMTP_PORT587SMTP server port.
SMTP_SECUREfalseSet to true for implicit TLS (typically port 465).
SMTP_USERAuthentication username.
SMTP_PASSAuthentication password.
SMTP_FROMSender address. A common value is "Assessors Studio" <noreply@example.com>.
SMTP_TLS_REJECT_UNAUTHORIZEDtrueSet to false only when using a self signed certificate in development.
VariableDefaultDescription
SLACK_ENABLEDfalseEnable Slack as a notification channel.
TEAMS_ENABLEDfalseEnable Microsoft Teams as a notification channel.
MATTERMOST_ENABLEDfalseEnable Mattermost as a notification channel.
CHAT_TIMEOUT10000HTTP timeout for chat deliveries, in milliseconds.
CHAT_DELIVERY_RETENTION_DAYS30Days to retain chat delivery logs before automatic purge.

Individual chat destinations (channel, webhook URL) are configured through the admin UI.

VariableDefaultDescription
METRICS_ENABLEDfalseMaster toggle for the /metrics endpoint.
METRICS_TOKENBearer token required to scrape metrics. Leave empty for unauthenticated access. Recommended for any production installation that exposes metrics to a shared collector.
METRICS_PREFIXcdxa_Prefix applied to all exported metric names.
METRICS_DOMAIN_REFRESH_INTERVAL60Seconds between domain gauge refreshes.

See Metrics and Monitoring for the metric catalog and alerting recommendations.

VariableDefaultDescription
MASTER_ENCRYPTION_KEY256 bit key encoded as 64 hex characters. Required in production when REQUIRE_ENCRYPTION=true. When unset, the encryption service operates in passthrough mode (values stored as plaintext).
REQUIRE_ENCRYPTIONfalseWhen true, the application refuses to start if MASTER_ENCRYPTION_KEY is missing or too short. Production deployments should always set this to true.
OLD_MASTER_ENCRYPTION_KEYOnly used by the npm run rekey-master CLI during master key rotation. Set this to the previous 64 character hex key so the script can decrypt existing values before re encrypting with the new key. Remove from the environment once the rekey completes.

Generate a master key with:

Terminal window
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

See Encryption at Rest for the key hierarchy, rotation procedure, and admin visibility.

OpenID Connect is on the roadmap. When it ships, the OIDC_* environment variables will be documented here. Until then, the platform authenticates with local username and password only.

A minimal production environment looks like:

Terminal window
DATABASE_PROVIDER=postgres
DATABASE_URL=postgresql://assessors:<password>@db:5432/assessors_studio
STORAGE_PROVIDER=s3
S3_BUCKET=my-evidence
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=<key>
S3_SECRET_ACCESS_KEY=<secret>
MASTER_ENCRYPTION_KEY=<64 hex chars>
REQUIRE_ENCRYPTION=true
JWT_SECRET=<32+ chars>
APP_URL=https://studio.example.com
CORS_ORIGIN=https://studio.example.com
SMTP_ENABLED=true
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=<user>
SMTP_PASS=<pass>
SMTP_FROM="Assessors Studio <noreply@example.com>"
METRICS_ENABLED=true
METRICS_TOKEN=<random 32 char>
NODE_ENV=production
LOG_LEVEL=info

Everything not listed above is optional or defaults to a safe value for production. Review the full table when you are configuring an integration you have not configured before.