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

# Environment Variable Reference for Safe Online Exam

> Safe Online Exam env var reference: profile resolution, hardened startup validation, PostgreSQL settings, required app values, and file-based secrets.

Safe Online Exam reads all of its runtime settings from process environment variables. The application does not load `.env` files itself — Compose explicitly supplies an environment file, and local development uses `.env.example` as a reference only. This page documents every variable the application accepts, the validation rules applied in hardened runtimes, and the deployment-template guidance that prevents misconfiguration.

<Note>
  Do not combine deployment templates blindly. Variables such as `APP_IMAGE`, `PROJECT_ID`, and `PUBLIC_HOST` are consumed by deployment scripts and Compose itself — they are not application runtime settings. Start from the single template that matches your topology: `.env.example` for a source checkout, `.env.compose.example` or `.env.compose.secrets.example` for Compose, or `deploy/cloudrun.env.example` for Cloud Run.
</Note>

## Profiles and Validation

The application resolves its runtime profile from the first non-empty value among `APP_ENV`, `NODE_ENV`, and the compatibility alias `SPRING_PROFILES_ACTIVE`.

| Resolved value                       | Result                                             |
| ------------------------------------ | -------------------------------------------------- |
| `prod` or `production`               | Production profile and hardened validation.        |
| `test`                               | Test profile; in-memory repositories are selected. |
| Any other value, or all values unset | Development profile.                               |

<Warning>
  Hardened validation is always active in production (`APP_ENV=prod`) **and** on any Cloud Run service regardless of profile — including an isolated `APP_ENV=dev` service. When hardened validation is active, the application performs strict checks on every database, Canvas, LTI, OAuth, secret, URL, and certificate value and terminates at startup before listening if any value is missing or unsafe. There is no partial startup.
</Warning>

The parser retains a small number of historical aliases — including `APP_BASE_URL`, `CANVAS_BASE_URL`, `DEPLOYMENT_ID`, and the `DEV_*`/`PROD_*` credential names — for maintained deployments. New installations should use only the canonical names documented here. In a hardened runtime, any supplied `APP_BASE_URL` or `BASE_URL` must exactly match `TOOL_URL`.

***

## Critical Application Variables

<ParamField path="TOOL_URL" type="string" required>
  Public HTTPS origin of this Safe Online Exam deployment. Must be an HTTPS origin with no path, query string, credentials, or fragment. This value is used in LTI launch verification, Canvas OAuth redirect validation, and SEB configuration generation.

  **Example:** `https://seb-tool.example.edu`
</ParamField>

<ParamField path="CANVAS_DOMAIN" type="string" required>
  The HTTPS origin of the connected Canvas LMS installation. Must be an HTTPS origin only. Do not include a trailing slash or path segment. The application derives `CANVAS_API_BASE_URL` from this value when that variable is not explicitly set.

  **Example:** `https://school.instructure.com`
</ParamField>

<ParamField path="LTI_CLIENT_ID" type="string" required>
  The client ID from the Canvas LTI 1.3 Developer Key created for this tool. This value must match the `aud` claim in LTI launch tokens issued by Canvas.

  **Example:** `170000000000042`
</ParamField>

<ParamField path="DATABASE_HOST" type="string" required>
  Hostname, IP address, or Unix socket directory for the PostgreSQL server. Required in hardened runtimes. For Cloud SQL on Cloud Run, use the socket path format: `/cloudsql/PROJECT:REGION:INSTANCE`.

  **Default:** `127.0.0.1`
</ParamField>

<ParamField path="SESSION_SECRET" type="string" required>
  Signing secret for Express session cookies. Must be at least 32 characters and must differ from `STATE_ENCRYPTION_KEY`. Rotating this value invalidates all active user sessions. Prefer the `SESSION_SECRET_FILE` alternative in production.
</ParamField>

***

## PostgreSQL Variables

PostgreSQL 17 or newer is the supported durable store. The application uses standard PostgreSQL protocol settings and is not tied to any managed provider.

