CVE-2026-23829

Mailpit Stop: SMTP Header Injection via Regex Failure

Alon Barad
Alon Barad
Software Engineer

Jan 20, 2026·7 min read·4 visits

Executive Summary (TL;DR)

Mailpit, a popular email testing tool, relied on a permissive regular expression to validate sender and recipient addresses in SMTP commands. The regex failed to block control characters like Carriage Return (`\r`). This allows an unauthenticated attacker to inject arbitrary SMTP headers into the captured emails, potentially confusing downstream parsers or the Mailpit UI. The fix involves replacing the regex trust logic with the standard library's `net/mail` parser.

A classic case of 'Regex is not a parser' leads to SMTP Header Injection in the popular Mailpit development tool. By failing to sanitize control characters in email addresses, Mailpit allows attackers to rewrite message headers.

The Hook: When Dev Tools Bite Back

We all love Mailpit. It’s the spiritual successor to MailHog—a lightweight, dependency-free binary that sits on your local machine, catches all the garbage emails your staging environment spews out, and gives you a nice UI to inspect them. It’s the 'dev's best friend' because it prevents you from accidentally emailing 50,000 real customers while testing a password reset loop. But here is the thing about development tools: we tend to trust them implicitly. We treat them like soft, cuddly toys that live behind our firewalls, safe from the horrors of the public internet.

That trust is dangerous. Mailpit implements an SMTP server (default port 1025) to catch those messages. And if you are implementing an SMTP server from scratch, you are walking into a minefield of ancient RFCs, legacy encoding standards, and text-based protocols that are notoriously difficult to parse securely. The protocol dates back to 1982 (RFC 821), a time when the internet was a small village where everyone left their doors unlocked.

CVE-2026-23829 is a reminder that even 'internal' tools need rigorous input validation. It turns out that Mailpit’s SMTP parser was a little too friendly, allowing attackers to smuggle control characters into the email envelope. This isn't just a parsing error; it's a fundamental misunderstanding of how dangerous a single byte can be when it ends up in a raw message header.

The Flaw: The Curse of the Negative Character Class

To understand this vulnerability, you have to look at how Mailpit decides what a valid email address looks like. When your application connects to Mailpit and says MAIL FROM:<admin@local>, Mailpit has to extract admin@local. A robust implementation would use a state machine or a compliant parser. Mailpit, like many projects before it, chose a Regular Expression.

The specific regex used for extracting addresses from MAIL FROM and RCPT TO commands was defined using a negative character class: [^<> ]. In English, this regex says: 'Match any character that is NOT a < (less than), > (greater than), or a space'.

Do you see the problem yet?

By defining what is forbidden (blocklisting) rather than what is allowed (allowlisting), the developers left the door open for everything else in the ASCII table. This includes the Carriage Return (\r, 0x0D), the Null byte (\0, 0x00), and other non-printable gremlins. The developer likely assumed that the surrounding SMTP syntax (the angle brackets) would contain the mess, but they forgot that inside those brackets, the regex engine will happily gobble up a \r and consider it part of the email address string.

The Code: Regex vs. RFC

Let's look at the smoking gun in internal/smtpd/smtpd.go. The vulnerability existed because the code assumed that if the regex matched, the input was safe to use as a raw string in the email headers.

The Vulnerable Code:

// mailFromRE was essentially /MAIL FROM:<([^<> ]+)>/i
match := mailFromRE.FindStringSubmatch(args)
if match != nil {
    // match[1] is blindly accepted as the sender address
    msg.From = match[1]
}

Because [^<> ] matches \r, an input like user@domain\rHeader:Val is captured in its entirety as match[1]. Mailpit then takes this string and eventually writes it into the Received header of the stored email.

The Fix (Commit 36cc06c): The patch is elegant because it stops trying to reinvent the wheel. Instead of trying to fix the regex by adding more exclusions, the maintainer brought in the heavy artillery: Go's standard net/mail library.

func extractAndValidateAddress(re *regexp.Regexp, args string) []string {
    match := re.FindStringSubmatch(args)
    // ... checks for nil ...
 
    if match[1] != "" {
        // The Critical Fix:
        _, err := mail.ParseAddress(match[1])
        if err != nil {
            return nil // Reject if not RFC compliant
        }
    }
    return match
}

