Summary
Mailpit's SMTP server reads each command line with an unbounded bufio.Reader.ReadString('\n') before parsing the command or enforcing any protocol length limit. A remote SMTP client can send an oversized single command line and force Mailpit to allocate attacker-controlled memory before the server returns a syntax error or times out, even though RFC 5321 limits SMTP command lines to 512 octets including CRLF.
Technical Details
Mailpit enables SMTP by default. config/config.go sets SMTPListen = "[::]:1025", and cmd/root.go calls smtpd.Listen() during normal startup. The SMTP server configures recipient and message DATA size limits in internal/smtpd/main.go, including the default 50 MiB MaxMessageSize, but those limits do not apply to command lines.
The vulnerable path is in the SMTP command loop. internal/smtpd/smtpd.go calls s.readLine() for every command before parsing the verb or arguments:
line, err := s.readLine()
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
s.writef("421 4.4.2 %s %s ESMTP Service closing transmission channel after timeout exceeded", s.srv.Hostname, s.srv.AppName)
}
break
}
verb, args := s.parseLine(line)
readLine() then buffers until newline without a maximum length:
func (s *session) readLine() (string, error) {
if s.srv.Timeout > 0 {
_ = s.conn.SetReadDeadline(time.Now().Add(s.srv.Timeout))
}
line, err := s.br.ReadString('\n')
if err != nil {
return "", err
}
line = strings.TrimSpace(line)
return line, err
}
This violates the SMTP command-line invariant before later validation can help. Address length validation in extractAndValidateAddress() runs only after the entire command line has already been buffered and parsed. The DATA reader has a separate srv.MaxSize check, but the issue is pre-DATA command input.
PoV
The following bounded test exercises the same command reader with a normal NOOP control and an 8 MiB oversized command line:
package smtpd
import (
"bufio"
"bytes"
"strings"
"testing"
)
func TestUnboundedSMTPCommandLinePoV(t *testing.T) {
short := "NOOP\r\n"
shortSession := session{srv: &Server{}, br: bufio.NewReader(strings.NewReader(short))}
shortLine, err := shortSession.readLine()
if err != nil {
t.Fatalf("short control readLine failed: %v", err)
}
t.Logf("short control: accepted len=%d command=%q", len(shortLine), shortLine)
oversizedLen := 8 * 1024 * 1024
oversized := strings.Repeat("X", oversizedLen) + "\r\n"
oversizedSession := session{srv: &Server{}, br: bufio.NewReader(bytes.NewBufferString(oversized))}
oversizedLine, err := oversizedSession.readLine()
if err != nil {
t.Fatalf("oversized readLine failed: %v", err)
}
t.Logf("oversized command: accepted len=%d; RFC 5321 command-line limit is 512 octets including CRLF", len(oversizedLine))
if len(oversizedLine) != oversizedLen {
t.Fatalf("readLine length = %d, want %d", len(oversizedLine), oversizedLen)
}
if len(oversizedLine) <= 512 {
t.Fatalf("oversized command did not exceed SMTP command-line limit")
}
}
PoC
From a Mailpit checkout, save the PoV above as internal/smtpd/smtp_command_line_pov_test.go and run:
docker run --rm -v "$PWD:/src" -w /src golang:1.25 go test ./internal/smtpd -run TestUnboundedSMTPCommandLinePoV -v
On current develop commit cd7661fd5b23cce1e218b583b21e157cfa612051, the test prints:
=== RUN TestUnboundedSMTPCommandLinePoV
smtp_command_line_pov_test.go:17: short control: accepted len=4 command="NOOP"
smtp_command_line_pov_test.go:26: oversized command: accepted len=8388608; RFC 5321 command-line limit is 512 octets including CRLF
--- PASS: TestUnboundedSMTPCommandLinePoV (0.01s)
PASS
ok github.com/axllent/mailpit/internal/smtpd 0.017s
The same test against release v1.30.3 commit 6acf5b8f942ab0e007b1227d31dfb3c3303e8d13 prints:
=== RUN TestUnboundedSMTPCommandLinePoV
smtp_command_line_pov_test.go:17: short control: accepted len=4 command="NOOP"
smtp_command_line_pov_test.go:26: oversized command: accepted len=8388608; RFC 5321 command-line limit is 512 octets including CRLF
--- PASS: TestUnboundedSMTPCommandLinePoV (0.02s)
PASS
ok github.com/axllent/mailpit/internal/smtpd 0.020s
The NOOP control shows the normal reader path. The oversized case shows the parser accepting an 8 MiB command line into memory instead of rejecting at the SMTP command-line limit.
Impact
An unauthenticated remote client that can reach Mailpit's SMTP listener can force heap allocation proportional to a single command line before any SMTP command is parsed. Repeating the input across concurrent connections can consume process memory and degrade or deny Mailpit service availability. This is distinct from message DATA size handling: the default MaxMessageSize cap is enforced after DATA, while the command-line reader is reached before DATA and before MAIL FROM SIZE= handling.
Suggested advisory metadata: CWE-400 (Uncontrolled Resource Consumption). Conservative CVSS 3.1 vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H, base score 7.5 (High). If a deployment explicitly binds SMTP to trusted loopback-only clients, environmental severity may be lower; the project default is a network SMTP listener on [::]:1025.
Suggested Fix
Bound SMTP command-line reads before buffering the full line. For ordinary SMTP commands, reject input exceeding RFC 5321's 512-octet command-line limit including CRLF, returning a 500 5.5.2-style command-line-too-long response before allocating the entire attacker-controlled line. Apply the same pre-parse bound to AUTH continuation lines because handleAuthLogin(), handleAuthPlain(), and handleAuthCramMD5() also call readLine(). Consider adding a similar POP3 command-line cap in internal/pop3/server.go, where the optional POP3 server also uses ReadString('\n') for commands.
Regression tests should cover a normal short command, an over-limit command line, and over-limit AUTH continuation input. They should assert that the over-limit cases fail without returning the oversized string to command parsing.
Affected Package/Versions
Confirmed affected:
- Current
develop: cd7661fd5b23cce1e218b583b21e157cfa612051
- Latest release:
v1.30.3, tag commit 6acf5b8f942ab0e007b1227d31dfb3c3303e8d13, published 2026-06-27
No fixed version was identified during this review.
Advisory History
The closest published Mailpit advisory is GHSA-fpxj-m5q8-fphw, which covers unauthenticated memory exhaustion through unlimited SMTP DATA and /api/v1/send body sizes. This report is different: it targets the pre-DATA SMTP command-line reader before srv.MaxSize, MAIL FROM SIZE=, or DATA handling applies.
Other published Mailpit advisories checked were GHSA-28pq-6qxg-wg5r for HTTP JSON body limits, GHSA-54wq-72mp-cq7c for SMTP header injection, GHSA-w4vj-r5pg-3722 for proxy CSS map concurrency, GHSA-qx5x-85p8-vg4j for dump path traversal, the SSRF/link-check/proxy advisories GHSA-8v65-47jx-7mfr, GHSA-mpf7-p9x7-96r3, GHSA-6jxm-fv7w-rw5j, GHSA-j3fj-qppj-fmmc, GHSA-w4mc-hhc6-xp28, and GHSA-524m-q5m7-79mm for CSWSH. None of these describe unbounded SMTP command-line buffering.
Public Mailpit issue searches for SMTP command line too long ReadString, SMTP 512 octets command line, SMTP memory DoS command line, and ReadString smtpd found no matching issue. Public commit searches for ReadString smtpd, command line too long, and MaxMessageSize smtp found no matching fix. No prior submitted, ready-for-review, or completed-but-unsubmitted Mailpit report available in the review materials matched this root cause.
References
References
Summary
Mailpit's SMTP server reads each command line with an unbounded
bufio.Reader.ReadString('\n')before parsing the command or enforcing any protocol length limit. A remote SMTP client can send an oversized single command line and force Mailpit to allocate attacker-controlled memory before the server returns a syntax error or times out, even though RFC 5321 limits SMTP command lines to 512 octets including CRLF.Technical Details
Mailpit enables SMTP by default.
config/config.gosetsSMTPListen = "[::]:1025", andcmd/root.gocallssmtpd.Listen()during normal startup. The SMTP server configures recipient and message DATA size limits ininternal/smtpd/main.go, including the default 50 MiBMaxMessageSize, but those limits do not apply to command lines.The vulnerable path is in the SMTP command loop.
internal/smtpd/smtpd.gocallss.readLine()for every command before parsing the verb or arguments:readLine()then buffers until newline without a maximum length:This violates the SMTP command-line invariant before later validation can help. Address length validation in
extractAndValidateAddress()runs only after the entire command line has already been buffered and parsed. The DATA reader has a separatesrv.MaxSizecheck, but the issue is pre-DATA command input.PoV
The following bounded test exercises the same command reader with a normal
NOOPcontrol and an 8 MiB oversized command line:PoC
From a Mailpit checkout, save the PoV above as
internal/smtpd/smtp_command_line_pov_test.goand run:On current
developcommitcd7661fd5b23cce1e218b583b21e157cfa612051, the test prints:The same test against release
v1.30.3commit6acf5b8f942ab0e007b1227d31dfb3c3303e8d13prints:The
NOOPcontrol shows the normal reader path. The oversized case shows the parser accepting an 8 MiB command line into memory instead of rejecting at the SMTP command-line limit.Impact
An unauthenticated remote client that can reach Mailpit's SMTP listener can force heap allocation proportional to a single command line before any SMTP command is parsed. Repeating the input across concurrent connections can consume process memory and degrade or deny Mailpit service availability. This is distinct from message DATA size handling: the default
MaxMessageSizecap is enforced afterDATA, while the command-line reader is reached before DATA and beforeMAIL FROM SIZE=handling.Suggested advisory metadata: CWE-400 (Uncontrolled Resource Consumption). Conservative CVSS 3.1 vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H, base score 7.5 (High). If a deployment explicitly binds SMTP to trusted loopback-only clients, environmental severity may be lower; the project default is a network SMTP listener on[::]:1025.Suggested Fix
Bound SMTP command-line reads before buffering the full line. For ordinary SMTP commands, reject input exceeding RFC 5321's 512-octet command-line limit including CRLF, returning a
500 5.5.2-style command-line-too-long response before allocating the entire attacker-controlled line. Apply the same pre-parse bound to AUTH continuation lines becausehandleAuthLogin(),handleAuthPlain(), andhandleAuthCramMD5()also callreadLine(). Consider adding a similar POP3 command-line cap ininternal/pop3/server.go, where the optional POP3 server also usesReadString('\n')for commands.Regression tests should cover a normal short command, an over-limit command line, and over-limit AUTH continuation input. They should assert that the over-limit cases fail without returning the oversized string to command parsing.
Affected Package/Versions
Confirmed affected:
develop:cd7661fd5b23cce1e218b583b21e157cfa612051v1.30.3, tag commit6acf5b8f942ab0e007b1227d31dfb3c3303e8d13, published 2026-06-27No fixed version was identified during this review.
Advisory History
The closest published Mailpit advisory is
GHSA-fpxj-m5q8-fphw, which covers unauthenticated memory exhaustion through unlimited SMTP DATA and/api/v1/sendbody sizes. This report is different: it targets the pre-DATA SMTP command-line reader beforesrv.MaxSize,MAIL FROM SIZE=, or DATA handling applies.Other published Mailpit advisories checked were
GHSA-28pq-6qxg-wg5rfor HTTP JSON body limits,GHSA-54wq-72mp-cq7cfor SMTP header injection,GHSA-w4vj-r5pg-3722for proxy CSS map concurrency,GHSA-qx5x-85p8-vg4jfor dump path traversal, the SSRF/link-check/proxy advisoriesGHSA-8v65-47jx-7mfr,GHSA-mpf7-p9x7-96r3,GHSA-6jxm-fv7w-rw5j,GHSA-j3fj-qppj-fmmc,GHSA-w4mc-hhc6-xp28, andGHSA-524m-q5m7-79mmfor CSWSH. None of these describe unbounded SMTP command-line buffering.Public Mailpit issue searches for
SMTP command line too long ReadString,SMTP 512 octets command line,SMTP memory DoS command line, andReadString smtpdfound no matching issue. Public commit searches forReadString smtpd,command line too long, andMaxMessageSize smtpfound no matching fix. No prior submitted, ready-for-review, or completed-but-unsubmitted Mailpit report available in the review materials matched this root cause.References
GHSA-fpxj-m5q8-fphw: GHSA-fpxj-m5q8-fphwGHSA-28pq-6qxg-wg5r: GHSA-28pq-6qxg-wg5rGHSA-54wq-72mp-cq7c: GHSA-54wq-72mp-cq7cReferences