| Variable                         | Default      | Notes                                                                                                        |
| -------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
| `DATABASE_HOST`                  | `127.0.0.1`  | Hostname, IP, or Unix socket directory. Required in hardened runtimes.                                       |
| `DATABASE_PORT`                  | `5432`       | Integer from 1–65535. For Cloud SQL sockets this remains `5432`.                                             |
| `DATABASE_NAME`                  | `canvas_seb` | Dedicated application database. Required in hardened runtimes.                                               |
| `DATABASE_USER`                  | `canvas_seb` | Application database role. Required in hardened runtimes.                                                    |
| `DATABASE_PASSWORD`              | Unset        | Required in hardened runtimes. Prefer `DATABASE_PASSWORD_FILE`.                                              |
| `DATABASE_PASSWORD_FILE`         | Unset        | Absolute path to a readable file containing the password. Conflicts with the direct variable.                |
| `DATABASE_SSL_MODE`              | `disable`    | `disable`, `require`, `verify-ca`, or `verify-full`. Use `verify-full` over untrusted networks.              |
| `DATABASE_POOL_MAX`              | `5`          | Per-process maximum connections, 1–100. Size the database for pool max multiplied by app instances and jobs. |
| `DATABASE_CONNECTION_TIMEOUT_MS` | `10000`      | Connection acquisition timeout in milliseconds, 100–120000.                                                  |
| `DATABASE_STATEMENT_TIMEOUT_MS`  | `30000`      | Server-side statement timeout in milliseconds, 100–600000.                                                   |
| `DATABASE_CLEANUP_BATCH_SIZE`    | `500`        | Batch size for the cleanup job, 1–10000. Read by `db:cleanup` only, not normal startup.                      |

A VM or external managed PostgreSQL connection should normally use `verify-full` with a trusted certificate chain. If the provider uses a private certificate authority, mount the CA file and set `NODE_EXTRA_CA_CERTS` to that path before process startup. Cloud Run uses an authenticated Unix socket that is local to the Cloud Run sandbox, so `DATABASE_SSL_MODE=disable` is correct in that topology.

***

## Required Application Values

These variables are validated in every hardened runtime. Startup fails before the server begins listening if any required value is absent or fails its format check.

| Variable                                    | Purpose                                          | Hardened requirement                                                                                      |
| ------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `NODE_ENV`                                  | Node runtime mode.                               | `production` for the deployed image.                                                                      |
| `APP_ENV`                                   | Application profile.                             | `dev` for isolated non-production; `prod` for production.                                                 |
| `PORT`                                      | HTTP listener port.                              | Defaults to `8080`; platforms may inject it.                                                              |
| `TOOL_URL`                                  | Public origin of this deployment.                | HTTPS origin only, no path, query, or credentials.                                                        |
| `CANVAS_DOMAIN`                             | Connected Canvas origin.                         | HTTPS origin only.                                                                                        |
| `LTI_CLIENT_ID`                             | Canvas LTI 1.3 Developer Key client ID.          | Required.                                                                                                 |
| `LTI_PRIVATE_KEY`                           | RSA private JWK used for tool signing.           | RSA 2048+ bits, exponent 65537, RS256-compatible.                                                         |
| `LTI_DEPLOYMENT_ID_CHECKING_ENABLED`        | Enforce deployment-ID allowlist.                 | Defaults to `true`. Set `false` only for a controlled self-service course-install rollout.                |
| `LTI_COURSE_NAVIGATION_VISIBLE_TO_STUDENTS` | Canvas placement visibility for students.        | Only the case-insensitive value `false` selects `admins` visibility; any other value preserves `members`. |
| `LTI_DEPLOYMENT_ID`                         | Installed External App deployment ID.            | Required when checking is enabled; comma/newline allowlist supported.                                     |
| `CANVAS_API_CLIENT_ID`                      | Canvas API OAuth Developer Key client ID.        | Required and distinct from the LTI key.                                                                   |
| `CANVAS_API_CLIENT_SECRET`                  | Canvas API OAuth secret.                         | Required secret. Prefer `CANVAS_API_CLIENT_SECRET_FILE`.                                                  |
| `SESSION_SECRET`                            | Express session signing secret.                  | At least 32 characters; must differ from `STATE_ENCRYPTION_KEY`.                                          |
| `STATE_ENCRYPTION_KEY`                      | AES-GCM material for opaque LTI/OAuth state.     | At least 32 characters; must differ from `SESSION_SECRET`.                                                |
| `OAUTH_TOKEN_ENCRYPTION_KEYRING`            | JSON key-ID map for stored Canvas OAuth tokens.  | Required secret; every value is a 32-byte base64url AES key.                                              |
| `OAUTH_TOKEN_ENCRYPTION_ACTIVE_KEY_ID`      | Key ID used for new token writes.                | Required and must exist in the keyring.                                                                   |
| `OAUTH_TOKEN_ENCRYPTION_MODE`               | OAuth-token persistence rollout mode.            | `enforce` for encrypted writes; `compat` is temporary rollback preparation only.                          |
| `SEB_CONFIG_ENCRYPTION_CERT_PEM`            | X.509 certificate used to encrypt `.seb` output. | Required when certificate encryption is enabled.                                                          |

