_FILE pattern, the OAuth token encryption keyring format, the compat vs enforce mode, the Docker Compose secrets profile, and safe rotation procedures for each class of secret.
File-Based Secret Alternatives
For each of the sensitive variables below, the application accepts a mutually exclusive_FILE alternative. Set either the direct variable or its file alternative — never both.
The application explicitly rejects a configuration where both a direct variable and its
_FILE counterpart are present in the environment. It reports the conflict and exits without reading either value. Unreadable paths are also reported as errors without echoing any secret contents.
Files are read exactly once during configuration startup. Required-value validation treats a missing or empty file result as equivalent to a missing direct variable — startup fails before listening with a descriptive error message.
OAuth Token Encryption Keyring
Safe Online Exam encrypts every stored Canvas OAuth access token and refresh token using AES-256-GCM. The keyring is a JSON object whose keys are string key IDs and whose values are 32-byte AES keys encoded as canonical base64url strings (no padding, URL-safe alphabet).[A-Za-z0-9][A-Za-z0-9._-]{0,63}.
OAUTH_TOKEN_ENCRYPTION_ACTIVE_KEY_ID identifies which key ID in the keyring is used for new token writes. Every read operation accepts any key ID present in the keyring, so old records remain decryptable after a key rotation as long as the retired key is not yet removed.
Generating a New AES Key
Use Node.js to generate a cryptographically random 32-byte base64url key:Keyring File Format
When usingOAUTH_TOKEN_ENCRYPTION_KEYRING_FILE, write the full JSON keyring object to the file. The file must contain valid JSON with no BOM:
Encryption Mode: enforce vs compat
OAUTH_TOKEN_ENCRYPTION_MODE controls how the application handles stored token records.
Set
OAUTH_TOKEN_ENCRYPTION_MODE=enforce in every production environment. The compat mode is documented only so that operators understand it is not a supported long-term state.
The db:encrypt-oauth-tokens Maintenance Command
After rotating to a new active key ID, run the bulk rewrite command to update every stored record to the new key before removing the old key from the keyring:
canvas_oauth_tokens table and re-encrypts any record whose stored key ID does not match OAUTH_TOKEN_ENCRYPTION_ACTIVE_KEY_ID. It reports counts only and fails closed — if any row cannot be decrypted with the keys in the current keyring, the command exits with an error and leaves all remaining rows untouched.
Only remove a retired key from the keyring after two successive runs of this command both report zero updated rows. This guarantees that every stored record has been rewritten under the active key.
Docker Compose Secrets Profile
The checked-incompose.secrets.yaml override implements the full file-based secrets pattern for Docker Compose deployments. Start it with .env.compose.secrets.example as the environment file.
The override does the following for every service that handles secrets (postgres, migrate, app, cleanup, and encrypt-oauth-tokens):
- Clears the corresponding direct secret variable to an empty value.
- Supplies the
_FILEpath pointing to/run/secrets/<name>. - Mounts only the named secret files into each container via Docker’s
secrets:mechanism.
POSTGRES_PASSWORD_FILE input via the same mechanism.
Directory Permissions
TheSECRETS_DIRECTORY (default ./secrets) is the host directory from which Compose reads each named file. On Linux hosts, set the containing directory to mode 0700 so that only the deploying user can list its contents:
0644 — Docker mounts only the named files into each container, and the 0700 directory prevents enumeration from other accounts on the same host.
Required files in SECRETS_DIRECTORY for the secrets profile:
Secret Rotation Effects on Live Sessions
Each secret class has a distinct impact when rotated. Rotate one secret at a time and follow the create → deploy → smoke-test → disable-old sequence.SESSION_SECRET rotation
SESSION_SECRET rotation
Rotating
SESSION_SECRET immediately invalidates all active user sessions. Every user will be redirected to re-authenticate with Canvas on their next request. There is no grace period. Schedule rotation during a low-traffic window and communicate the expected re-authentication to users in advance.STATE_ENCRYPTION_KEY rotation
STATE_ENCRYPTION_KEY rotation
Rotating
STATE_ENCRYPTION_KEY invalidates all outstanding opaque LTI and OAuth state tokens. Any user in the middle of an LTI launch or Canvas OAuth authorisation flow at the moment of rotation will see an error and need to restart that flow. Sessions that have already completed are unaffected.LTI_PRIVATE_KEY rotation
LTI_PRIVATE_KEY rotation
Rotating the LTI signing key requires coordination with Canvas. The new public key must appear in the JWKS endpoint at
${TOOL_URL}/lti/jwks before Canvas can verify launches signed with it. After deploying the new key, re-register the tool in Canvas by fetching the configuration from ${TOOL_URL}/lti/config so Canvas stores the updated JWKS reference.OAUTH_TOKEN_ENCRYPTION_KEYRING rotation
OAUTH_TOKEN_ENCRYPTION_KEYRING rotation
To rotate an OAuth token encryption key safely:
- Add a new 32-byte base64url key under a new key ID to the keyring JSON.
- Update
OAUTH_TOKEN_ENCRYPTION_ACTIVE_KEY_IDto the new key ID. - Deploy the updated keyring and active key ID.
- Run
npm run db:encrypt-oauth-tokens:builtto rewrite all stored tokens to the new key. - Run the command a second time and confirm zero updated rows.
- Only after both runs report zero updates, remove the old key from the keyring JSON and deploy again.
SEB certificate rotation
SEB certificate rotation
Rotating
SEB_CONFIG_ENCRYPTION_CERT_PEM or SEB_CONFIG_ENCRYPTION_CERT_PATH requires distributing the matching new private identity to all managed SEB client devices before deploying the new certificate. Existing downloaded SEB configurations encrypted with the old certificate will no longer open after rotation. Issue fresh SEB configurations for all active assessments after deploying the new certificate.