mail.ParseAddress parses email addresses according to RFC 5322. It does not tolerate unquoted control characters or malformed syntax. By validating the regex capture group against a strict parser, the vulnerability is dead.

The Exploit: Smuggling Headers

Exploiting this is trivially easy if you have network access to the Mailpit instance. We don't need authentication, and we don't need complex memory corruption exploits. We just need netcat.

The goal is to inject a Received header or any other header that might modify how the email is interpreted. In SMTP, headers are separated by CRLF (\r\n). However, due to the 'bare CR' (\r) injection allowed here, we can confuse parsers that treat a lone CR as a line break, or we can simply corrupt the raw .eml file.

The Attack Chain:

  1. Connect: nc target-ip 1025
  2. Handshake: HELO evil.corp
  3. The Payload: We send a MAIL FROM command where the email address includes a Carriage Return followed by a fake header.
    MAIL FROM:<hacker@evil.com\rX-Hacked: true>
    Note: In a raw netcat session, you might need to hex-edit your payload to ensure a raw 0x0D byte is sent without the 0x0A (New Line).
  4. Finish the transaction:
    RCPT TO:<victim@local>
    DATA
    Subject: Hello
     
    I am inside your headers.
    .

When Mailpit processes this, it generates a Received header that looks something like this: Received: from evil.corp ... sender <hacker@evil.com\rX-Hacked: true>; ...

If the viewing application (or Mailpit's own UI renderer) treats that \r as a newline, the email now effectively has an X-Hacked: true header. While this example is benign, an attacker could inject Content-Type, Content-Transfer-Encoding, or Subject headers to override the actual message content.

The Impact: Why Should We Care?

You might be thinking, "It's a dev tool, who cares if the headers are messy?" That is a valid point, which is why the CVSS score is a modest 5.3. This isn't a remote root shell. However, the integrity of development tools is crucial.

First, there is the Confusion Attack. If an attacker can inject headers, they can manipulate the Subject or Date displayed in the UI. They could make a malicious email look like it came from a system administrator or a security alert system, potentially tricking a developer into clicking a link in the body (phishing the devs).

Second, there is Downstream Contamination. Developers use Mailpit to inspect the raw structure of emails (.eml files). If Mailpit produces corrupt .eml files containing bare carriage returns (which are technically illegal in SMTP headers), testing against these files might mask bugs in the actual production code. Or worse, if these .eml files are fed into automated testing pipelines that use stricter parsers, the pipeline crashes, causing a denial of service in the CI/CD process.

Finally, this serves as a masterclass in CWE-150 (Improper Neutralization of Control Sequences). It demonstrates that input validation must always be positive (allowlisting) rather than negative (blocklisting).

The Fix: Patching and hardening

The remediation is straightforward: Update Mailpit to v1.28.3. The developer, Ralph Slooten (axllent), responded quickly and the patch is robust.

Beyond just patching the binary, this vulnerability highlights the importance of network segmentation for development tools. Mailpit typically does not have authentication enabled by default on the SMTP port. It is designed to be an open bucket for testing.

Defense in Depth:

  1. Bind to Localhost: Unless you strictly need to receive mail from other containers or VMs, bind Mailpit to 127.0.0.1.
  2. Firewalling: If you run Mailpit in a shared dev environment (e.g., Kubernetes), ensure Network Policies prevent access from the public internet or untrusted namespaces.
  3. Audit your Regex: If you are writing code that parses protocols, stop using Regex. Use a parser library. If you must use Regex, always use ^ and $ anchors and strict allow-lists (e.g., ^[a-zA-Z0-9@._-]+$) rather than exclude-lists.

Fix Analysis (1)

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
EPSS Probability
0.04%
Top 89% most exploited

Affected Systems

Mailpit < v1.28.3

Affected Versions Detail

Product
Affected Versions
Fixed Version
Mailpit
axllent
< 1.28.31.28.3
AttributeDetail
CWECWE-93 (CRLF Injection)
CVSS v3.15.3 (Medium)
Attack VectorNetwork (SMTP)
Exploit StatusPoC Available
Patch StatusReleased (v1.28.3)
ProtocolSMTP (Port 1025)
CWE-93
CRLF Injection

Improper Neutralization of CRLF Sequences

Vulnerability Timeline

Fix committed by vendor
2026-01-14
Security Advisory Published
2026-01-18
CVE Assigned
2026-01-19

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.