forwardemail / forwardemail/forwardemail.net

[fix] 2.14.2 disabling legacy AES-256-CBC decryption breaks pre-migration stored ciphertext (550 Decryption failed for all supported formats)

Open
#584 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
1.7k
Forks
203
PR merge metrics
No merged PRs in 30d

Description

## Summary

Release 2.14.2 (commit f4dd4364, "fix: harden calendar, redirect, DMARC, and crypto boundaries")
gates legacy AES-256-CBC decryption in `helpers/encrypt-decrypt.js` behind a new
`ALLOW_LEGACY_AES_CBC_DECRYPTION` env var, defaulted off. For roughly an hour after that release
went out, outbound mail on one of the domains on our account failed with
`550 Decryption failed for all supported formats`, `Error from: Forward Email`.

That is consistent with a stored value still in the pre-migration ciphertext format becoming
unreadable once the flag defaulted to off. The likeliest candidate is `domain.dkim_private_key` —
it is the only `decrypt()` call on the path after a message has already been accepted
(`helpers/process-email.js:760`); the other call in the SMTP path
(`helpers/on-data-smtp.js:83`) surfaces as `535`, not `550`.

## Evidence

- Deliverability log export (`/v1/logs/download`) for the affected domain shows delivery failing
with `550 Decryption failed for all supported formats` at 2026-09-04 22:02–22:15 UTC — 22 to 35
minutes after 2.14.2 was tagged (2026-09-04T21:40:54Z).
- Delivery resumed cleanly by 23:00 UTC the same day. Every hourly forward from then through
2026-09-05 is `250`, and there have been no further occurrences.
- The affected domain's record dates to 2025-03-15, predating the encryption-format change tracked
in #273 (closed 2025-11-23) — consistent with it still holding legacy-CBC ciphertext.
- Nothing changed on our side, before or during the window. MX, SPF, DMARC, the DKIM TXT record and
the `fe-bounces` CNAME were all valid throughout.

## Impact

Small for us: four messages lost inside about an hour, self-healed with no action on our part.

Raising it because the failure mode is quiet and generic. The same helper backs SMTP auth password
decryption and webhook signing, so any account whose stored values predate the v2 format could hit
this on the same upgrade, and the only symptom is a `550` whose text does not say which value failed
to decrypt or that a legacy format was involved.

## Suggested follow-up

- Run a one-time migration re-encrypting pre-v2 ciphertext to the current format before the legacy
path is disabled by default, rather than relying on operators to know to set the flag.
- Log distinctly when the legacy-CBC branch is reached but disabled, instead of falling through to
the generic "failed for all supported formats" error — that would have made this self-diagnosing.

Happy to supply the exact log rows if useful.

Contributor guide

Open the contributing guide

Research direction

Start in helpers/encrypt-decrypt.js, then trace the decrypt() calls at helpers/process-email.js:760 and helpers/on-data-smtp.js:83; review the encryption-format change tracked in #273. Confirm how pre-v2 ciphertext is detected and how the disabled legacy branch currently fails. Done means pre-v2 values are migrated before the legacy path is disabled and the disabled-legacy case is logged distinctly.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.