All Docs
FeaturesCalmony Sanctions MonitorUpdated March 11, 2026

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:

  1. No production guard. The accommodation was applied unconditionally, with no check against NODE_ENV. A production deployment with a missing or placeholder ENCRYPTION_SECRET would behave identically to a development environment, silently storing plaintext.

  2. 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_SECRET is not set, or
  • ENCRYPTION_SECRET is 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:

  1. Whether ENCRYPTION_ENABLED is explicitly set to opt out (non-production environments only).
  2. Whether ENCRYPTION_SECRET meets 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

VariableRequired in productionDescription
ENCRYPTION_SECRETYesSecret key used to encrypt PII fields. Must be 32+ characters.
ENCRYPTION_ENABLEDNoSet 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:

  1. Audit your ENCRYPTION_SECRET configuration. Confirm the variable is set, is at least 32 characters, and does not contain placeholder strings such as dummy, replace-me, or similar.
  2. Upgrade to v0.1.40 immediately. The startup guard introduced in this release will confirm your configuration is valid on next deploy.
  3. Inspect stored PII. If ENCRYPTION_SECRET was 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.
  4. Rotate your ENCRYPTION_SECRET if 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.