***

## Optional and Diagnostic Variables

| Variable                                | Default   | Notes                                                                                                                                                       |
| --------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SEB_QUIT_PASSWORD`                     | Unset     | Optional managed exit-password fallback. Must be 8–128 characters with at least 5 different letters or numbers; common and predictable values are rejected. |
| `SEB_REQUIRED_DOMAINS`                  | Empty     | Concrete reviewed hostnames only. Wildcards and identity-provider hosts are rejected. Accepts comma- or newline-separated values.                           |
| `SEB_CONFIG_ENCRYPTION_ENABLED`         | `true`    | Set explicitly to `false` to disable certificate wrapping. A teacher-set start password still wraps that assessment's configuration.                        |
| `SEB_CONFIG_ENCRYPTION_CERT_PATH`       | Unset     | Path to the public certificate file; used by the Compose secret mount.                                                                                      |
| `SEB_CONFIG_ENCRYPTION_PUBLIC_KEY_PEM`  | Unset     | Local-development fallback public key; insufficient for hardened validation.                                                                                |
| `SEB_CONFIG_ENCRYPTION_PUBLIC_KEY_PATH` | Unset     | File path form of the local public-key fallback; insufficient for hardened validation.                                                                      |
| `HOST`                                  | `0.0.0.0` | TCP bind address.                                                                                                                                           |
| `USE_IN_MEMORY_STORE`                   | `false`   | Local and test use only; hardened runtimes reject `true`.                                                                                                   |
| `APP_DEBUG_ENABLED`                     | `false`   | Hardened runtimes reject `true`.                                                                                                                            |
| `APP_DETECTOR_DIAGNOSTICS_ENABLED`      | `false`   | Sanitized detector trace details. Production profile rejects `true`.                                                                                        |
| `APP_ASSET_VERSION`                     | Unset     | Optional client cache-busting version string. `K_REVISION` is used when present on Cloud Run.                                                               |
| `DEV_TESTBED_ENABLED`                   | `false`   | Enables bounded diagnostics on an isolated Cloud Run development instance. Allowed only with `APP_ENV=dev`; production rejects it.                          |

***

## File-Based Secrets

The following sensitive values each accept a mutually exclusive `_FILE` alternative that instructs the application to read the secret from a file path rather than a direct environment variable. Files are read once during configuration startup.

| Direct variable                  | File alternative                      |
| -------------------------------- | ------------------------------------- |
| `DATABASE_PASSWORD`              | `DATABASE_PASSWORD_FILE`              |
| `LTI_PRIVATE_KEY`                | `LTI_PRIVATE_KEY_FILE`                |
| `CANVAS_API_CLIENT_SECRET`       | `CANVAS_API_CLIENT_SECRET_FILE`       |
| `SESSION_SECRET`                 | `SESSION_SECRET_FILE`                 |
| `STATE_ENCRYPTION_KEY`           | `STATE_ENCRYPTION_KEY_FILE`           |
| `OAUTH_TOKEN_ENCRYPTION_KEYRING` | `OAUTH_TOKEN_ENCRYPTION_KEYRING_FILE` |
| `SEB_QUIT_PASSWORD`              | `SEB_QUIT_PASSWORD_FILE`              |

The application rejects any configuration where both the direct variable and its `_FILE` alternative are set simultaneously. Unreadable paths are reported without echoing any secret contents. Required-value validation rejects a missing or empty file result just as it would a missing direct variable. Docker and Kubernetes secret mounts work without any provider-specific SDK.

For detailed guidance on secret file format, keyring structure, the Docker Compose secrets profile, and rotation procedures, see [Managing Secrets and File-Based Configuration](/configuration/secrets).
