Security Advisory: SEC-06 — Cryptographic Failures in encryptField()
Security Advisory: SEC-06 — Cryptographic Failures in encryptField()
Severity: Critical
OWASP Category: A02:2021 — Cryptographic Failures
Control ID: SEC-06
Fixed in: v0.1.40
Affected file: src/lib/encryption.ts
Overview
Prior to v0.1.40, the encryptField() function in src/lib/encryption.ts contained a silent degradation path: if the ENCRYPTION_SECRET environment variable was absent — or if its value contained the substring 'dummy' — the function would return the input value in plaintext rather than an encrypted ciphertext.
Because the only signal of this degradation was a console.log message, a production environment with a missing or misconfigured ENCRYPTION_SECRET could operate indefinitely while storing all PII (names, dates of birth, and other screened-entity fields) in plaintext in the database, with no hard failure or operator alert.
Root Cause
The original design included a dev/CI accommodation — skipping encryption when a real secret was not available — intended to make local development and automated testing easier. Two problems arose from this approach:
-
No production guard. The accommodation was applied unconditionally, with no check against
NODE_ENV. A production deployment with a missing or placeholderENCRYPTION_SECRETwould behave identically to a development environment, silently storing plaintext. -
Overly broad bypass condition. The check disabled encryption for any secret containing the string
'dummy'. This could match a key that was never updated from a deployment template (e.g.dummy-replace-me-with-real-key), creating a false sense of security — the variable appears to be set, but encryption is still disabled.
Fix Detail
Startup enforcement
A startup-time validation check has been added. When NODE_ENV=production, the application will now throw a hard error if:
ENCRYPTION_SECRETis not set, orENCRYPTION_SECRETis shorter than 32 characters.
This prevents the application from starting in an insecure state — operators will receive an immediate, unambiguous failure rather than a silent degradation.
Removal of the 'dummy' check
The substring match against 'dummy' has been removed entirely. The only criteria for whether encryption is active are:
- Whether
ENCRYPTION_ENABLEDis explicitly set to opt out (non-production environments only). - Whether
ENCRYPTION_SECRETmeets the minimum 32-character length requirement.
Explicit ENCRYPTION_ENABLED flag
A dedicated ENCRYPTION_ENABLED environment variable has been introduced for non-production environments where skipping encryption is intentional. This makes the intent explicit and auditable, rather than inferred from the absence or placeholder value of ENCRYPTION_SECRET.
Configuration Reference
| Variable | Required in production | Description |
|---|---|---|
ENCRYPTION_SECRET | Yes | Secret key used to encrypt PII fields. Must be 32+ characters. |
ENCRYPTION_ENABLED | No | Set to false to explicitly disable encryption in non-production environments. Has no effect when NODE_ENV=production. |
Example: valid production configuration
NODE_ENV=production
ENCRYPTION_SECRET=a-very-long-and-random-secret-key-at-least-32-chars
Example: local development without encryption
NODE_ENV=development
ENCRYPTION_ENABLED=false
Example: startup error (production, missing secret)
Error: ENCRYPTION_SECRET is not set or does not meet the minimum length
requirement (32 characters). Cannot start in production without encryption.
Action Required for Operators
If you are running any version prior to v0.1.40 in production:
- Audit your
ENCRYPTION_SECRETconfiguration. Confirm the variable is set, is at least 32 characters, and does not contain placeholder strings such asdummy,replace-me, or similar. - Upgrade to v0.1.40 immediately. The startup guard introduced in this release will confirm your configuration is valid on next deploy.
- Inspect stored PII. If
ENCRYPTION_SECRETwas previously unset or matched the'dummy'bypass, records written during that window will be stored in plaintext. Review your database records and assess whether a re-encryption pass is necessary. - Rotate your
ENCRYPTION_SECRETif in doubt. If there is any uncertainty about whether a valid key was in use, treat existing encrypted records as potentially compromised and rotate accordingly.
No Breaking Change for Correctly Configured Deployments
For deployments that already had a valid ENCRYPTION_SECRET (≥32 characters, not a placeholder), this release is a non-breaking upgrade. Existing encrypted records are unaffected — only the startup validation and key-check logic has changed.