Encryption Configuration
Encryption Configuration
The platform encrypts sensitive fields (names, dates of birth, and other PII) using AES-256-GCM. The encryption key is derived from the ENCRYPTION_SECRET environment variable via PBKDF2 (100,000 iterations).
Encrypted values are stored in the format: enc:iv:authTag:ciphertext (all base64).
Environment Variables
| Variable | Required | Description |
|---|---|---|
ENCRYPTION_SECRET | Yes, in production | The raw secret from which the AES-256-GCM key is derived. Minimum 32 characters. Generate with openssl rand -hex 32. |
ENCRYPTION_ENABLED | No | Set to "false" to explicitly disable encryption. The only supported opt-out mechanism. |
Production Enforcement (SEC-06)
As of v0.1.40, the encryption module includes a startup guard that runs at module import time. If NODE_ENV=production and ENCRYPTION_ENABLED is not set to "false", the following checks are enforced:
- If
ENCRYPTION_SECRETis absent or empty → fatal error, application refuses to start. - If
ENCRYPTION_SECRETis shorter than 32 characters → fatal error, application refuses to start.
This prevents silent plaintext storage of PII due to a misconfigured or forgotten secret. The error is surfaced by Next.js at startup, before any request is served.
Example fatal error messages
[FATAL] ENCRYPTION_SECRET is not set in a production environment.
All PII would be stored as plaintext, violating GDPR/HIPAA requirements.
Generate a key with: openssl rand -hex 32
[FATAL] ENCRYPTION_SECRET is too short (16 chars).
A minimum of 32 characters is required to ensure sufficient entropy.
Generate a compliant key with: openssl rand -hex 32
Generating a Compliant Key
openssl rand -hex 32
Copy the output (64 hex characters, which exceeds the 32-character minimum) and set it as your ENCRYPTION_SECRET.
Disabling Encryption (Dev / CI)
To run without encryption in non-production environments (e.g. CI pipelines, local development with no real PII), set:
ENCRYPTION_ENABLED=false
This is the only supported way to opt out. Do not simply leave ENCRYPTION_SECRET unset in production — the application will throw a fatal error.
When ENCRYPTION_ENABLED=false is set:
encryptField()returns plaintext unchanged.- A security warning is logged at startup.
- The
/api/healthendpoint reports encryption status aswarnrather thanpass.
Key Quality Rules
Prior to v0.1.40, the module used an includes("dummy") substring check to detect placeholder secrets. This heuristic has been removed. Key quality is now enforced exclusively by minimum length (≥ 32 characters), which is objective and unambiguous.
Crypto Failure Behaviour
As of v0.1.40, encryptField() re-throws any crypto error rather than silently falling back to plaintext. Callers should handle the exception and abort the write operation rather than storing unencrypted PII.
Health Check
The /api/health endpoint reports encryption status under the encryption key. The status values are:
| Status | Meaning |
|---|---|
pass | ENCRYPTION_SECRET is present and meets the minimum length requirement. |
warn | ENCRYPTION_ENABLED=false (explicit opt-out), or secret is absent/short in a non-production environment. |
fail | Secret is absent or too short in a production environment — this is a critical misconfiguration. |
.env.example Reference
# AES-256-GCM encryption for sensitive fields (names, dates of birth, etc.).
# The key is derived from ENCRYPTION_SECRET via PBKDF2 (100 000 iterations).
# ENCRYPTION_SECRET
# Required in production (NODE_ENV=production). The app will REFUSE TO START
# if this is absent or shorter than 32 characters in production.
# Generate a compliant key: openssl rand -hex 32
ENCRYPTION_SECRET= # Min 32 chars. Example: openssl rand -hex 32
# ENCRYPTION_ENABLED
# Set to "false" to explicitly opt out of encryption in non-production
# environments (dev, CI, staging without real PII).
# Default: encryption is active when ENCRYPTION_SECRET meets the minimum
# length requirement.
ENCRYPTION_ENABLED= # "false" to disable; omit (or leave blank) to enable