🛡️ CVE-2026-48824 — mailpit
Description
Mailpit: Sibling-endpoint memory-exhaustion DoS via unbounded JSON body on /api/v1/messages, /api/v1/tags, and /api/v1/message/{id}/release (incomplete fix of GHSA-fpxj-m5q8-fphw)
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.0are vulnerable to the original GHSA-fpxj on/api/v1/send; versionv1.30.0carries 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:
| File | Function | MaxBytesReader? | Unauth in default deploy? |
|---|---|---|---|
| server/apiv1/send.go:45-48 (SendMessageHandler) | POST /api/v1/send | YES (50 MB) | YES (via sendAPIAuthMiddleware falling back to middleWareFunc) |
| server/apiv1/messages.go:107 (SetReadStatus) | PUT /api/v1/messages | NO | YES |
| server/apiv1/messages.go:187 (DeleteMessages) | DELETE /api/v1/messages | NO | YES |
| server/apiv1/tags.go:54 (SetMessageTags) | PUT /api/v1/tags | NO | YES |
| server/apiv1/release.go:55 (ReleaseMessage) | POST /api/v1/message/{id}/release | NO | YES |
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 a
How this vulnerability can be exploited
This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality none, integrity none, availability low.
Weakness class
CVE-2026-48824 is classified as CWE-770: Allocation of Resources Without Limits. Resources are allocated on request with no cap, so a client can exhaust them.
Affected software
CVE-2026-48824 is recorded against 2 packages.
- github.com/axllent/mailpit
- mailpit (fixed in 1.30.1)
Timeline and source
Published on 1 July 2026 and last revised on 7 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.
References
Details
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| github.com/axllent/mailpit | — | — |
| mailpit | — | 1.30.1 |
References
Similar Threats
- Medium CVE-2026-55187
- Medium CVE-2026-45709
- Medium CVE-2026-45711
- Medium CVE-2026-45712
- High CVE-2026-45713
More CVE 2026 advisories
Browse all of CVE 2026 in the advisory index.
Vulnerability Monitoring
Track new vulnerabilities in mailpit
CVE-2026-48824 is rated CVSS 5.3 Medium. BotEraser monitors your WordPress installation and notifies you when software you use appears in our vulnerability database.
Set Up Free Alerts →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.