GitHub Advisoryadvisory · Go

GHSA-28pq-6qxg-wg5r 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)

MEDIUM Published 2026-07-01 Modified 2026-09-02 Official page ↗
Aliases
CVE-2026-48824
Ecosystem
Go
Packages
github.com/axllent/mailpit
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Weaknesses
CWE-770
Reviewed
2026-07-01

Affected packages

• Go github.com/axllent/mailpit: < 1.30.1 → fixed in 1.30.1

Description

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 ` 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 axllent/mailpit:latest 2) baseline RSS docker stats mailpit-test --no-stream --format '{{.MemUsage}}' → 8.473MiB / 5.772GiB 3) trigger python3 - 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.

Related

Alias · CVE (1)

  • CVE-2026-48824 Mailpit is an email testing tool and API for developers. vulnerability

Weakness · CWE (1)

  • CWE-770 Allocation of Resources Without Limits or Throttling weakness

References