From 53a6670e608d734c93e0ecb5372469fbb6fc4658 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 17 Sep 2026 20:55:39 +0200 Subject: [PATCH] fix: strip CR/LF from read-receipt header values (GHSA-w38p-hpqv-g89c) --- lib/__tests__/mdn-header-injection.test.ts | 152 +++++++++++++++++++++ lib/mdn.ts | 63 +++++++-- 2 files changed, 202 insertions(+), 13 deletions(-) create mode 100644 lib/__tests__/mdn-header-injection.test.ts diff --git a/lib/__tests__/mdn-header-injection.test.ts b/lib/__tests__/mdn-header-injection.test.ts new file mode 100644 index 000000000..0da11a53a --- /dev/null +++ b/lib/__tests__/mdn-header-injection.test.ts @@ -0,0 +1,152 @@ +import { describe, it, expect } from 'vitest'; +import { buildMdnMessage } from '@/lib/mdn'; + +// GHSA-w38p-hpqv-g89c: every value that lands in a header of the read receipt +// originates with the sender of the message being answered, and Stalwart hands +// it back already RFC 2047-decoded. A bare CRLF smuggled inside an encoded-word +// used to be interpolated verbatim into the Subject: line, letting the sender +// append From/Reply-To/Content-Type headers and a body to a message the +// victim's own account files into Sent and submits. + +const base = { + to: 'sender@other.com', + fromEmail: 'me@example.com', + originalMessageId: 'orig@other.com', +}; + +// The decoded subject from the advisory's proof of concept. +const INJECTED_SUBJECT = + 'Invoice\r\nFrom: IT Helpdesk \r\nReply-To: attacker@evil.test\r\n' + + 'Content-Type: text/html; charset=utf-8\r\n\r\n

Forged